You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter中SingleChildScrollView与Stack、Container搭配异常求助

Flutter登录页背景图不显示且屏幕变黑问题解决

我在Flutter开发中,尝试在Scaffold内用Container设置背景图,再通过SingleChildScrollView嵌套Stack堆叠内容时,出现屏幕变黑且背景图无法显示的问题,问题出在login.dart文件的60-70行。以下是完整代码:

import 'package:flutter/material.dart'; 
import 'package:provider/provider.dart';

import 'authentication.dart';

class LoginScreen extends StatelessWidget {
  const LoginScreen({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return WillPopScope(onWillPop: () async => false, child: 
      Scaffold(
        backgroundColor: Colors.transparent,
        appBar: AppBar(
          elevation: 0,
          bottomOpacity: 0.0,
          title: IconButton(icon:Image.asset('assets/topImage.png'), iconSize: 65, 
            onPressed: () {
              Navigator.pop(context);
            },
          ),
          centerTitle: true,
        ),
        body: const Inputs(),
        extendBodyBehindAppBar: true,
      )
    );
  }
}

class Inputs extends StatefulWidget {
  const Inputs({Key? key}) : super(key: key);

  @override
  State<Inputs> createState() => _InputsState();
}

class _InputsState extends State<Inputs> {
  final emailInController = TextEditingController();
  final passwordInController = TextEditingController();

  final emailUpController = TextEditingController();
  final passwordUpController = TextEditingController();

  String loginTitle = "Login";
  bool _isVisible = true;
  String buttonText = "Sign In";
  String infoText = "Enter your email above and click \"Reset Password\". An email will be sent to reset your password";

  @override
  void dispose() {
    emailInController.dispose();
    passwordInController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return SingleChildScrollView(
        child: Stack(children: [
          Container(
            decoration: const BoxDecoration(
              image: DecorationImage(
                image: AssetImage("assets/bgImage.jpeg"),
                fit: BoxFit.cover,
              ),
            ),
          ),
          Column(
          children: <Widget>[
            const SizedBox(height:120),

            // Top Login Text
            Center(child: Text(loginTitle, style: const TextStyle(fontSize: 35, fontFamily: 'samarkan')),),

            // Email Enter (Sign-In)
            Padding(
              padding: const EdgeInsets.symmetric(horizontal: 15),
              child: TextField(
                controller: emailInController,
                decoration: const InputDecoration(
                  border: OutlineInputBorder(),
                  labelText: 'Email',
                  hintText: 'Enter valid email id as abcgamil.com'
                )
              ),
            ),

            Visibility(
              visible: _isVisible,
              child: Column(
                children: [
                // Sign In Password
                Padding(
                  padding: const EdgeInsets.only(
                    left: 15.0, right: 15.0, top: 15, bottom: 0
                  ),
                  child: TextField(
                    controller: passwordInController,
                    obscureText: true,
                    decoration: const InputDecoration(
                      border: OutlineInputBorder(),
                      labelText: 'Password',
                      hintText: 'Enter Secure Password'
                    ),
                  )
                ),

                // Forgot Password Text
                TextButton(
                  onPressed: (){
                    setState(() {
                      loginTitle = "Password Reset";
                      _isVisible = !_isVisible;
                      buttonText = "Reset Password";
                    });
                  },  
                  child: const Text('Forgot Password', style: TextStyle(color: Colors.black, fontSize: 15),),
                ),
                ],
              )
            ),

            // Text Above Submit
            Visibility(
              visible: !_isVisible,
              child: Text(infoText, 
              style: const TextStyle(fontSize: 20, fontFamily: "Georgia"), 
              textAlign: TextAlign.center)
            ),

            // Sign In Submit
            InkWell(
              onTap: () {
                context.read<AuthenticationService>().signIn(
                  email: emailInController.text.trim(), 
                  password: passwordInController.text.trim(),);

                passwordInController.text = "";

                if(!_isVisible) {
                  context.read<AuthenticationService>().passReset(email: emailInController.text);

                  setState(() {
                    _isVisible = true;
                    buttonText = "Sign In";
                    loginTitle = "Login";
                  });
                }

                emailInController.text = "";
              },
              child: Ink(
                height: 50, 
                width: 250, 
                decoration: BoxDecoration(
                  color: Colors.black, 
                  borderRadius: BorderRadius.circular(20)
                ),
                child: Center(child: Text(buttonText, style: const TextStyle(color: Colors.white)))
              )
            ),

            // Sign Up Text
            Divider(height: 40, color: Colors.grey[800], indent: 10, endIndent: 10,),
            const Center(child: Text("Sign Up", style: TextStyle(fontSize: 35, fontFamily: 'samarkan'))),

            // Sign Up Email
            Padding(
              padding: const EdgeInsets.symmetric(horizontal: 15),
              child: TextField(
                controller: emailUpController,
                decoration: const InputDecoration(
                  border: OutlineInputBorder(),
                  labelText: 'Email',
                  hintText: 'Enter valid email id as abcgamil.com'
                )
              ),
            ),

            // Sign Up Password
            Padding(
              padding: const EdgeInsets.only(
                left: 15.0, right: 15.0, top: 15, bottom: 0
              ),
              child: TextField(
                controller: passwordUpController,
                obscureText: true,
                decoration: const InputDecoration(
                  border: OutlineInputBorder(),
                  labelText: 'Password',
                  hintText: 'Enter Secure Password'
                ),
              )
            ),

            // Sign Up Submit
            Padding(
              padding: const EdgeInsets.only(top: 15),
              child: InkWell(
                onTap: () {
                  context.read<AuthenticationService>().signUp(
                    email: emailUpController.text.trim(), 
                    password: passwordUpController.text.trim(),);
                   
                  emailInController.text = "";
                  passwordInController.text = "";
                },
                child: Ink(
                  height: 50, 
                  width: 250, 
                  decoration: BoxDecoration(
                    color: Colors.black, 
                    borderRadius: BorderRadius.circular(20)
                  ),
                  child: const Center(child: Text("Sign Up", style: TextStyle(color: Colors.white)))
                )
              )
            ),
          ]
        ),
      ])
    );
  }
}

问题原因

作为背景的Container没有设置尺寸约束,SingleChildScrollView的子组件Stack在无约束时,Container会被压缩到最小尺寸,导致背景图无法显示;同时Stack上层的Column默认背景透明,Scaffold的backgroundColor设为transparent,最终显示的是Flutter默认的底层黑色画布。

修复方案

给背景Container添加尺寸约束,确保它铺满整个屏幕空间,以下是两种可行的修改方式:

方式一:使用MediaQuery设置固定宽高

修改Inputs组件build方法中的Stack部分:

@override
Widget build(BuildContext context) {
  return SingleChildScrollView(
    child: Stack(
      children: [
        SizedBox(
          height: MediaQuery.of(context).size.height,
          width: MediaQuery.of(context).size.width,
          child: Container(
            decoration: const BoxDecoration(
              image: DecorationImage(
                image: AssetImage("assets/bgImage.jpeg"),
                fit: BoxFit.cover,
              ),
            ),
          ),
        ),
        // 原有Column内容保持不变
        Column(
          children: <Widget>[
            // ... 原有代码 ...
          ]
        ),
      ]
    )
  );
}

方式二:使用Positioned.fill自动填充Stack

这种方式更优雅,无需手动设置宽高:

@override
Widget build(BuildContext context) {
  return SingleChildScrollView(
    child: ConstrainedBox(
      // 确保ScrollView子组件至少占满屏幕高度
      constraints: BoxConstraints(
        minHeight: MediaQuery.of(context).size.height,
      ),
      child: Stack(
        children: [
          Positioned.fill(
            child: Container(
              decoration: const BoxDecoration(
                image: DecorationImage(
                  image: AssetImage("assets/bgImage.jpeg"),
                  fit: BoxFit.cover,
                ),
              ),
            ),
          ),
          Column(
            children: <Widget>[
              // ... 原有代码 ...
            ]
          ),
        ]
      ),
    ),
  );
}

额外注意事项

  • 确认assets/bgImage.jpeg已在pubspec.yaml中正确配置资源路径,示例配置:
    assets:
      - assets/bgImage.jpeg
      - assets/topImage.png
    
  • 如果内容较长,SingleChildScrollView依然可以正常滚动,不会影响背景图显示

内容的提问来源于stack exchange,提问作者PraneethO

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.24 22:57:20