You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

点击ExpansionTile触发Flutter渲染库异常,求解决方案

Flutter ExpansionTile 点击触发渲染异常排查解决

问题描述

点击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.md

The relevant error-causing widget was:
ExpansionTile
ExpansionTile:file:///E:/Kvu/StudioProjects/shop_app/lib/widgets/category/main_category_widget.dart:28:18

When 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-UPDATE

Another exception was thrown: RenderBox was not laid out: RenderPadding#92988 relayoutBoundary=up11 NEEDS-PAINT
NEEDS-COMPOSITING-BITS-UPDATE

Another exception was thrown: RenderBox was not laid out: RenderOffstage#ccb35 relayoutBoundary=up10 NEEDS-PAINT
NEEDS-COMPOSITING-BITS-UPDATE

Another 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所在的容器是垂直方向可滚动的(无固定高度约束),这就导致了冲突——既要收缩包裹子项,又要让子项填充无限空间,两者无法同时满足。

修复步骤

  1. 移除SubCategoryWidget中的Expanded:Expanded在这里没有必要,因为ExpansionTile展开后的区域不需要填充剩余无限空间,去掉后让子组件自适应内容高度即可。
  2. 可选:给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,
相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 23:45:38