likes
comments
collection
share

Flutter是如何渲染Widget的

作者站长头像
站长
· 阅读数 87

FlutterGoogle开发的构建高性能UI界面的框架。在Flutter中一切都是Widget

渲染组成

Widget

Widget是对用户界面的一种不可变的描述。由于Widget是不可变的,当App进行交互的时候,我们的界面要进行变动,是怎么做到的呢?

实际上,Flutter由3种树Widget TreeElement TreeRenderObject Tree。Flutter通过结合这三颗树,更加高效的渲染UI界面。

使用Widget,可以声明我们想要的UI样式,配置样式属性信息。可以说是WidgetElement的一个配置描述。Widget会被映射为一个Element对象。

Widget它们本身没有可变的状态,如果我们想让Widget有可变的状态,可以使用StatefulWidgetStatefulWidget会创建一个State对象,并将State对象插入到Element树中。

同一个Widget可能会被多次移除和添加,同样的,同一个Element对象也会被多次在Element树中移除和添加。

Element

Element是用来描述Widget在树中位置的,记录了其上下级关系。它是可变的,用来管理UI的更新和变化。我们可以认为Element是来管理Widget的生命周期的。实际上Widget是没有生命周期的,Element有生命周期的,Element的生命周期也代表了Widget对象是否还在树中。

RenderObject

RenderObject负责设置大小,布局和绘制UI的,Flutter通过参照Render tree进行UI绘制。

通过以上我们知道由Widget负责配置信息,Element负责生命周期,RenderObject负责绘制。那么Flutter渲染Widget也分为3部分:设置配置信息,控制生命周期和绘制

Flutter是如何渲染Widget的

  • 1,通过设置Widgetpropertiesmethod,来配置UI的信息。
  • 2,通过Element来控制UI对象的生命周期,并且管理Element对象的父子关系。
  • 3,通过RenderObject,每个componet都有paint方法,通过约束和大小确定位置关系,并开始绘制

Flutter是如何渲染Widget的

Source Step By Step

3颗树的创建流程

我们来看如下代码:

Widget build(BuildContext context) {
  return Center(
    child: RichText(
      text: const TextSpan(text: "Rich Text"),
    ),
  );
}

当创建RichTextWidget的时候, 1,会创建 MultiChildRenderObjectElement对象。

class RichText extends MultiChildRenderObjectWidget {
    //....
}


abstract class MultiChildRenderObjectWidget extends RenderObjectWidget {
    ....
    @override
    MultiChildRenderObjectElement createElement() => MultiChildRenderObjectElement(this);
}

2,第二步,在mount方法中,创建RenderObject对象。

abstract class RenderObjectElement extends Element {
  ......
  @override
  void mount(Element? parent, Object? newSlot) {
    super.mount(parent, newSlot);
     ......
    _renderObject = widget.createRenderObject(this);
     ......
    attachRenderObject(newSlot);
    _dirty = false;
  }

2.1,将Widget中的配置信息(属性),赋值给RenderObject对象。

MultiChildRenderObjectWidget:

@override
RenderParagraph createRenderObject(BuildContext context) {
  assert(textDirection != null || debugCheckHasDirectionality(context));
  return RenderParagraph(text,
    textAlign: textAlign,
    textDirection: textDirection ?? Directionality.of(context),
    softWrap: softWrap,
    overflow: overflow,
    textScaleFactor: textScaleFactor,
    maxLines: maxLines,
    strutStyle: strutStyle,
    textWidthBasis: textWidthBasis,
    textHeightBehavior: textHeightBehavior,
    locale: locale ?? Localizations.maybeLocaleOf(context),
  );
}

通过以上步骤,我们就得到了MultiChildRenderObjectWidget、MultiChildRenderObjectElement 和 RenderParagraph

更新子部件

有时候我们需要根据不同的状态显示不同UI界面,这时候,就会去更新子部件,我们看如下代码

Widget build(BuildContext context) {
  return Center(
    child: RichText(
      text: reverse
          ? const TextSpan(text: "Rich Text Two")
          : const TextSpan(text: "Rich Text"),
    ),
  );
}

reverse值更改的时候,在下一次rebuild()的过程中,将 TextSpan(text: "Rich Text Two")加入到Widget树,将原来的TextSpanWidget树中移除,与相同位置的Widget进行比较,通过canUpdate方法判断是否可以更新

Widget:

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

两个TextSpan具有相同的runtimeType,且他俩的key值都为null,返回Yes

然后执行 widget.updateRenderObject(this, renderObject); 更新RenderObject信息。

Widget:

@override
void updateRenderObject(BuildContext context, RenderParagraph renderObject) {
  assert(textDirection != null || debugCheckHasDirectionality(context));
  renderObject
    ..text = text
    ..textAlign = textAlign
    ..textDirection = textDirection ?? Directionality.of(context)
    ..softWrap = softWrap
    ..overflow = overflow
    ..textScaleFactor = textScaleFactor
    ..maxLines = maxLines
    ..strutStyle = strutStyle
    ..textWidthBasis = textWidthBasis
    ..textHeightBehavior = textHeightBehavior
    ..locale = locale ?? Localizations.maybeLocaleOf(context);
}

这样,RenderObject树中的信息就会被更新。通过重用RenderObject对象,就大大的提高了效率。毕竟,新建一个对象的开销非常大。

参考

How Flutter Render Widgets

转载自:https://juejin.cn/post/7094933420979175438
评论
请登录