Flutter中解决自定义Dialog小屏幕无法滚动被截断的问题
问题
我制作了一个供用户修改内容的CustomDialog,发现在小屏幕设备上它无法滚动,导致内容被截断,甚至无法点击确认按钮。我看到其他相关问题中有人在调用showDialog时设置"scrolling: true",但该属性并不存在,不知道该如何配置。相关代码如下:
child: GestureDetector( onDoubleTap: () { showDialog( context: context, builder: (context) { return MaterialApp( debugShowCheckedModeBanner: false, title: 'iOrganize', theme: ThemeData.dark().copyWith( scaffoldBackgroundColor: secondBgColor, textTheme: GoogleFonts.poppinsTextTheme( Theme.of(context).textTheme) .apply(bodyColor: primaryColor), ), home: CustomDialogModificarTarea(usuario, tarjeta), ); }); },
CustomDialogModificarTarea的完整代码:
class CustomDialogModificarTarea extends StatelessWidget { final Usuario usuario; final Tarjeta tarjeta; ProyectoService proyectoService = ProyectoServiceFirebase(); UsuarioService usuarioService = UsuarioServiceFirebase(); TarjetaService tarjetaService=TarjetaServiceFirebase(); TextEditingController nombreController = TextEditingController(); TextEditingController fechaController = TextEditingController(); CustomDialogModificarTarea(this.usuario,this.tarjeta); @override Widget build(BuildContext context) { return Dialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(Consts.padding), ), elevation: 0.0, backgroundColor: Colors.transparent, child: dialogContent(context), ); } dialogContent(BuildContext context) { return Stack( children: <Widget>[ buildCard(context), buildCircle(), //...bottom card part, //...top circlular image part, ], ); } Widget buildCard(BuildContext context) { return Positioned( top: 50, left: Consts.padding, right: Consts.padding * 2, child: Container( decoration: new BoxDecoration( color: secondBgColor, shape: BoxShape.rectangle, borderRadius: BorderRadius.circular(Consts.padding), boxShadow: [ BoxShadow( color: Color.fromARGB(66, 0, 0, 0), blurRadius: 10.0, offset: const Offset(0.0, 10.0), ), ], ), padding: const EdgeInsets.only( top: Consts.avatarRadius + Consts.padding, bottom: Consts.avatarRadius + Consts.padding, left: Consts.avatarRadius + Consts.padding, right: Consts.avatarRadius + Consts.padding, ), margin: const EdgeInsets.only(top: Consts.avatarRadius), child: Column( mainAxisSize: MainAxisSize.min, // To make the card compact children: <Widget>[ const Text( "Modificar tarea", style: TextStyle( fontSize: 24.0, fontWeight: FontWeight.w700, ), ), const SizedBox(height: 16.0), TextFormField( controller: nombreController, decoration: const InputDecoration( hintText: 'Cambiar el nombre de la tarea', ), ), TextFormField( controller: fechaController, decoration: const InputDecoration( hintText: 'Cambiar la fecha de la tarea', ), ), const SizedBox(height: 16.0), Align( alignment: Alignment.bottomLeft, child: TextButton( onPressed: () { Navigator.of(context).pop(); }, child: const Text('Cancelar'), ), ), Align( alignment: Alignment.bottomCenter, child: TextButton( onPressed: () { TablaService tablaService=TablaServiceFirebase(); int pos; pos=int.tryParse(usuario.idProyectoActual)!; TarjetaService tarjetaService=TarjetaServiceFirebase(); print("Borrar Tarjetas"); for(int i=0;i<2;i++){ List<Tarjeta>? tarjetas=usuario.proyectos![pos].tablas![i].tarjetas; print(tarjetas!.length); bool borrado=false; for(int j=0; tarjetas!=null && j<tarjetas.length && !borrado;j++){ print("Segundo for"); if(tarjetas[j].id==tarjeta.id){ print("La he encontrado $i ${tarjetas[j].id}"); tarjetas.removeAt(j); usuario.proyectos![pos].tablas![i].tarjetas=tarjetas; print(usuario.proyectos![pos].tablas![i].tarjetas!.length); borrado=true; } } print("Imprimo el id: ${usuario.proyectos![pos].tablas![i].id!.split("-")[1] }"); tablaService.updateTabla(id: usuario.proyectos![pos].tablas![i].id!.split("-")[1], tipo: usuario.proyectos![pos].tablas![i].id!.split("-")[0], t: usuario.proyectos![pos].tablas![i]); } print("Salgo ta"); tarjetaService.deleteTarjeta(id: tarjeta.id); Navigator.of(context).pop(); }, child: const Text('Borrar tarea'), ), ), Align( alignment: Alignment.bottomRight, child: TextButton( onPressed: () async { if(nombreController.text!=""){ tarjeta.name=nombreController.text; } if(fechaController.text!=""){ tarjeta.fecha=fechaController.text; } tarjetaService.updateTarjeta(tarjeta: tarjeta); Navigator.of(context).pop(); }, child: const Text('Modificar'), ), ), ], ), ), ); } Widget buildCircle() { return const Positioned( right: 16, top: 50, child: const CircleAvatar( backgroundColor: Colors.blueAccent, radius: Consts.avatarRadius, ), ); } } class Consts { Consts._(); static const double padding = 16.0; static const double avatarRadius = 16.0; }
解决方案
1. 移除showDialog中的MaterialApp嵌套
showDialog无需嵌套全新的MaterialApp,直接返回CustomDialogModificarTarea即可,避免上下文混乱:
child: GestureDetector( onDoubleTap: () { showDialog( context: context, builder: (context) { return CustomDialogModificarTarea(usuario, tarjeta); } ); }, ),
2. 给Dialog内容添加滚动容器
核心问题是内容无滚动支持,将包裹内容的Column用SingleChildScrollView包裹,确保小屏幕下可滚动:
修改buildCard方法中的Container子组件:
child: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, children: <Widget>[ // 原有内容保持不变 ], ), ),
3. 优化布局减少垂直空间占用
将底部三个按钮改为Row布局,压缩垂直高度,同时调整Positioned布局避免内容挤压:
修改dialogContent方法:
dialogContent(BuildContext context) { return Stack( clipBehavior: Clip.none, // 允许头像超出容器范围 children: <Widget>[ Padding( padding: EdgeInsets.only(top: Consts.avatarRadius), child: buildCard(context), ), buildCircle(), ], ); }
修改buildCard方法,移除Positioned并调整按钮布局:
Widget buildCard(BuildContext context) { return Container( margin: EdgeInsets.symmetric(horizontal: Consts.padding), decoration: new BoxDecoration( color: secondBgColor, shape: BoxShape.rectangle, borderRadius: BorderRadius.circular(Consts.padding), boxShadow: [ BoxShadow( color: Color.fromARGB(66, 0, 0, 0), blurRadius: 10.0, offset: const Offset(0.0, 10.0), ), ], ), padding: const EdgeInsets.only( top: Consts.avatarRadius + Consts.padding, bottom: Consts.padding, left: Consts.padding, right: Consts.padding, ), child: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, children: <Widget>[ const Text( "Modificar tarea", style: TextStyle( fontSize: 24.0, fontWeight: FontWeight.w700, ), ), const SizedBox(height: 16.0), TextFormField( controller: nombreController, decoration: const InputDecoration( hintText: 'Cambiar el nombre de la tarea', ), ), TextFormField( controller: fechaController, decoration: const InputDecoration( hintText: 'Cambiar la fecha de la tarea', ), ), const SizedBox(height: 16.0), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ TextButton( onPressed: () { Navigator.of(context).pop(); }, child: const Text('Cancelar'), ), TextButton( onPressed: () { // 原有删除逻辑保持不变 Navigator.of(context).pop(); }, child: const Text('Borrar tarea'), ), TextButton( onPressed: () async { // 原有修改逻辑保持不变 Navigator.of(context).pop(); }, child: const Text('Modificar'), ), ], ), ], ), ), ); }
最终完整代码
class CustomDialogModificarTarea extends StatelessWidget { final Usuario usuario; final Tarjeta tarjeta; ProyectoService proyectoService = ProyectoServiceFirebase(); UsuarioService usuarioService = UsuarioServiceFirebase(); TarjetaService tarjetaService=TarjetaServiceFirebase(); TextEditingController nombreController = TextEditingController(); TextEditingController fechaController = TextEditingController(); CustomDialogModificarTarea(this.usuario,this.tarjeta); @override Widget build(BuildContext context) { return Dialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(Consts.padding), ), elevation: 0.0, backgroundColor: Colors.transparent, child: dialogContent(context), ); } dialogContent(BuildContext context) { return Stack( clipBehavior: Clip.none, children: <Widget>[ Padding( padding: EdgeInsets.only(top: Consts.avatarRadius), child: buildCard(context), ), buildCircle(), ], ); } Widget buildCard(BuildContext context) { return Container( margin: EdgeInsets.symmetric(horizontal: Consts.padding), decoration: new BoxDecoration( color: secondBgColor, shape: BoxShape.rectangle, borderRadius: BorderRadius.circular(Consts.padding), boxShadow: [ BoxShadow( color: Color.fromARGB(66, 0, 0, 0), blurRadius: 10.0, offset: const Offset(0.0, 10.0), ), ], ), padding: const EdgeInsets.only( top: Consts.avatarRadius + Consts.padding, bottom: Consts.padding, left: Consts.padding, right: Consts.padding, ), child: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, children: <Widget>[ const Text( "Modificar tarea", style: TextStyle( fontSize: 24.0, fontWeight: FontWeight.w700, ), ), const SizedBox(height: 16.0), TextFormField( controller: nombreController, decoration: const InputDecoration( hintText: 'Cambiar el nombre de la tarea', ), ), TextFormField( controller: fechaController, decoration: const InputDecoration( hintText: 'Cambiar la fecha de la tarea', ), ), const SizedBox(height: 16.0), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ TextButton( onPressed: () { Navigator.of(context).pop(); }, child: const Text('Cancelar'), ), TextButton( onPressed: () { TablaService tablaService=TablaServiceFirebase(); int pos; pos=int.tryParse(usuario.idProyectoActual)!; TarjetaService tarjetaService=TarjetaServiceFirebase(); print("Borrar Tarjetas"); for(int i=0;i<2;i++){ List<Tarjeta>? tarjetas=usuario.proyectos![pos].tablas![i].tarjetas; print(tarjetas!.length); bool borrado=false; for(int j=0; tarjetas!=null && j<tarjetas.length && !borrado;j++){ print("Segundo for"); if(tarjetas[j].id==tarjeta.id){ print("La he encontrado $i ${tarjetas[j].id}"); tarjetas.removeAt(j); usuario.proyectos![pos].tablas![i].tarjetas=tarjetas; print(usuario.proyectos![pos].tablas![i].tarjetas!.length); borrado=true; } } print("Imprimo el id: ${usuario.proyectos![pos].tablas![i].id!.split("-")[1] }"); tablaService.updateTabla(id: usuario.proyectos![pos].tablas![i].id!.split("-")[1], tipo: usuario.proyectos![pos].tablas![i].id!.split("-")[0], t: usuario.proyectos![pos].tablas![i]); } print("Salgo ta"); tarjetaService.deleteTarjeta(id: tarjeta.id); Navigator.of(context).pop(); }, child: const Text('Borrar tarea'), ), TextButton( onPressed: () async { if(nombreController.text!=""){ tarjeta.name=nombreController.text; } if(fechaController.text!=""){ tarjeta.fecha=fechaController.text; } tarjetaService.updateTarjeta(tarjeta: tarjeta); Navigator.of(context).pop(); }, child: const Text('Modificar'), ), ], ), ], ), ), ); } Widget buildCircle() { return const Positioned( right: Consts.padding, top: -Consts.avatarRadius, child: CircleAvatar( backgroundColor: Colors.blueAccent, radius: Consts.avatarRadius, ), ); } } class Consts { Consts._(); static const double padding = 16.0; static const double avatarRadius = 16.0; }
内容的提问来源于stack exchange,提问作者Tatiana Sánchez
相关产品推荐
相关产品推荐

