如何正确使用flutter_screenutil第二种方案?点击TextField报错求助
问题描述
项目中使用flutter_screenutil的第二种配置方式后,点击TextField出现断言错误,需明确该包的正确使用方法。
报错信息
No Overlay widget exists above EditableText-[LabeledGlobalKey#d3506](controller: TextEditingController#dc3bb(TextEditingValue(text: ┤ghf├, selection: TextSelection.collapsed(offset: 3, affinity: TextAffinity.downstream, isDirectional: false), composing: TextRange(start: 0, end: 3))), focusNode: FocusNode#482e2([PRIMARY FOCUS]), autocorrect: false, debugLabel: ((englishLike titleMedium 2014).merge(blackMountainView titleMedium)).merge(unknown), inherit: false, color: Color(0xff878787), family: EncodeSans_regular, familyFallback: [EncodeSans], size: 16.0, weight: 400, baseline: alphabetic, decoration: TextDecoration.none, textAlign: start, keyboardType: TextInputType(name: TextInputType.text, signed: null, decimal: null), autofillHints: [], dirty, dependencies: [Directionality, MediaQuery, ScrollConfiguration], state: EditableTextState#1d73f(tickers: tracking 1 ticker)). Usually the Navigator created by WidgetsApp provides the overlay. Perhaps your app content was created above the Navigator with the WidgetsApp builder parameter. 'package:flutter/src/widgets/text_selection.dart': Failed assertion: line 610 pos 7: 'overlay != null'
现有代码
main.dart
void main() async { await ScreenUtil.ensureScreenSize(); runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, builder: (ctx, child) { ScreenUtil.init(ctx); return Theme( data: ThemeData( primarySwatch: Colors.blue, ), child: const HomeScreen()); }, ); } }
HomeScreen.dart
class HomeScreen extends StatefulWidget { const HomeScreen({Key? key}) : super(key: key); @override State<HomeScreen> createState() => _HomeScreenState(); } class _HomeScreenState extends State<HomeScreen> { @override Widget build(BuildContext context) { ScreenUtil.init(context, designSize: const Size(360, 739)); TextEditingController searchController = TextEditingController(); return SafeArea( child: Scaffold( body: Padding( padding: EdgeInsets.symmetric( horizontal: kPaddingHorizontal, ), child: Column( children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.center, children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, children: [ Text( "Hello, Welcome", style: kEncodeSansRegular.copyWith(color: kDarkBrown), ), SizedBox(height: 5.h), Text( "Hello, Welcome", style: kEncodeSansBold.copyWith(color: kDarkBrown), ), ], ), CircleAvatar( radius: 23.r, backgroundImage: const AssetImage("assets/images/image5.png"), ), ], ), SizedBox(height: 20.h), Row(children: [ Expanded( child: TextField( style: kEncodeSansRegular.copyWith(color: kDarkGrey), controller: searchController, keyboardType: TextInputType.text, autocorrect: false, decoration: InputDecoration( isDense: true, contentPadding: EdgeInsets.symmetric(horizontal: 10.w), prefixIcon: const Icon( Icons.search, color: kGrey, ), hintText: "Search Clothes...", border: kInputBorder, errorBorder: kInputBorder, disabledBorder: kInputBorder, focusedBorder: kInputBorder, focusedErrorBorder: kInputBorder, enabledBorder: kInputBorder, hintStyle: kEncodeSansRegular.copyWith(color: kDarkGrey), ), ), ],
问题根源及解决方案
问题本质
报错核心是Overlay缺失:MaterialApp的builder参数中直接返回了HomeScreen,跳过了Navigator及其提供的Overlay。TextField依赖Overlay实现光标、文本选择菜单等功能,无Overlay则触发断言错误。此外,在HomeScreen的build方法中重复调用ScreenUtil.init属于冗余操作,会导致重复初始化。
正确配置方案
修复MaterialApp的builder逻辑:
保留builder参数中的child(该child包含Navigator和Overlay),不要直接替换为HomeScreen,将ScreenUtil初始化逻辑包裹在child外层。统一初始化位置:
仅在MaterialApp的builder中初始化一次ScreenUtil,无需在页面build方法中重复调用。
修改后代码
main.dart
void main() async { await ScreenUtil.ensureScreenSize(); runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, home: const HomeScreen(), // 将HomeScreen交由Navigator管理 builder: (ctx, child) { // 使用ScreenUtilInit组件完成初始化,保留原child层级 return ScreenUtilInit( designSize: const Size(360, 739), // 统一设置设计稿尺寸 builder: (context, widget) => Theme( data: ThemeData( primarySwatch: Colors.blue, ), child: child!, // 返回原child,确保Navigator和Overlay存在 ), ); }, ); } }
HomeScreen.dart
class HomeScreen extends StatefulWidget { const HomeScreen({Key? key}) : super(key: key); @override State<HomeScreen> createState() => _HomeScreenState(); } class _HomeScreenState extends State<HomeScreen> { // 将Controller定义为成员变量,避免每次build重建 final TextEditingController searchController = TextEditingController(); @override Widget build(BuildContext context) { // 移除此处的ScreenUtil.init调用 return SafeArea( child: Scaffold( body: Padding( padding: EdgeInsets.symmetric( horizontal: kPaddingHorizontal, ), child: Column( children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.center, children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, children: [ Text( "Hello, Welcome", style: kEncodeSansRegular.copyWith(color: kDarkBrown), ), SizedBox(height: 5.h), Text( "Hello, Welcome", style: kEncodeSansBold.copyWith(color: kDarkBrown), ), ], ), CircleAvatar( radius: 23.r, backgroundImage: const AssetImage("assets/images/image5.png"), ), ], ), SizedBox(height: 20.h), Row(children: [ Expanded( child: TextField( style: kEncodeSansRegular.copyWith(color: kDarkGrey), controller: searchController, keyboardType: TextInputType.text, autocorrect: false, decoration: InputDecoration( isDense: true, contentPadding: EdgeInsets.symmetric(horizontal: 10.w), prefixIcon: const Icon( Icons.search, color: kGrey, ), hintText: "Search Clothes...", border: kInputBorder, errorBorder: kInputBorder, disabledBorder: kInputBorder, focusedBorder: kInputBorder, focusedErrorBorder: kInputBorder, enabledBorder: kInputBorder, hintStyle: kEncodeSansRegular.copyWith(color: kDarkGrey), ), ), ), ]), ], ), ), ), ); } @override void dispose() { // 释放Controller资源 searchController.dispose(); super.dispose(); } }
额外注意事项
- 不要在
build方法中创建TextEditingController,应在成员变量或initState中定义,并在dispose方法中释放资源,避免不必要的重建。 - 优先使用
ScreenUtilInit组件(flutter_screenutil官方推荐方式)完成初始化,替代直接调用ScreenUtil.init,确保初始化流程安全。 - 所有依赖Overlay的组件(如TextField、DropdownButton等)必须处于
Navigator层级之下,即MaterialApp的home或routes配置的页面中。
内容的提问来源于stack exchange,提问作者Mansoor Malik

