概述

#

`RenderEditable` 实例必须在处理命中测试前进行布局。在布局前尝试对 `RenderEditable` 对象进行命中测试会导致如下断言:

Failed assertion: line 123 pos 45: '!debugNeedsLayout': is not true.

背景

#

为了支持可选文本中的手势识别器,`RenderEditable` 需要其文本跨度的布局信息,以确定哪个文本跨度接收指针事件。(在此更改之前,`RenderEditable` 对象在评估命中测试时不会考虑其文本。)为了实现这一点,布局被设置为对 `RenderEditable` 对象执行命中测试的先决条件。

实际上,这很少是一个问题。Widget 库确保在对所有渲染对象进行任何命中测试之前完成布局。此问题只可能出现在直接与渲染对象交互的代码中,例如在自定义渲染对象的测试中。

迁移指南

#

如果你在命中测试 `RenderEditable` 时看到 `'!debugNeedsLayout': is not true` 断言错误,请在此之前布局 `RenderEditable`。

迁移前的代码

dart
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/material.dart';

void main() {
  test('attach and detach correctly handle gesture', () {
    final RenderEditable editable = RenderEditable(
      textDirection: TextDirection.ltr,
      offset: ViewportOffset.zero(),
      textSelectionDelegate: FakeEditableTextState(),
      startHandleLayerLink: LayerLink(),
      endHandleLayerLink: LayerLink(),
    );
    final PipelineOwner owner = PipelineOwner(onNeedVisualUpdate: () {});
    editable.attach(owner);
    // This throws an assertion error because
    // the RenderEditable hasn't been laid out.
    editable.handleEvent(const PointerDownEvent(),
        BoxHitTestEntry(editable, const Offset(10, 10)));
    editable.detach();
  });
}

class FakeEditableTextState extends TextSelectionDelegate {
  @override
  TextEditingValue textEditingValue;
  @override
  void hideToolbar() {}
  @override
  void bringIntoView(TextPosition position) {}
}

迁移后的代码

dart
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/material.dart';

void main() {
  test('attach and detach correctly handle gesture', () {
    final RenderEditable editable = RenderEditable(
      textDirection: TextDirection.ltr,
      offset: ViewportOffset.zero(),
      textSelectionDelegate: FakeEditableTextState(),
      startHandleLayerLink: LayerLink(),
      endHandleLayerLink: LayerLink(),
    );
    // Lay out the RenderEditable first.
    editable.layout(BoxConstraints.loose(const Size(1000.0, 1000.0)));
    final PipelineOwner owner = PipelineOwner(onNeedVisualUpdate: () {});
    editable.attach(owner);
    editable.handleEvent(const PointerDownEvent(),
        BoxHitTestEntry(editable, const Offset(10, 10)));
    editable.detach();
  });
}

class FakeEditableTextState extends TextSelectionDelegate {
  @override
  TextEditingValue textEditingValue;
  @override
  void hideToolbar() {}
  @override
  void bringIntoView(TextPosition position) {}
}

时间线

#

发布版本:1.18.0
稳定版本中:1.20

参考资料

#

API 文档

相关议题

  • 问题 43494: `SelectableText.rich` 与 `TapGestureRecognizer` 一起使用时无法正常工作

相关 PR