Flutter Column嵌套TabBarView报ParentDataWidget使用错误
问题描述
在Column组件内的Flexible子组件中使用TabBarView时,出现Incorrect use of ParentDataWidget报错。
当前调试环境下页面显示正常,但TestFlight环境运行会因ParentDataWidgetError触发灰屏。
已尝试的无效操作:
- 给Column包裹固定高度的SizedBox,操作后页面所有组件消失
- 给TabBar本身包裹SizedBox,同样出现所有组件消失的问题
预期页面效果:
相关代码
页面主体_buildBody()代码
Widget _buildBody() { TabController _topTabController = TabController(length: 2, vsync: this, animationDuration: Duration.zero); var loc = Utils.getLocalizations(context)!; double deviceWidth = Utils.deviceWidth(context); _topTabController.animateTo(tabIndex); _topTabController.addListener(() { if (!_topTabController.indexIsChanging) { tabIndex = _topTabController.index; } }); //点击空白区域让搜索框失焦 return GestureDetector( onTap: () => FocusScope.of(context).unfocus(), child: Material( color: Colors.white, child: Column( mainAxisSize: MainAxisSize.min, children: [ HomePageBanner(title: loc.messagesTitle,), Padding( padding: const EdgeInsets.only(top: 20), child: DecoratedBox( decoration: BoxDecoration( border: Border(bottom: BorderSide(color: Colors.grey.shade300, width: 0.8)), ), child: TabBar( labelColor: Colors.black, labelStyle: const TextStyle(fontWeight: FontWeight.bold), unselectedLabelColor: Colors.black, unselectedLabelStyle: const TextStyle(fontWeight: FontWeight.normal), controller: _topTabController, indicator: const UnderlineTabIndicator( borderSide: BorderSide(color: Colors.black, width: 3.0), ), tabs: [ Tab( text: loc.messagesJustMetTabName,), Tab(text: loc.messagesFriendsTabName,) ], ), ), ), Flexible( child: Padding( padding: EdgeInsets.only(left: deviceWidth*0.012, right: deviceWidth*0.012, top: 5), child: TabBarView( controller: _topTabController, children: [ StatefulBuilder( builder: (BuildContext context, void Function(void Function()) setMetState) { return _justMetTab(context, setMetState); }, ), StatefulBuilder( builder: (BuildContext context, void Function(void Function()) setFriendState) { return _friendsTab(context, setFriendState); }, ), ], ), ), ), ], ), ), ); }
build()方法代码(展示_buildBody在组件树的位置)
@override Widget build(BuildContext context) { return FutureBuilder<dynamic>( future: getAllChatRequests(), // 异步请求 builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) { switch (snapshot.connectionState) { case ConnectionState.waiting: return const Center(child: CircularProgressIndicator()); default: if (snapshot.hasError) { return Center(child: Text('Error: ${snapshot.error}')); } else { return _buildBody(); } } }, ); }
错误日志
======== Exception caught by widgets library ======================================================= The following assertion was thrown while applying parent data.: Incorrect use of ParentDataWidget. The ParentDataWidget Expanded(flex: 1) wants to apply ParentData of type FlexParentData to a RenderObject, which has been set up to accept ParentData of incompatible type ParentData. Usually, this means that the Expanded widget has the wrong ancestor RenderObjectWidget. Typically, Expanded widgets are placed directly inside Flex widgets. The offending Expanded is currently placed inside a RepaintBoundary widget. The ownership chain for the RenderObject that received the incompatible parent data was: Padding ← Expanded ← StatefulBuilder ← KeyedSubtree-[<0>] ← RepaintBoundary ← IndexedSemantics ← NotificationListener<KeepAliveNotification> ← KeepAlive ← AutomaticKeepAlive ← KeyedSubtree-[Key <[<0>]>] ← ⋯ When the exception was thrown, this was the stack: #0 RenderObjectElement._updateParentData.<anonymous closure> (package:flutter/src/widgets/framework.dart:5960:11) #1 RenderObjectElement._updateParentData (package:flutter/src/widgets/framework.dart:5977:6) #2 RenderObjectElement.attachRenderObject (package:flutter/src/widgets/framework.dart:5999:7) #3 RenderObjectElement.mount (package:flutter/src/widgets/framework.dart:5673:5) #4 SingleChildRenderObjectElement.mount (package:flutter/src/widgets/framework.dart:6214:11) ... Normal element mounting (53 frames) #57 Element.inflateWidget (package:flutter/src/widgets/framework.dart:3817:16) #58 Element.updateChild (package:flutter/src/widgets/framework.dart:3551:18) #59 SliverMultiBoxAdaptorElement.updateChild (package:flutter/src/widgets/sliver.dart:1265:37) #60 SliverMultiBoxAdaptorElement.createChild.<anonymous closure> (package:flutter/src/widgets/sliver.dart:1250:20) #61 BuildOwner.buildScope (package:flutter/src/widgets/framework.dart:2597:19) #62 SliverMultiBoxAdaptorElement.createChild (package:flutter/src/widgets/sliver.dart:1242:12) #63 RenderSliverMultiBoxAdaptor._createOrObtainChild.<anonymous closure> (package:flutter/src/rendering/sliver_multi_box_adaptor.dart:349:23) #64 RenderObject.invokeLayoutCallback.<anonymous closure> (package:flutter/src/rendering/object.dart:2025:59) #65 PipelineOwner._enableMutationsToDirtySubtrees (package:flutter/src/rendering/object.dart:918:15) #66 RenderObject.invokeLayoutCallback (package:flutter/src/rendering/object.dart:2025:14) #67 RenderSliverMultiBoxAdaptor._createOrObtainChild (package:flutter/src/rendering/sliver_multi_box_adaptor.dart:338:5) #68 RenderSliverMultiBoxAdaptor.addInitialChild (package:flutter/src/rendering/sliver_multi_box_adaptor.dart:422:5) #69 RenderSliverFixedExtentBoxAdaptor.performLayout (package:flutter/src/rendering/sliver_fixed_extent_list.dart:205:12) #70 RenderObject.layout (package:flutter/src/rendering/object.dart:1915:7) #71 RenderSliverEdgeInsetsPadding.performLayout (package:flutter/src/rendering/sliver_padding.dart:137:12) #72 _RenderSliverFractionalPadding.performLayout (package:flutter/src/widgets/sliver_fill.dart:167:11) #73 RenderObject.layout (package:flutter/src/rendering/object.dart:1915:7) #74 RenderViewportBase.layoutChildSequence (package:flutter/src/rendering/viewport.dart:510:13) #75 RenderViewport._attemptLayout (package:flutter/src/rendering/viewport.dart:1580:12) #76 RenderViewport.performLayout (package:flutter/src/rendering/viewport.dart:1489:20) #77 RenderObject.layout (package:flutter/src/rendering/object.dart:1915:7) #78 RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:116:14) #79 RenderObject.layout (package:flutter/src/rendering/object.dart:1915:7) #80 RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:116:14) #81 RenderObject.layout (package:flutter/src/rendering/object.dart:1915:7) #82 RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:116:14) #83 RenderObject.layout (package:flutter/src/rendering/object.dart:1915:7) #84 RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:116:14) #85 RenderObject.layout (package:flutter/src/rendering/object.dart:1915:7) #86 RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:116:14) #87 RenderObject.layout (package:flutter/src/rendering/object.dart:1915:7) #88 RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:116:14) #89 RenderObject.layout (package:flutter/src/rendering/object.dart:1915:7) #90 RenderPadding.performLayout (package:flutter/src/rendering/shifted_box.dart:233:12) #91 RenderObject.layout (package:flutter/src/rendering/object.dart:1915:7) #92 ChildLayoutHelper.layoutChild (package:flutter/src/rendering/layout_helper.dart:56:11) #93 RenderFlex._computeSizes (package:flutter/src/rendering/flex.dart:896:45) #94 RenderFlex.performLayout (package:flutter/src/rendering/flex.dart:931:32) #95 RenderObject.layout (package:flutter/src/rendering/object.dart:1915:7) #96 RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:116:14) #97 RenderObject.layout (package:flutter/src/rendering/object.dart:1915:7) #98 RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:116:14) #99 _RenderCustomClip.performLayout (package:flutter/src/rendering/proxy_box.dart:1376:11) #100 RenderObject.layout (package:flutter/src/rendering/object.dart:1915:7) #101 RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:116:14) #102 RenderObject.layout (package:flutter/src/rendering/object.dart:1915:7) #103 RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:116:14) #104 RenderObject.layout (package:flutter/src/rendering/object.dart:1915:7) #105 RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:116:14) #106 RenderObject._layoutWithoutResize (package:flutter/src/rendering/object.dart:1757:7) #107 PipelineOwner.flushLayout (package:flutter/src/rendering/object.dart:887:18) #108 RendererBinding.drawFrame (package:flutter/src/rendering/binding.dart:504:19) #109 WidgetsBinding.drawFrame (package:flutter/src/widgets/binding.dart:892:13) #110 RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:370:5) #111 SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:1146:15) #112 SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:1083:9) #113 SchedulerBinding._handleDrawFrame (package:flutter/src/scheduler/binding.dart:997:5) #117 _invoke (dart:ui/hooks.dart:151:10) #118 PlatformDispatcher._drawFrame (dart:ui/platform_dispatcher.dart:308:5) #119 _drawFrame (dart:ui/hooks.dart:115:31) (elided 3 frames from dart:async) ====================================================================================================
错误原因
从错误日志可以直接定位问题:_justMetTab或_friendsTab返回的组件树里,有个被Expanded包裹的Padding组件,这个Expanded的直接父组件不是Column/Row这类Flex布局组件,而是TabBarView内部的RepaintBoundary组件。Expanded、Flexible这类ParentDataWidget只能直接作为Column、Row、Flex组件的子组件,中间隔了其他组件或者父级不是Flex组件就会报这个错。debug模式下断言有时不会第一时间触发,但release环境(TestFlight包属于这类)会直接抛错灰屏。
之前给Column、TabBar套固定高度SizedBox没用,是因为问题根本不在外层布局,是Tab页内部的组件用错了Expanded。
解决方案
- 检查
_justMetTab和_friendsTab两个方法返回的组件树,找到所有使用Expanded/Flexible的位置 - 确认这些
Expanded/Flexible的直接父组件是否是Column/Row/Flex:- 如果父组件是ListView、CustomScrollView这类滚动组件,直接去掉外层的
Expanded,滚动组件本身会自动处理子组件布局 - 如果确实需要在Column/Row内使用Expanded分配剩余空间,确保Expanded是该Flex组件的直接子级,中间不要穿插其他非Flex组件
- 如果父组件是ListView、CustomScrollView这类滚动组件,直接去掉外层的
- 修复现有代码的其他隐患:
TabController不要在_buildBody方法内部初始化,每次build都会重新创建控制器,会导致状态丢失、性能问题,应该把控制器初始化放到initState中,在dispose中释放- 不要每次build都调用
animateTo,初始跳转逻辑放到首帧渲染完成后执行 - Column设置了
mainAxisSize: MainAxisSize.min又搭配Flexible使用,容易出现高度计算异常,建议改回默认的MainAxisSize.max
参考修改后的控制器代码:
// 在State类中声明 late TabController _topTabController; @override void initState() { super.initState(); _topTabController = TabController(length: 2, vsync: this, animationDuration: Duration.zero); _topTabController.addListener(() { if (!_topTabController.indexIsChanging) { tabIndex = _topTabController.index; } }); // 初始跳转逻辑放到首帧后执行 WidgetsBinding.instance.addPostFrameCallback((_) { _topTabController.animateTo(tabIndex); }); } @override void dispose() { _topTabController.dispose(); super.dispose(); }
内容的提问来源于stack exchange,提问作者youcantgetridofmestackoverflow
相关产品推荐
相关产品推荐

