Flutter应用APK真机灰屏,模拟器正常问题排查
Flutter真机灰屏问题排查与修复
问题现象
应用在模拟器运行正常,但安装APK到真机后显示灰屏;接入自定义JsonString文件前功能正常,当前抛出错误:
════════ Exception caught by widgets library Incorrect use of ParentDataWidget. The itss ══════════════════════════════
怀疑问题出在ListView组件。
MainScreen代码
import 'package:dromis/feature/common/dr_app_bar_bottom.dart'; import 'package:dromis/feature/common/dr_bgr_for_main.dart'; import 'package:dromis/feature/common/dr_colors.dart'; import 'package:dromis/feature/common/dr_app_bar.dart'; import 'package:dromis/feature/dashboard/dashboard_screen.dart'; import 'package:dromis/feature/main/widgets/main_active_list.dart'; import 'package:dromis/feature/main/widgets/main_available_list.dart'; import 'package:dromis/feature/main/widgets/main_completed_list.dart'; import 'package:dromis/feature/utils/navigable.dart'; import 'package:dromis/feature/info/info_screen.dart'; import 'package:flutter/material.dart'; import '../../core/injection/configurator.dart'; import '../pulse/pulse_screen.dart'; import '../ranking/ranking_screen.dart'; import '../utils/routes.dart'; import 'bloc/active_list_bloc/active_list_bloc.dart'; import 'bloc/available_list_bloc/available_list_bloc.dart'; import 'bloc/completed_list_bloc/completed_list_bloc.dart'; class MainScreen extends StatefulWidget implements Navigable{ @override String getName() => Routes.mainScreen; @override State<MainScreen> createState() => _MainScreenState(); } class _MainScreenState extends State<MainScreen> { bool activeBool = true; bool availableBool = true; bool completedBool = true; final activeListBloc = getIt<ActiveListBloc>(); final availableListBloc = getIt<AvailableListBloc>(); final completedListBloc = getIt<CompletedListBloc>(); @override Widget build(BuildContext context) { return DRBgrForMain( child: SafeArea( child: Scaffold( backgroundColor: Colors.transparent, appBar: PreferredSize( preferredSize: const Size.fromHeight(90.0), child: AppBar( backgroundColor: Colors.transparent, toolbarHeight: 90, foregroundColor: Colors.transparent, shadowColor: Colors.transparent, leadingWidth: 0, centerTitle: false, elevation: 0.2, title: DRAppbar(moneyCount: '4,50', onTapFaq: (){ Navigator.push( context, FadeInRoute( routeName: Routes.infoScreen, page: InfoScreen(), ), ); }, onTapMoneyCount: (){}, onTapProfile: (){}), ), ), bottomNavigationBar: BottomAppBar(child: DRAppBarBottom(onTapPlay: (){}, onTapDashBoard: (){ Navigator.push( context, FadeInRoute( routeName: Routes.dashboardScreen, page: DashboardScreen(), ), ); }, onTapRanking: (){ Navigator.push( context, FadeInRoute( routeName: Routes.rankingScreen, page: RankingScreen(), ), ); }, onTapPulse: (){ Navigator.push( context, FadeInRoute( routeName: Routes.pulseScreen, page: PulseScreen(), ), ); }, selectedPageNum: 1)), body: SingleChildScrollView( child: Column( children: [ Row( children: [ const Spacer(flex: 1,), Container( height: 10, width: 10, decoration: BoxDecoration( borderRadius: BorderRadius.circular(66), color: DRColors.greenColor, ), ), const SizedBox( width: 9, ), InkWell( splashColor: Colors.transparent, highlightColor: Colors.transparent, hoverColor: Colors.transparent, onTap: () { setState(() { activeBool? activeBool = false: activeBool = true; }); }, child: Row( children: [ Text('ACTIVE', style: TextStyle( color: Colors.white, fontFamily: 'Montserrat', fontWeight: FontWeight.w300, fontSize: 20, ), ), const SizedBox( width: 7, ), const SizedBox( width: 7, ), Icon(activeBool? Icons.keyboard_arrow_down: Icons.keyboard_arrow_up, color: DRColors.whiteTextColor, ), ], ), ), const Spacer(flex: 9,), ], ), const SizedBox(height: 5,), ActiveListWidget(activeListBloc: activeListBloc, showAll: activeBool,), const SizedBox(height: 10,), Row( children: [ const Spacer(flex: 1,), Container( height: 10, width: 10, decoration: BoxDecoration( borderRadius: BorderRadius.circular(66), color: Colors.yellow, ), ), const SizedBox( width: 9, ), InkWell( splashColor: Colors.transparent, highlightColor: Colors.transparent, hoverColor: Colors.transparent, onTap: () { setState(() { availableBool? availableBool = false: availableBool = true; }); }, child: Row( children: [ Text('AVAILABLE', style: TextStyle( color: Color.fromARGB(255, 255, 255, 255), fontFamily: 'Montserrat', fontWeight: FontWeight.w300, letterSpacing: 1, fontSize: 19, ), ), const SizedBox( width: 7, ), Icon(availableBool? Icons.keyboard_arrow_down: Icons.keyboard_arrow_up, color: DRColors.whiteTextColor, ), ], ), ), const Spacer(flex: 8,), ], ), const SizedBox(height: 5,), AvailableListWidget(availableListBloc: availableListBloc, showAll: availableBool), const SizedBox(height: 10,), Row( children: [ const Spacer(flex: 1,), Container( height: 10, width: 10, decoration: BoxDecoration( borderRadius: BorderRadius.circular(66), color: DRColors.inputTextColor, ), ), const SizedBox( width: 9, ), InkWell( splashColor: Colors.transparent, highlightColor: Colors.transparent, hoverColor: Colors.transparent, onTap: () { setState(() { completedBool? completedBool = false: completedBool = true; }); }, child: Row( children: [ Text('COMPLETED', style: TextStyle( color: Colors.white, fontFamily: 'Montserrat', fontWeight: FontWeight.w300, letterSpacing: 1, fontSize: 19, ), ), const SizedBox( width: 7, ), Icon(completedBool? Icons.keyboard_arrow_down: Icons.keyboard_arrow_up, color: DRColors.whiteTextColor, ), ], ), ), const Spacer(flex: 7,), ], ), const SizedBox(height: 5,), CompletedListWidget(completedListBloc: completedListBloc, showAll: completedBool) ], ), ), ), ), ); } }
列表组件代码
import 'package:dromis/feature/common/info_buttons_card/dr_info_cards_active.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import '../bloc/active_list_bloc/active_list_bloc.dart'; import '../bloc/active_list_bloc/active_list_event.dart'; import '../bloc/active_list_bloc/active_list_state.dart'; class ActiveListWidget extends StatelessWidget { final ActiveListBloc activeListBloc; bool showAll; ActiveListWidget({ super.key, required this.activeListBloc, required this.showAll, }); @override Widget build(BuildContext context) { return BlocProvider<ActiveListBloc>.value( value: activeListBloc..add(GetActiveListEvent()), child: BlocBuilder<ActiveListBloc, ActiveListState>( builder: (context, state) { if (state is ActiveListLoadedState) { return state.list.data?.isEmpty ?? true ? const SizedBox() : ListView.separated( physics: const NeverScrollableScrollPhysics(), padding: const EdgeInsets.symmetric(horizontal: 10), itemCount: showAll? 2:state.list.data!.length, shrinkWrap: true, separatorBuilder: (context, index) { return const SizedBox( height: 10, ); }, itemBuilder: (context, index) { return DRInfoCardActive( listdata: state.list.data?[index], ); }, ); } else { return const SizedBox( height: 100, ); } }, ), ); } }
修复方案
1. 解决ParentDataWidget错误
该错误核心是Widget的父容器与自身所需的父组件不兼容,比如Expanded/Flexible被放在非Row/Column的父容器中。
- 检查
DRInfoCardActive内部代码,确保所有Expanded/Flexible的父组件是Row或Column; - 若卡片内使用了
Stack,避免直接在Stack子组件中使用需要ParentData的Widget。
2. 优化滚动组件嵌套结构
SingleChildScrollView嵌套ListView(即使设置了shrinkWrap: true)容易引发真机布局计算异常,可替换为两种方案:
方案A:用Column替代ListView
直接遍历列表项,减少滚动组件嵌套:
// 替换ListView.separated为以下代码 Column( children: [ for (int index = 0; index < (showAll ? 2 : state.list.data!.length); index++) ...[ DRInfoCardActive(listdata: state.list.data?[index]), // 最后一项不显示分隔符 if (index != (showAll ? 1 : state.list.data!.length - 1)) const SizedBox(height: 10), ], ], )
方案B:改用CustomScrollView
将整个页面改为CustomScrollView,用SliverToBoxAdapter包裹静态内容,SliverList加载列表:
// MainScreen的body替换为 CustomScrollView( slivers: [ SliverToBoxAdapter(child: /* 原Row标题部分 */), SliverToBoxAdapter(child: const SizedBox(height:5)), ActiveListWidget(/* 参数 */), // 其余列表同理替换为Sliver结构 ], )
3. 排查JSON解析问题
接入自定义JsonString后出现问题,需验证真机上的解析逻辑:
- 在Bloc的事件处理中添加异常捕获,打印详细错误:
// 在GetActiveListEvent的处理逻辑中 try { // JSON解析代码 emit(ActiveListLoadedState(list: parsedData)); } catch (e, stackTrace) { print('JSON解析失败: $e'); print(stackTrace); emit(ActiveListErrorState(error: e.toString())); } - 在UI中添加错误状态展示,避免空布局导致灰屏:
// BlocBuilder中补充错误状态处理 } else if (state is ActiveListErrorState) { return Padding( padding: const EdgeInsets.all(16), child: Text('加载失败: ${state.error}', color: Colors.white), ); }
4. 修正Bloc事件触发逻辑
当前列表组件每次build都会触发GetActiveListEvent(),可能引发重复请求和状态混乱:
- 将事件移到MainScreen的
initState中触发:@override void initState() { super.initState(); activeListBloc.add(GetActiveListEvent()); availableListBloc.add(GetAvailableListEvent()); completedListBloc.add(GetCompletedListEvent()); } - 列表组件中移除
BlocProvider.value,直接使用BlocBuilder:@override Widget build(BuildContext context) { return BlocBuilder<ActiveListBloc, ActiveListState>( builder: (context, state) { // 原状态处理逻辑 }, ); }
内容的提问来源于stack exchange,提问作者Murray Karelin
相关产品推荐
相关产品推荐

