美文网首页
Flutter渲染原理

Flutter渲染原理

作者: yuLiangC | 来源:发表于2021-08-25 23:17 被阅读0次

    widget介绍

    flutter开发最常用到的对象就是widget,它不仅包含了各种UI组件,还囊括了手势操作组件(GestureDetector)和动画组件(AnimatedWidget)等各种功能的widget。因此官方说“everything is widget”,widget是配置信息,我们用它来方便快捷的实现各种UI效果,例如,我们做一个最简单的页面。

    void main() {
      runApp(Container(
        color: Colors.white,
        child: Center(
          child: Text("hello Flutter",
          textDirection: TextDirection.ltr,
          textAlign: TextAlign.center,
          style: TextStyle(color: Colors.red,fontSize: 22),),
        ),
      ));
    }
    

    这里我们只声明了一个container的根widget和一个text的字widget,页面就显示出来了,看起来非常简单,那么它是怎么绘制到屏幕上的呢?

    三棵树

    实际上,flutter的UI绘制包含了三个元素,widget,element和renderObject。这三个元素分别组成了三棵树:Widget 树,Element 树和 RenderObject 树,如下图:


    三棵树.png

    系统启动时,runApp方法会被调用,flutter会从最外层的widget去遍历创建一颗widget树;每一个widget创建后会调用createElement()创建相应的element,形成一颗element树;element创建后会通过createRenderObject()创建相应的renderObject树,如此就形成了三棵树。

    三棵树的作用

    那么,这三棵树都是用来干嘛的呢?

    • widget:配置信息,用来描述UI特征,比如尺寸多大,颜色是什么,位置在哪里
    • element:element是widget的实际实例,它同时持有了widget和renderObject的引用,用来决定是否进行UI更新。
    • renderObject:UI更新的执行者,保存了元素的大小,布局等信息。它才是真正调用渲染引擎去进行更新的对象
      那么,flutter为什么要设计成这样呢?为什么要弄成复杂的三层结构?
      答案是性能优化。如果每一点细微的操作就去完全重绘一遍UI,将带来极大的性能开销。flutter的三棵树型模式设计可以有效地带来性能提升。widget的重建开销非常小,所以可以随意的重建,因为它不一会导致页面重绘,并且它也不一定会常常变化。而renderObject如果频繁创建和销毁成本就很高了,对性能的影响比较大,因此它会缓存所有页面元素,只是当这些元素有变化时才去重绘页面。而判断页面有无变化就依靠element了,每次widget变化时element会比较前后两个widget,只有当某一个位置的Widget和新Widget不一致,才会重新创建Element和widget;其他时候则只会修改renderObject的配置而不会进行耗费性能的RenderObject的实例化工作了。
      Element的updateChild方法:
        assert(() {
          final int oldElementClass = Element._debugConcreteSubtype(child);
          final int newWidgetClass = Widget._debugConcreteSubtype(newWidget);
          hasSameSuperclass = oldElementClass == newWidgetClass;
          return true;
        }());
        if (hasSameSuperclass && child.widget == newWidget) {
          if (child.slot != newSlot)
            updateSlotForChild(child, newSlot);
          newChild = child;
        } else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) {
          if (child.slot != newSlot)
            updateSlotForChild(child, newSlot);
          child.update(newWidget);
          assert(child.widget == newWidget);
          assert(() {
            child.owner!._debugElementWasRebuilt(child);
            return true;
          }());
          newChild = child;
        } else {
          deactivateChild(child);
          assert(child._parent == null);
          newChild = inflateWidget(newWidget, newSlot);
        }
      } else {
        newChild = inflateWidget(newWidget, newSlot);
      }
    
      assert(() {
        if (child != null)
          _debugRemoveGlobalKeyReservation(child);
        final Key? key = newWidget.key;
        if (key is GlobalKey) {
          assert(owner != null);
          owner!._debugReserveGlobalKeyFor(this, newChild, key);
        }
        return true;
      }());
    
      return newChild;
    }
    

    可以看出,当widget指针相同或者类型相同时,只会进行微更新,即只更新内容,renderObject并不会重建。而当widget类型不同或key不同时,element才会将旧的widget从widget树中移除并attach上新的widget。
    element的更新策略:

      static bool canUpdate(Widget oldWidget, Widget newWidget) {
        return oldWidget.runtimeType == newWidget.runtimeType
            && oldWidget.key == newWidget.key;
      }
    

    接下来看两个例子:

    当widget不变时

    class ChangeWidget extends StatefulWidget{
      @override
      Widget build(BuildContext context) {
        // TODO: implement build
        throw UnimplementedError();
      }
    
      @override
      State<StatefulWidget> createState() {
        return ChangeWidgetState();
      }
    }
    
    class ChangeWidgetState extends State{
      bool isChangeText =false;
    
      @override
      Widget build(BuildContext context) {
        print("build");
        return Scaffold(
          appBar: AppBar(
            title: Text("计数器"),
          ),
          body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                isChangeText?buildText1():buildText2(),
              ],
            ),
          ),
          floatingActionButton: FloatingActionButton(onPressed: () {
            increaseCount();
          },tooltip: "切换textWidget",
            child: Icon(Icons.add),
          ),
        );
      }
    
      void increaseCount(){
        setState(() {
          isChangeText = ! isChangeText;
        });
      }
    
      Widget buildText1(){
        return Text("flutter1",style: Theme.of(context).textTheme.headline4);
      }
    
      Widget buildText2(){
        return Text("flutter2",style: Theme.of(context).textTheme.headline3);
      }
    
    }
    

    如上代码,一个statefulWidget,有两个方法分别返回了不同文字的TextWidget,由一个按钮控制来显示哪一个。运行后切换按钮会发现文字会来回变动。然而打开Dart DevTools观察,会发现Text下面的RichText的RenderObject的ID一直是#63684,没有变化,不管怎么切换这个UI都不会发生变化。


    image.png

    说明虽然这两个text的文字变化了,但是widget类型并没有变化,所以并不会重建renderObject。只会将变化了的元素在页面上进行了渲染。

    当widget改变时

    而如果我们将其中的一个组件更改成不同类型的,renderObject的类型还会发生变化吗?
    让buildText2返回一个不同的widget试试。

      Widget buildText2() {
        // return Text("flutter2", style: Theme.of(context).textTheme.headline3);
        return SizedBox(height: 30,width: 30);
      }
    
    image.png

    刷新一下发现这两个widget的renderObject的ID是不同的。由此我们可以验证当widget类型不同时,element和renderObject都会发生变化,旧的销毁,新的重建。
    这就是flutter高性能的秘诀之一。

    相关文章

      网友评论

          本文标题:Flutter渲染原理

          本文链接:https://www.haomeiwen.com/subject/guthiltx.html