Flutter开发 -- [13 - 渲染流程]

2020-11-23  本文已影响0人  happy神悦

一. Flutter的渲染流程

1.1. Widget-Element-RenderObject关系

1.2. Widget是什么?

官方对Widget的说明:

自己的理解:

1.3. Element是什么?

官方对Element的描述:

1.4. RenderObject

官方对RenderObject的描述:

二. 对象的创建过程

我们这里以Padding为例,Padding用来设置内边距

2.1. Widget

Padding是一个Widget,并且继承自SingleChildRenderObjectWidget

继承关系如下:

Padding -> SingleChildRenderObjectWidget -> RenderObjectWidget -> Widget

我们之前在创建Widget时,经常使用StatelessWidget和StatefulWidget,这种Widget只是将其他的Widget在build方法中组装起来,并不是一个真正可以渲染的Widget(在之前的课程中其实有提到)。

在Padding的类中,我们找不到任何和渲染相关的代码,这是因为Padding仅仅作为一个配置信息,这个配置信息会随着我们设置的属性不同,频繁的销毁和创建。

问题:频繁的销毁和创建会不会影响Flutter的性能呢?

那么真正的渲染相关的代码在哪里执行呢?

2.2. RenderObject

我们来看Padding里面的代码,有一个非常重要的方法:

@override
RenderPadding createRenderObject(BuildContext context) {
  return RenderPadding(
    padding: padding,
    textDirection: Directionality.of(context),
  );
}

上面的代码创建了什么呢?RenderPadding

RenderPadding的继承关系是什么呢?

RenderPadding -> RenderShiftedBox -> RenderBox -> RenderObject

我们来具体查看一下RenderPadding的源代码:

  set padding(EdgeInsetsGeometry value) {
    assert(value != null);
    assert(value.isNonNegative);
    if (_padding == value)
      return;
    _padding = value;
    _markNeedResolution();
  }

2.3. Element

我们来思考一个问题:

Element什么时候创建?

在每一次创建Widget的时候,会创建一个对应的Element,然后将该元素插入树中。

在SingleChildRenderObjectWidget中,我们可以找到如下代码:

@override
SingleChildRenderObjectElement createElement() => SingleChildRenderObjectElement(this);

在创建完一个Element之后,Framework会调用mount方法来将Element插入到树中具体的位置:

在调用mount方法时,会同时使用Widget来创建RenderObject,并且保持对RenderObject的引用:

  @override
  void mount(Element parent, dynamic newSlot) {
    super.mount(parent, newSlot);
    _renderObject = widget.createRenderObject(this);
    assert(() {
      _debugUpdateRenderObjectOwner();
      return true;
    }());
    assert(_slot == newSlot);
    attachRenderObject(newSlot);
    _dirty = false;
  }

但是,如果你去看类似于Text这种组合类的Widget,它也会执行mount方法,但是mount方法中并没有调用createRenderObject这样的方法。

  @override
  void mount(Element parent, dynamic newSlot) {
    super.mount(parent, newSlot);
    assert(_child == null);
    assert(_active);
    _firstBuild();
    assert(_child != null);
  }

  void _firstBuild() {
    rebuild();
  }

如果是一个StatefulWidget,则创建出来的是一个StatefulElement

我们来看一下StatefulElement的构造器:

  StatefulElement(StatefulWidget widget)
      : _state = widget.createState(),
  ....省略代码
  _state._widget = widget;

而调用build的时候,本质上调用的是_state中的build方法:

Widget build() => state.build(this);

2.4. build的context是什么

在StatelessElement中,我们发现是将this传入,所以本质上BuildContext就是当前的Element

Widget build() => widget.build(this);

我们来看一下继承关系图:

abstract class Element extends DiagnosticableTree implements BuildContext

在StatefulElement中,build方法也是类似,调用state的build方式时,传入的是this

Widget build() => state.build(this);

2.5. 创建过程小结

Widget只是描述了配置信息:

Element是真正保存树结构的对象:

RenderObject是真正渲染的对象:

三. Widget的key

在我们创建Widget的时候,总是会看到一个key的参数,它又是做什么的呢?

3.1. key的案例需求

我们一起来做一个key的案例需求

<figcaption style="margin-top: 5px; text-align: center; color: rgb(136, 136, 136); font-size: 14px;">key的案例需求</figcaption>

home界面的基本代码:

class _HYHomePageState extends State<HYHomePage> {
  List<String> names = ["aaa", "bbb", "ccc"];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Test Key"),
      ),
      body: ListView(
        children: names.map((name) {
          return ListItemLess(name);
        }).toList(),
      ),

      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.delete),
        onPressed: () {
          setState(() {
            names.removeAt(0);
          });
        }
      ),
    );
  }
}

注意:待会儿我们会修改返回的ListItem为ListItemLess或者ListItemFul

3.2. StatelessWidget的实现

我们先对ListItem使用一个StatelessWidget进行实现:

class ListItemLess extends StatelessWidget {
  final String name;
  final Color randomColor = Color.fromARGB(255, Random().nextInt(256), Random().nextInt(256), Random().nextInt(256));

  ListItemLess(this.name);

  @override
  Widget build(BuildContext context) {
    return Container(
      height: 60,
      child: Text(name),
      color: randomColor,
    );
  }
}

它的实现效果是每删除一个,所有的颜色都会发现一次变化

3.3. StatefulWidget的实现(没有key)

我们对ListItem使用StatefulWidget来实现

class ListItemFul extends StatefulWidget {
  final String name;
  ListItemFul(this.name): super();
  @override
  _ListItemFulState createState() => _ListItemFulState();
}

class _ListItemFulState extends State<ListItemFul> {
  final Color randomColor = Color.fromARGB(255, Random().nextInt(256), Random().nextInt(256), Random().nextInt(256));

  @override
  Widget build(BuildContext context) {
    return Container(
      height: 60,
      child: Text(widget.name),
      color: randomColor,
    );
  }
}

我们发现一个很奇怪的现象,颜色不变化,但是数据向上移动了

3.4. StatefulWidget的实现(随机key)

我们使用一个随机的key

ListItemFul的修改如下:

class ListItemFul extends StatefulWidget {
  final String name;
  ListItemFul(this.name, {Key key}): super(key: key);
  @override
  _ListItemFulState createState() => _ListItemFulState();
}

home界面代码修改如下:

body: ListView(
  children: names.map((name) {
    return ListItemFul(name, key: ValueKey(Random().nextInt(10000)),);
  }).toList(),
),

这一次我们发现,每次删除都会出现随机颜色的现象:

// Widget类中的代码
static bool canUpdate(Widget oldWidget, Widget newWidget) {
  return oldWidget.runtimeType == newWidget.runtimeType
    && oldWidget.key == newWidget.key;
}
image-20200320152321905

<figcaption style="margin-top: 5px; text-align: center; color: rgb(136, 136, 136); font-size: 14px;">image-20200320152321905</figcaption>

3.5. StatefulWidget的实现(name为key)

这次,我们将name作为key来看一下结果:

body: ListView(
  children: names.map((name) {
    return ListItemFul(name, key: ValueKey(name));
  }).toList(),
),
复制代码

我们理想中的效果:

3.6. Key的分类

Key本身是一个抽象,不过它也有一个工厂构造器,创建出来一个ValueKey

直接子类主要有:LocalKey和GlobalKey

3.6.1. LocalKey

LocalKey有三个子类

ValueKey:

ObjectKey:

UniqueKey

3.6.2. GlobalKey

GlobalKey可以帮助我们访问某个Widget的信息,包括Widget或State或Element等对象

我们来看下面的例子:我希望可以在HYHomePage中直接访问HYHomeContent中的内容

class HYHomePage extends StatelessWidget {
  final GlobalKey<_HYHomeContentState> homeKey = GlobalKey();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("列表测试"),
      ),
      body: HYHomeContent(key: homeKey),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.data_usage),
        onPressed: () {
          print("${homeKey.currentState.value}");
          print("${homeKey.currentState.widget.name}");
          print("${homeKey.currentContext}");
        },
      ),
    );
  }
}

class HYHomeContent extends StatefulWidget {
  final String name = "123";

  HYHomeContent({Key key}): super(key: key);

  @override
  _HYHomeContentState createState() => _HYHomeContentState();
}

class _HYHomeContentState extends State<HYHomeContent> {
  final String value = "abc";

  @override
  Widget build(BuildContext context) {
    return Container();
  }
}
上一篇下一篇

猜你喜欢

热点阅读