Flutter渲染机制(一):Widget

一、概述

Widget 是我们构建页面UI的基本元素,如设置间距用的Padding、行排列用的Row、流式布局用的 Wrap。Widget内的所有属性都是 final 类型,它是一个配置文件,是 Element 的描述性信息。Widget 的作用类似于 Android Native 开发中的 xml 角色。

Widget 的4种类型:

  • StatelessWidget
  • StatefulWidget
  • ProxyWidget
  • RenderObjectWidget

它们之间的继承关系:
在这里插入图片描述

下面我们分别介绍一下这些类型的特点。

二、Widget

2.1 StatelessWidget

StatelessWidget 表示一个无状态的Widget,无状态是指没有 State 对象的 Widget,只需重写 build 方法就可以显示 UI。一般用来显示没有业务逻辑的区域,如常用的 Container、 Text、Builder 等就是 StatelessWidget。

class MyStatelessWidget extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return Text("StatelessWidget");
  }
}

我们知道每一个 Widget 都对有一个对应的 Element,StatelessWidget 对应的 Element 是 StatelessElement。StatelessWidget.createElement() 会在 Element.inflateWidget() 中被调用。如下图所示:

abstract class StatelessWidget extends Widget {
  const StatelessWidget({ Key? key }) : super(key: key);

  
  StatelessElement createElement() => StatelessElement(this); //这里将Widget作为参数传递给 StatelessElement。
	
  
  Widget build(BuildContext context); //该方法在StatelessElement.build()中被触发。
}

class StatelessElement extends ComponentElement {

  StatelessElement(StatelessWidget widget) : super(widget);
  
  
  Widget build() => widget.build(this); //当Element.build()被调用时,实际是调用Widget.build()方法来构建Widget。
}

小结:

  1. StatelessWidget 是无状态的 Widget,只需要创建对应的 StatelessElement 即可。

2.2 StatefulWidget

StatelessWidget 表示一个有状态的Widget,有状态是指 Widget 内会创建 State 对象,在 State 中可以根据运行时的不同数据,显示不同 UI 。常用的 Scaffold、Checkbox、AppBar 等就是 StatefulWidget。

class MyStatefulWidget extends StatefulWidget {
  
  State<StatefulWidget> createState() {
    return _MyWidgetState();
  }
}

class _MyWidgetState extends State<MyStatefulWidget> {
  
  Widget build(BuildContext context) {
    return Text("StatefulWidget");
  }
}
  • 前面我们提到,Widget 内的所有属性都是 final 类型(不可变),因为 Widget 是 Element 的一个配置信息。但在实际的业务场景中,肯定会存在配置数据可变的场景,因此 StatefulWidget 通过在内部抽象出一个 State 的概念用来存放可变参数(StatefulWidget 本身保持不可变),实现可变因素与不可变因素的拆分。
  • StatefulWidget 对应的 Element 是 StatefulElement。
  • Widget 最终都会调用 build() 方法,在 StatefulWidget 中,会通过 State.build() 对 StatefulWidget.build() 进行代理来实现页面渲染。
abstract class StatefulWidget extends Widget {
  const StatefulWidget({ Key? key }) : super(key: key);

  
  StatefulElement createElement() => StatefulElement(this);

  State createState(); 
}

class StatefulElement extends ComponentElement {
  /// Creates an element that uses the given widget as its configuration.
  StatefulElement(StatefulWidget widget)
      : _state = widget.createState(), //StatefulElement构造方法中创建State.
        super(widget) {
    state._element = this;
    state._widget = widget;
  }

  
  Widget build() => state.build(this); //将Widget.build()方法通过State.build()进行代理。
}

小结:

  1. StatefulWidget 是有状态的 Widget,需要创建对应的 StatefulElement 和 State。
  2. StatefulWidget 和 StatelessWidget 都是组合型的 Widget,都是 ComponentElement 的子类,并且不参与绘制等过程。所以在它们的 Widget 的代码中,没有任何的绘制相关的信息。

2.3 ProxyWidget

代理类型 Widget,并不用于直接显示 UI,这个类型的 Widget 一般作为功能实现的支持,比如我们熟知的 MediaQuery 节点。常见的 ProxyWidget 有 InheritedWidget、hook 父节点数据的 ParentDataWidget 等。

abstract class ProxyWidget extends Widget {

  const ProxyWidget({ Key? key, required this.child }) : super(key: key);

  final Widget child; //只有一个子Widget.
}

ProxyWidget 内只有一个 child Widget,这个 child 字段就是显示的内容。

2.4 RenderObjectWidget

RenderObjectWidget 是渲染类型的 Widget,负责为 Element 创建用于渲染的 RenderObject,创建的 RenderObject 中就包含了布局、绘制等流程。系统提供的组件中多数都是渲染类型的。比如:Padding、Opacity、Row 等。这种类型的 Widget 重写比较复杂,需要指定渲染过程。

下面以 Padding 组件为例来看大概的流程。

Padding -> SingleChildRenderObjectWidget -> RenderObjectWidget

class Padding extends SingleChildRenderObjectWidget {
  const Padding({ Key? key, required this.padding, Widget? child, }) : assert(padding != null),
       super(key: key, child: child);

  /// The amount of space by which to inset the child.
  final EdgeInsetsGeometry padding;

  // 创建 RenderObject 对象。
  
  RenderPadding createRenderObject(BuildContext context) {
    return RenderPadding(
      padding: padding,
      textDirection: Directionality.maybeOf(context),
    );
  }
  
  // SingleChildRenderObjectWidget类中
  // 创建Widget对应的Element.
  
  SingleChildRenderObjectElement createElement() => SingleChildRenderObjectElement(this);
}

abstract class RenderObjectWidget extends Widget {
  // 构造
  const RenderObjectWidget({ Key? key }) : super(key: key);
  // 为每个Widget创建对应的Element.
  RenderObjectElement createElement();
  // 创建RenderObject,并放入到RenderObject树种.
  RenderObject createRenderObject(BuildContext context);
  // 更新RenderObject.
  void updateRenderObject(BuildContext context, covariant RenderObject renderObject) { }
  // 将RenderObject从RenderObject树中移除时,会调用该方法。可以在这里做一些资源释放。
  void didUnmountRenderObject(covariant RenderObject renderObject) { }
}

下面我们看下与 Padding 对应的 RenderObject (即 RenderPadding)。RenderPadding 中主要看三个方法:

  • RenderObject.performLayout():测量当前控件的尺寸信息。
  • RenderObject.layout() :内部会调用 RenderObject.performLayout() 方法进行递归测量。
  • RenderObject.paint() :绘制操作。
class RenderPadding extends RenderShiftedBox {
  // 布局逻辑
  
  void performLayout() {
    final BoxConstraints constraints = this.constraints;
    // ...
    if (child == null) {
      // 1.没有child时,返回当前RenderObject的尺寸。
      return;
    }
    // 2.有child时,先递归测量child的尺寸,最后才确定当前RenderObject的尺寸。
    final BoxConstraints innerConstraints = constraints.deflate(_resolvedPadding!);
    // 2.1 递归测量子RenderObject的尺寸,传入当前可用的尺寸.
    child!.layout(innerConstraints, parentUsesSize: true);
	// ...
    // 2.2 确定当前RenderObject的尺寸.
    // ...
  }
  // RenderShiftedBox类中。
  // 绘制逻辑
  void paint(PaintingContext context, Offset offset) {
    if (child != null) {
      final BoxParentData childParentData = child!.parentData! as BoxParentData;
      context.paintChild(child!, childParentData.offset + offset);
    }
  }
}

// RenderObject类
void layout(Constraints constraints, { bool parentUsesSize = false }) {
  if (sizedByParent) {
  	performResize();
  }
  // 调用当前RenderObject的performLayout()方法。
  performLayout();
  markNeedsSemanticsUpdate();
  _needsLayout = false;
  markNeedsPaint();
}

小结:

  1. RenderObjectWidget 是实际的渲染 Widget,所以需要创建对应的 StatefulElement 和 RenderObject (实际进行渲染的对象)。
  2. RenderObject.performLayout() 方法对 RenderObject 树进行递归测量对应 RenderObject 的尺寸信息(先测量子节点,再测量父节点,这与Android中的 measure 流程相同)。
  3. RenderObject.paint() 方法会执行渲染逻辑。

三、渲染前后 Widget 树的差异

代码层面调用某个 Widget 时,渲染出来的 Widget 树不一定只有当前这一个 Widget,因为Widget 里面的属性可能也是通过其他 Widget 的组合来实现的。

下面以设置了 Color 属性的 Container 为例来说明:

class MyPage extends StatelessWidget {
  
  Widget build(BuildContext context) {
  	return Container(
      color: Colors.red,
      child: Row(
        children: [
          Image.network(
          		"http url",
                width: 500,
                height: 500,
            ),
          Text("My page.")
        ],
      ),
    );
  }
}

class Container extends StatelessWidget {

	Widget build(BuildContext context) {
		//...
		// 如果颜色不为空,则为颜色设置一个ColoredBox,ColoredBox是一个RenderObjectWidget。
	   	if (color != null) {
	   		// 将 Text类型的Widget作为ColoredBox的child节点。
	    	current = ColoredBox(color: color!, child: current);
		}
	    return current!;
	}
}

class ColoredBox extends SingleChildRenderObjectWidget {}

上述的 Container 设置颜色后,Widget 树的结构如下图红框所示,会多一个 ColoredBox。
在这里插入图片描述

MyPage 渲染前后的 Widget 树如下图所示:

渲染前渲染后
在这里插入图片描述在这里插入图片描述

四、小结

通过上述几类 Widget 的组合,我们可以实现任何 UI 的展示。

几种 Widget 创建的对象:

创建的对象StatelessWidgetStatefulWidgetProxyWidgetRenderObjectWidget
创建ElementYYYY
创建State/Y//
创建RenderObject///Y

小结:

  1. 常见的问类 Widget:StatelessWidget、StatefulWidget、ProxyWidget、RenderObjectWidget,各个 Widget 内创建的对象如上表所示。
  2. 显示 UI 时 ,Widget 的使用原则。
    • 优先使用 RenderObjectWidget 类型的 Widget。
    • 如果 RenderObjectWidget 不能满足需求,则尝试通过 StatelessWidget、StatefulWidget 组合 Widget 来实现。
  3. 如果想让 Widget 提供某种功能,通过重写 ProxyWidget 来实现。如 Provider 就是通过 InheritWidget 实现的。
  4. 代码层面的 Widget 关系与渲染后的 Widget 树(如上图所示)不一定相同,因为 Widget 里面的属性可能也是通过其他 Widget 的组合来实现的。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值