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

Flutter two_dimensional_scrollables鼠标滚轮快速滚动崩溃求助

问题描述

我最近在Flutter项目中切换到two_dimensional_scrollables组件,替换了原来的双ScrollView方案,该组件的懒加载特性带来了更好的性能。我在TableView.builder()外层使用了带有CustomScrollBehaviour的ScrollConfiguration,以支持鼠标拖拽滚动。为充分利用懒加载,我将TableView拆分为多个小块。

现在遇到的问题:当使用鼠标滚轮快速滚动(或滚动速度稍慢但同时显示更多元素)时,组件会崩溃并逐渐消失,即使热重启也无法恢复,必须完全重启应用。拖拽滚动(包括鼠标拖拽)时无此问题。

已尝试的操作

  • 排查发现问题在低算力设备上更明显,当TableView的小块需要计算构建时问题更严重
  • 通过缓存已构建的组件,问题有所缓解但未解决

环境信息

  • 问题出现平台:Web、Windows、Linux;Android因使用触摸滚动暂未出现该问题
  • 当前使用版本:two_dimensional_scrollables 0.2.1 + Flutter 3.19.5

错误信息

The following assertion was thrown during performLayout():
'package:flutter/src/widgets/two_dimensional_viewport.dart': Failed assertion: line 783 pos 12: '_children.containsValue(child)': is not true.

The relevant error-causing widget was: 
  TableView TableView:file:///home/<etc>/<etc>/file.dart:1131:40
When the exception was thrown, this was the stack: 
#2      RenderTwoDimensionalViewport.parentDataOf (package:flutter/src/widgets/two_dimensional_viewport.dart:783:12)
#3      RenderTableViewport.parentDataOf (package:two_dimensional_scrollables/src/table_view/table.dart:395:13)
#4      RenderTwoDimensionalViewport.performLayout.<anonymous closure>.<anonymous closure> (package:flutter/src/widgets/two_dimensional_viewport.dart:1264:17)
#5      WhereIterator.moveNext (dart:_internal/iterable.dart:451:13)
#6      Iterable.isEmpty (dart:core/iterable.dart:540:33)
#7      RenderTwoDimensionalViewport.performLayout.<anonymous closure> (package:flutter/src/widgets/two_dimensional_viewport.dart:1265:10)
#8      RenderObject.invokeLayoutCallback.<anonymous closure> (package:flutter/src/rendering/object.dart:2686:59)
#9      PipelineOwner._enableMutationsToDirtySubtrees (package:flutter/src/rendering/object.dart:1097:15)
#10     RenderObject.invokeLayoutCallback (package:flutter/src/rendering/object.dart:2686:14)
#11     RenderTwoDimensionalViewport.performLayout (package:flutter/src/widgets/two_dimensional_viewport.dart:1258:5)
#12     RenderObject._layoutWithoutResize (package:flutter/src/rendering/object.dart:2414:7)
#13     PipelineOwner.flushLayout (package:flutter/src/rendering/object.dart:1051:18)
#14     PipelineOwner.flushLayout (package:flutter/src/rendering/object.dart:1064:15)
#15     RendererBinding.drawFrame (package:flutter/src/rendering/binding.dart:582:23)
#16     WidgetsBinding.drawFrame (package:flutter/src/widgets/binding.dart:991:13)
#17     RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:448:5)
#18     SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:1386:15)
#19     SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:1311:9)
#20     SchedulerBinding._handleDrawFrame (package:flutter/src/scheduler/binding.dart:1169:5)
#21     _invoke (dart:ui/hooks.dart:312:13)
#22     PlatformDispatcher._drawFrame (dart:ui/platform_dispatcher.dart:399:5)
#23     _drawFrame (dart:ui/hooks.dart:283:31)
(elided 2 frames from class _AssertionError)
The following RenderObject was being processed when the exception was fired: RenderTableViewport#a1771 NEEDS-LAYOUT NEEDS-COMPOSITING-BITS-UPDATE
...  needs compositing
...  parentData: <none> (can use size)
...  constraints: BoxConstraints(w=2133.3, h=919.2)
...  layer: OffsetLayer#1f62a
...    engine layer: OffsetEngineLayer#b03e9
...    handles: 2
...    offset: Offset(213.3, 356.9)
...  size: Size(2133.3, 919.2)
RenderObject: RenderTableViewport#a1771 NEEDS-LAYOUT NEEDS-COMPOSITING-BITS-UPDATE
  needs compositing
  parentData: <none> (can use size)
  constraints: BoxConstraints(w=2133.3, h=919.2)
  layer: OffsetLayer#1f62a
    engine layer: OffsetEngineLayer#b03e9
    handles: 2
    offset: Offset(213.3, 356.9)
size: Size(2133.3, 919.2)
...  (row: 0, column: 0): RenderRepaintBoundary#b39ec
...    needs compositing
...    parentData: vicinity=(row: 0, column: 0); layoutOffset=Offset(0.0, 0.0); paintOffset=Offset(0.0, 0.0); visible - paintExtent=Size(610.0, 150.0); 
...    constraints: BoxConstraints(w=610.0, h=150.0)
...    layer: OffsetLayer#f7b0b
...      engine layer: OffsetEngineLayer#89d12
...      handles: 2
...      offset: Offset(0.0, 0.0)
...    size: Size(610.0, 150.0)
...    metrics: 97.0% useful (1 bad vs 32 good)
...    diagnosis: this is an outstandingly useful repaint boundary and should definitely be kept
...    child: RenderFlex#80fea
...      parentData: <none> (can use size)
...      constraints: BoxConstraints(w=610.0, h=150.0)
...      size: Size(610.0, 150.0)
...      direction: horizontal
...      mainAxisAlignment: start
...      mainAxisSize: max
...      crossAxisAlignment: center
...      textDirection: ltr
...      verticalDirection: down
...      child 1: RenderSemanticsAnnotations#94584 relayoutBoundary=up1
...        parentData: offset=Offset(0.0, 4.0); flex=null; fit=null (can use size)
...        constraints: BoxConstraints(0.0<=w<=Infinity, 0.0<=h<=150.0)
...        semantics node: SemanticsNode#148
...          Rect.fromLTRB(0.0, 4.0, 306.0, 146.0)
...          label: "Gesamt

======== Exception caught by rendering library =====================================================
The following assertion was thrown during performLayout():
'package:flutter/src/widgets/two_dimensional_viewport.dart': Failed assertion: line 783 pos 12: '_children.containsValue(child)': is not true.

The relevant error-causing widget was: 
  TableView TableView:file:///home/<etc>/<etc>/file.dart:1131:40
When the exception was thrown, this was the stack: 
#2      RenderTwoDimensionalViewport.parentDataOf (package:flutter/src/widgets/two_dimensional_viewport.dart:783:12)
#3      RenderTableViewport.parentDataOf (package:two_dimensional_scrollables/src/table_view/table.dart:395:13)
#4      RenderTwoDimensionalViewport.performLayout.<anonymous closure>.<anonymous closure> (package:flutter/src/widgets/two_dimensional_viewport.dart:1264:17)
#5      WhereIterator.moveNext (dart:_internal/iterable.dart:451:13)
#6      Iterable.isEmpty (dart:core/iterable.dart:540:33)
#7      RenderTwoDimensionalViewport.performLayout.<anonymous closure> (package:flutter/src/widgets/two_dimensional_viewport.dart:1265:10)
#8      RenderObject.invokeLayoutCallback.<anonymous closure> (package:flutter/src/rendering/object.dart:2686:59)
#9      PipelineOwner._enableMutationsToDirtySubtrees (package:flutter/src/rendering/object.dart:1097:15)
#10     RenderObject.invokeLayoutCallback (package:flutter/src/rendering/object.dart:2686:14)
#11     RenderTwoDimensionalViewport.performLayout (package:flutter/src/widgets/two_dimensional_viewport.dart:1258:5)
#12     RenderObject._layoutWithoutResize (package:flutter/src/rendering/object.dart:2414:7)
#13     PipelineOwner.flushLayout (package:flutter/src/rendering/object.dart:1051:18)
#14     PipelineOwner.flushLayout (package:flutter/src/rendering/object.dart:1064:15)
#15     RendererBinding.drawFrame (package:flutter/src/rendering/binding.dart:582:23)
#16     WidgetsBinding.drawFrame (package:flutter/src/widgets/binding.dart:991:13)
#17     RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:448:5)
#18     SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:1386:15)
#19     SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:1311:9)
#20     SchedulerBinding._handleDrawFrame (package:flutter/src/scheduler/binding.dart:1169:5)
#21     _invoke (dart:ui/hooks.dart:312:13)
#22     PlatformDispatcher._drawFrame (dart:ui/platform_dispatcher.dart:399:5)
#23     _drawFrame (dart:ui/hooks.dart:283:31)
(elided 2 frames from class _AssertionError)
The following RenderObject was being processed when the exception was fired: RenderTableViewport#a1771 NEEDS-LAYOUT NEEDS-COMPOSITING-BITS-UPDATE
...  needs compositing
...  parentData: <none> (can use size)
...  constraints: BoxConstraints(w=2133.3, h=919.2)
...  layer: OffsetLayer#1f62a
...    engine layer: OffsetEngineLayer#278ca
...    handles: 2
...    offset: Offset(213.3, 356.9)
...  size: Size(2133.3, 919.2)
RenderObject: RenderTableViewport#a1771 NEEDS-LAYOUT NEEDS-COMPOSITING-BITS-UPDATE
  needs compositing
  parentData: <none> (can use size)
  constraints: BoxConstraints(w=2133.3, h=919.2)
  layer: OffsetLayer#1f62a
    engine layer: OffsetEngineLayer#278ca
    handles: 2
    offset: Offset(213.3, 356.9)
  size: Size(2133.3, 919.2)
...  (row: 0, column: 0): RenderRepaintBoundary#b39ec
...    needs compositing
...    parentData: vicinity=(row: 0, column: 0); layoutOffset=Offset(0.0, 0.0); paintOffset=Offset(0.0, 0.0); visible - paintExtent=Size(610.0, 150.0); 
...    constraints: BoxConstraints(w=610.0, h=150.0)
...    layer: OffsetLayer#f7b0b
...      engine layer: OffsetEngineLayer#89d12
...      handles: 2
...      offset: Offset(0.0, 0.0)
...    size: Size(610.0, 150.0)
...    metrics: 97.1% useful (1 bad vs 33 good)
...    diagnosis: this is an outstandingly useful repaint boundary and should definitely be kept
...    child: RenderFlex#80fea
...      parentData: <none> (can use size)
...      constraints: BoxConstraints(w=610.0, h=150.0)
...      size: Size(610.0, 150.0)
...      direction: horizontal
...      mainAxisAlignment: start
...      mainAxisSize: max
...      crossAxisAlignment: center
...      textDirection: ltr
...      verticalDirection: down
...      child 1: RenderSemanticsAnnotations#94584 relayoutBoundary=up1
...        parentData: offset=Offset(0.0, 4.0); flex=null; fit=null (can use size)
...        constraints: BoxConstraints(0.0<=w<=Infinity, 0.0<=h<=150.0)
...        semantics node: SemanticsNode#148
...          STALE
...          owner: null
...          Rect.fromLTRB(0.0, 4.0, 306.0, 146.0)
...          label: "Gesamt
Gesamt
Gesamt
Projekte: 59
Projekte: 59
解决方案

这个断言错误源于two_dimensional_viewport布局时尝试访问已被移除的子元素,属于组件内部状态不同步问题,结合你的场景可尝试以下方案:

  1. 升级依赖版本
    当前使用的two_dimensional_scrollables 0.2.1版本较旧,后续版本大概率修复了这类布局同步问题。直接升级到最新稳定版,同时确保Flutter版本兼容(建议升级到3.22及以上)。

  2. 调整滚动行为逻辑
    自定义的CustomScrollBehaviour可能与组件滚动事件处理冲突,尤其是鼠标滚轮快速滚动触发的频繁布局更新。可尝试:

    • 简化CustomScrollBehaviour,仅保留鼠标拖拽支持,移除多余事件拦截逻辑
    • 给鼠标滚轮添加滚动阻尼,减少单次滚动触发的布局更新次数,示例代码:
      @override
      ScrollPhysics getScrollPhysics(BuildContext context) {
        return const ClampingScrollPhysics(parent: BouncingScrollPhysics())
            .applyTo(const ScrollPhysics());
      }
      
  3. 优化TableView分块策略
    过小的分块会导致滚动时频繁触发组件创建与销毁,加剧状态不同步。尝试调大分块尺寸,减少分块数量,降低布局计算压力。

  4. 固定TableView状态
    给TableView添加全局GlobalKey,确保组件在热重载或布局更新时不丢失状态,减少状态重置导致的子元素异常:

    final tableViewKey = GlobalKey();
    
    // 在build中使用
    TableView.builder(
      key: tableViewKey,
      // ...其他参数
    )
    
  5. 排查语义节点问题
    错误日志中出现STALE语义节点,可能是语义更新与布局更新不同步导致的。可尝试在TableView子组件中禁用语义注解,或手动控制语义节点更新时机。

若以上方案均无效,建议在Flutter官方仓库提交issue,附上复现代码与环境信息,协助官方定位修复。


内容的提问来源于stack exchange,提问作者yahoooza

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 13:25:54