了解 Flutter 渲染引擎

Understanding Flutter Render Engine

关于如何更新 ListView 的文档 here 说:

In Flutter, if you were to update the list of widgets inside a setState(), you would quickly see that your data did not change visually. This is because when setState() is called, the Flutter rendering engine looks at the widget tree to see if anything has changed. When it gets to your ListView, it performs a == check, and determines that the two ListViews are the same. Nothing has changed, so no update is required.

For a simple way to update your ListView, create a new List inside of setState(), and copy the data from the old list to the new list.

在这种情况下,我不明白渲染引擎如何确定小部件树中是否有任何更改。

AFAICS,我们关心调用 setState,它将 State 对象标记为脏并要求它重建。一旦重建,就会有一个新的 ListView,不是吗?那么为什么 == 检查说它是同一个对象呢?

还有,新的List会在State对象内部,Flutter引擎会比较State对象内部的所有对象吗?我认为它只比较了 Widget 树。

所以,基本上我不明白渲染引擎如何决定它要更新什么以及要忽略什么,因为我看不到创建新的 List 如何向渲染器发送任何信息引擎,正如文档所说,渲染引擎只是寻找一个新的 ListView... 而据我所知,一个新的 List 不会创建一个新的 ListView

Flutter 不仅仅由 Widgets 组成。

当您调用 setState 时,您将 Widget 标记为脏。但这个 Widget 实际上并不是您在屏幕上呈现的内容。 小部件存在于 create/mutate RenderObjects;正是这些 RenderObjects 在屏幕上绘制了您的内容。

RenderObjects 和 Widgets 之间的 link 是使用一种新的 Widget 完成的:RenderObjectWidget (such as LeafRenderObjectWidget)

Flutter提供的大部分widget在某种程度上都是RenderObjectWidget,包括ListView。

一个典型的 RenderObjectWidget 示例是这样的:

class MyWidget extends LeafRenderObjectWidget {
  final String title;

  MyWidget(this.title);

  @override
  MyRenderObject createRenderObject(BuildContext context) {
    return new MyRenderObject()
      ..title = title;
  }

  @override
    void updateRenderObject(BuildContext context, MyRenderObject renderObject) {
      renderObject
        ..title = title;
    }
}

此示例使用小部件 create/update RenderObject。仅仅通知框架有东西要重绘是不够的。

要重新绘制 RenderObject,必须对所需的 renderObject 调用 markNeedsPaintmarkNeedsLayout

这通常由 RenderObject 本身使用自定义字段 setter 以这种方式完成:

class MyRenderObject extends RenderBox {
  String _title;
  String get title => _title;
  set title(String value) {
    if (value != _title) {
      markNeedsLayout();
      _title = value;
    }
  }
}

注意 if (value != previous).

此检查可确保在不更改任何内容的情况下重建小部件时,Flutter 不会 relayout/repaint 任何内容。

由于这个确切的条件,改变 ListMap 不会使 ListView 重新渲染。它基本上有以下内容:

List<Widget> _children;
List<Widget> get children => _children;
set children(List<Widget> value) {
  if (value != _children) {
    markNeedsLayout();
    _children = value;
  }
}

但这意味着如果您 改变 列表而不是创建新列表,RenderObject 将不会被标记为需要 relayout/repaint。因此不会有任何视觉更新。