点击ExpansionTile触发Flutter渲染库异常,求解决方案
问题描述
点击ExpansionTile组件时触发Flutter渲染库异常。项目从Firestore获取分类数据,每个主分类下仅有少量子分类。在MainCategoryWidget的ExpansionTile中添加普通文本可正常运行,但添加SubCategoryWidget组件就报错。
异常信息
══╡ EXCEPTION CAUGHT BY RENDERING LIBRARY ╞═════════════════════════════════════════════════════════
The following assertion was thrown during performLayout():
RenderFlex children have non-zero flex but incoming height constraints are unbounded.
When a column is in a parent that does not provide a finite height constraint, for example if it is
in a vertical scrollable, it will try to shrink-wrap its children along the vertical axis. Setting a
flex on a child (e.g. using Expanded) indicates that the child is to expand to fill the remaining
space in the vertical direction.
These two directives are mutually exclusive. If a parent is to shrink-wrap its child, the child
cannot simultaneously expand to fit its parent.
Consider setting mainAxisSize to MainAxisSize.min and using FlexFit.loose fits for the flexible
children (using Flexible rather than Expanded). This will allow the flexible children to size
themselves to less than the infinite remaining space they would otherwise be forced to take, and
then will cause the RenderFlex to shrink-wrap the children rather than expanding to fit the maximum
constraints provided by the parent.
If this message did not help you determine the problem, consider using debugDumpRenderTree():
https://flutter.dev/debugging/#rendering-layer
http://api.flutter.dev/flutter/rendering/debugDumpRenderTree.html
The affected RenderFlex is:
RenderFlex#f821c relayoutBoundary=up12 NEEDS-LAYOUT NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE(creator: Column ← Padding ←
_EffectiveTickerMode ← TickerMode ← Offstage ← Align ← ClipRect ← Column ← Padding ← DecoratedBox ← Container ← AnimatedBuilder ← ⋯,
parentData: offset=Offset(0.0, 0.0) (can use size), constraints: BoxConstraints(0.0<=w<=292.7, 0.0<=h<=Infinity), size: MISSING,
direction: vertical, mainAxisAlignment: start, mainAxisSize: max, crossAxisAlignment: center, verticalDirection: down)
The creator information is set to:
Column ← Padding ← _EffectiveTickerMode ← TickerMode ← Offstage ← Align ← ClipRect ← Column ←
Padding ← DecoratedBox ← Container ← AnimatedBuilder ← ⋯
See also: https://flutter.dev/layout/
If none of the above helps enough to fix this problem, please don't hesitate to file a bug:
https://github.com/flutter/flutter/issues/new?template=2_bug.mdThe relevant error-causing widget was:
ExpansionTile
ExpansionTile:file:///E:/Kvu/StudioProjects/shop_app/lib/widgets/category/main_category_widget.dart:28:18When the exception was thrown, this was the stack:
#0 RenderFlex.performLayout.(package:flutter/src/rendering/flex.dart:926:9)
#1 RenderFlex.performLayout (package:flutter/src/rendering/flex.dart:929:6)
#2 RenderObject.layout (package:flutter/src/rendering/object.dart:1915:7)
#3 RenderPadding.performLayout (package:flutter/src/rendering/shifted_box.dart:233:12)
#4 RenderObject.layout (package:flutter/src/rendering/object.dart:1915:7)
#5 RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:116:14)
#6 RenderOffstage.performLayout (package:flutter/src/rendering/proxy_box.dart:3462:13)
#7 RenderObject.layout (package:flutter/src/rendering/object.dart:1915:7)
#8 RenderPositionedBox.performLayout (package:flutter/src/rendering/shifted_box.dart:437:14)
#9 RenderObject.layout (package:flutter/src/rendering/object.dart:1915:7)
#10 RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:116:14)
#11 _RenderCustomClip.performLayout (package:flutter/src/rendering/proxy_box.dart:1376:11)
#12 RenderObject.layout (package:flutter/src/rendering/object.dart:1915:7)
#13 ChildLayoutHelper.layoutChild (package:flutter/src/rendering/layout_helper.dart:56:11)
#14 RenderFlex._computeSizes (package:flutter/src/rendering/flex.dart:829:43)
#15 RenderFlex.performLayout (package:flutter/src/rendering/flex.dart:931:32)
#16 RenderObject.layout (package:flutter/src/rendering/object.dart:1915:7)
#17 RenderPadding.performLayout (package:flutter/src/rendering/shifted_box.dart:233:12)
#18 RenderObject.layout (package:flutter/src/rendering/object.dart:1915:7)
#19 RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:116:14)
#20 RenderObject.layout (package:flutter/src/rendering/object.dart:1915:7)
#21 RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:116:14)
#22 RenderObject.layout (package:flutter/src/rendering/object.dart:1915:7)
#23 RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:116:14)
#24 RenderObject.layout (package:flutter/src/rendering/object.dart:1915:7)
#25 RenderSliverList.performLayout (package:flutter/src/rendering/sliver_list.dart:213:28)
#26 RenderObject.layout (package:flutter/src/rendering/object.dart:1915:7)
#27 RenderSliverEdgeInsetsPadding.performLayout (package:flutter/src/rendering/sliver_padding.dart:137:12)
#28 RenderSliverPadding.performLayout (package:flutter/src/rendering/sliver_padding.dart:371:11)
#29 RenderObject.layout (package:flutter/src/rendering/object.dart:1915:7)
#30 RenderViewportBase.layoutChildSequence (package:flutter/src/rendering/viewport.dart:510:13)
#31 RenderViewport._attemptLayout (package:flutter/src/rendering/viewport.dart:1580:12)
#32 RenderViewport.performLayout (package:flutter/src/rendering/viewport.dart:1489:20)
#33 RenderObject._layoutWithoutResize (package:flutter/src/rendering/object.dart:1757:7)
#34 PipelineOwner.flushLayout (package:flutter/src/rendering/object.dart:887:18)
#35 RendererBinding.drawFrame (package:flutter/src/rendering/binding.dart:504:19)
#36 WidgetsBinding.drawFrame (package:flutter/src/widgets/binding.dart:892:13)
#37 RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:370:5)
#38 SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:1146:15)
#39 SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:1083:9)
#40 SchedulerBinding._handleDrawFrame (package:flutter/src/scheduler/binding.dart:997:5)
#44 _invoke (dart:ui/hooks.dart:151:10)
#45 PlatformDispatcher._drawFrame (dart:ui/platform_dispatcher.dart:308:5)
#46 _drawFrame (dart:ui/hooks.dart:115:31)
(elided 3 frames from dart:async)The following RenderObject was being processed when the exception was fired: RenderFlex#f821c relayoutBoundary=up12 NEEDS-LAYOUT
NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE:
creator: Column ← Padding ← _EffectiveTickerMode ← TickerMode ← Offstage ← Align ← ClipRect ← Column
← Padding ← DecoratedBox ← Container ← AnimatedBuilder ← ⋯
parentData: offset=Offset(0.0, 0.0) (can use size)
constraints: BoxConstraints(0.0<=w<=292.7, 0.0<=h<=Infinity)
size: MISSING
direction: vertical
mainAxisAlignment: start
mainAxisSize: max
crossAxisAlignment: center
verticalDirection: down
This RenderObject had the following descendants (showing up to depth 5):
child 1: RenderPositionedBox#34444 NEEDS-LAYOUT NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
child: RenderSemanticsAnnotations#32acb NEEDS-LAYOUT NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
child: RenderConstrainedBox#b0b19 NEEDS-LAYOUT NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
child: RenderCustomPaint#b59a9 NEEDS-LAYOUT NEEDS-PAINT
════════════════════════════════════════════════════════════════════════════════════════════════════Another exception was thrown: RenderBox was not laid out: RenderFlex#f821c relayoutBoundary=up12 NEEDS-PAINT
NEEDS-COMPOSITING-BITS-UPDATEAnother exception was thrown: RenderBox was not laid out: RenderPadding#92988 relayoutBoundary=up11 NEEDS-PAINT
NEEDS-COMPOSITING-BITS-UPDATEAnother exception was thrown: RenderBox was not laid out: RenderOffstage#ccb35 relayoutBoundary=up10 NEEDS-PAINT
NEEDS-COMPOSITING-BITS-UPDATEAnother exception was thrown: RenderBox was not laid out: RenderOffstage#ccb35 relayoutBoundary=up10
Another exception was thrown: 'package:flutter/src/rendering/object.dart': Failed assertion: line 1840 pos 12:
'!_debugDoingThisLayout': is not true.
相关代码
main_category_widget.dart
class MainCategoryWidget extends StatefulWidget { final String? selectedCategory; const MainCategoryWidget({ Key? key, this.selectedCategory, }) : super(key: key); @override State<MainCategoryWidget> createState() => _MainCategoryWidgetState(); } class _MainCategoryWidgetState extends State<MainCategoryWidget> { @override Widget build(BuildContext context) { return Expanded( child: FirestoreListView<MainCategoryModel>( query: mainCategoryCollection(widget.selectedCategory), itemBuilder: (context, snapshot) { MainCategoryModel mainCategory = snapshot.data(); return ExpansionTile( title: Text(mainCategory.mainCategory), children: [ SubCategoryWidget( selectedSubCategory: mainCategory.category, ), ], ); }, ), ); } }
main_category_model.dart
class MainCategoryModel { MainCategoryModel({required this.category, required this.mainCategory}); MainCategoryModel.fromJson(Map<String, Object?> json) : this( category: json['category']! as String, mainCategory: json['mainCategory']! as String, ); final String category; final String mainCategory; Map<String, Object?> toJson() { return { 'category': category, 'mainCategory': mainCategory, }; } } FirebaseServices _service = FirebaseServices(); mainCategoryCollection(selectedCategory) { return _service.mainCategory .where('approved', isEqualTo: true) .where('category', isEqualTo: selectedCategory) .withConverter<MainCategoryModel>( fromFirestore: (snapshot, _) => MainCategoryModel.fromJson(snapshot.data()!), toFirestore: (movie, _) => movie.toJson(), ); }
sub_category_widget.dart
class SubCategoryWidget extends StatelessWidget { final String? selectedSubCategory; const SubCategoryWidget({ Key? key, this.selectedSubCategory, }) : super(key: key); @override Widget build(BuildContext context) { return Expanded( child: FirestoreQueryBuilder<SubCategoryModel>( query: subCategoryCollection( selectedSubCategory: selectedSubCategory, ), builder: (context, snapshot, _) { if (snapshot.isFetching) { return const Center(child: CircularProgressIndicator()); } if (snapshot.hasError) { return Text('error ${snapshot.error}'); } return GridView.builder( shrinkWrap: true, itemCount: snapshot.docs.length, gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 3, childAspectRatio: 1 / 1.1, ), itemBuilder: (context, index) { SubCategoryModel subCategory = snapshot.docs[index].data(); return Column( children: [ FittedBox( fit: BoxFit.fill, child: Image.network(subCategory.image), ), Text(subCategory.subCategoryName), ], ); }, ); }, ), ); } }
sub_category_model.dart
class SubCategoryModel { SubCategoryModel({ required this.mainCategoryName, required this.subCategoryName, required this.image, }); SubCategoryModel.fromJson(Map<String, Object?> json) : this( mainCategoryName: json['mainCategoryName']! as String, subCategoryName: json['subCategoryName']! as String, image: json['image']! as String, ); final String mainCategoryName; final String subCategoryName; final String image; Map<String, Object?> toJson() { return { 'mainCategoryName': mainCategoryName, 'subCategoryName': subCategoryName, 'image': image, }; } } FirebaseServices _service = FirebaseServices(); subCategoryCollection({selectedSubCategory}) { return _service.subCategory .where('active', isEqualTo: true) .where('subCategoryName', isEqualTo: selectedSubCategory) .withConverter<SubCategoryModel>( fromFirestore: (snapshot, _) => SubCategoryModel.fromJson(snapshot.data()!), toFirestore: (movie, _) => movie.toJson(), ); }
问题原因及解决方案
原因分析
异常核心是RenderFlex子项设置了flex属性,但父容器提供的高度约束是无限的。具体来看:
SubCategoryWidget外层使用了Expanded,它要求父容器提供有限的高度来填充剩余空间;- 而
ExpansionTile的children所在的容器是垂直方向可滚动的(无固定高度约束),这就导致了冲突——既要收缩包裹子项,又要让子项填充无限空间,两者无法同时满足。
修复步骤
- 移除SubCategoryWidget中的Expanded:
Expanded在这里没有必要,因为ExpansionTile展开后的区域不需要填充剩余无限空间,去掉后让子组件自适应内容高度即可。 - 可选:给GridView添加physics属性:如果子分类数量较多,添加
physics: const NeverScrollableScrollPhysics()避免内部滚动和外部滚动冲突(因为外层已经是可滚动列表)。
修改后的sub_category_widget.dart关键代码:
class SubCategoryWidget extends StatelessWidget { final String? selectedSubCategory; const SubCategoryWidget({ Key? key, this.selectedSubCategory, }) : super(key: key); @override Widget build(BuildContext context) { // 移除外层的Expanded return FirestoreQueryBuilder<SubCategoryModel>( query: subCategoryCollection( selectedSubCategory: selectedSubCategory,

