好比用户一个输入操做,能够理解发出为Vsunc信号,这时,fliutter会先作Animation相关工做,而后Build当前UI,以后视图开始布局和绘制。生成视图数据,可是只会生成Layer Tree,并不能直接使用,仍是须要Composite合成为一个Layer进行Rasterize光栅化处理。层级合并的缘由是由于通常flutter的层级不少,直接把每一层传给GPU传递,效率很低,因此会先作Composite,提升效率。 光栅化以后才会给Flutter-Engine处理,这里只是Framework层面的工做,因此看不到Engine,而咱们分析的也只是Framework中的一小部分。html
在这以前,咱们要先了解几个概念git
这里的Widget就是咱们平时写的Widget,它是 Flutter中控件实现的基本单位。 一个Widget里面通常存储了视图的配置信息,包括布局、属性等等。因此它只是一份直接使用的数据结构。在构建为结构树,甚至从新建立和销毁结构树时都不存在明显的性能问题。github
Element是Widget的抽象,它承载了视图构建的上下文数据。flutter系统经过遍历 Element树来构建 RenderObject数据,因此Element是真正被使用的集合,Widget只是数据结构。好比视图更新时,只会标记dirty Element,而不会标记dirty Widget。canvas
咱们要分析的Layout、Paint均发生在RenderObject中,而且LayerTree也是由RenderObject生成,可见其重要程度。因此 Flutter中大部分的绘图性能优化发生在这里。RenderObject树构建的数据会被加入到 Engine所需的 LayerTree中。segmentfault
它的目的是提升flutter的绘图性能,它的做用是设置测量边界,边界内的Widget作任何改变都不会致使边界外从新计算并绘制。性能优化
什么是isTight呢?用BoxConstraints为例bash
tight 若是最小约束(minWidth,minHeight)和最大约束(maxWidth,maxHeight)分别都是同样的数据结构
loose 若是最小约束都是0.0(无论最大约束),若是最小约束和最大约束都是0.0,就同时是tightly和looseless
bounded 若是最大约束都不是infiniteide
unbounded 若是最大约束都是infinite
expanding 若是最小约束和最大约束都是infinite
因此isTight就是强约束,Widget的size已经被肯定,里面的子Widget作任何变化,size都不会变。那么从该Widget开始里面的任意子Wisget作任意变化,都不会对外有影响,就会被添加Relayout boundary(说添加不科学,由于实际上这种状况,它会把size指向本身,这样就不会再向上递归而引发父Widget的Layout了)
实际上parentUsesSize与sizedByParent看起来很像,但含义有很大区别 parentUsesSize表示父Widget是否要依赖子Widget的size,若是是false,子Widget要从新布局的时候并不须要通知parent,布局的边界就是自身了。
sizedByParent表示当前的Widget虽然不是isTight,可是经过其余约束属性,也能够明确的知道size,好比Expanded,并不必定须要明确的size。
经过查看RenderObject-1579行,固然能够看到Layout的实现
void layout(Constraints constraints, { bool parentUsesSize = false }) {
...省略1w+...
if (!parentUsesSize || sizedByParent || constraints.isTight || parent is! RenderObject) {
relayoutBoundary = this;
} else {
final RenderObject parent = this.parent;
relayoutBoundary = parent._relayoutBoundary;
}
...省略1w+...
}
复制代码
经过Layout能够看到,flutter为了提升效率所作的努力,那做为开发者能够直接使用relayout boundary吗?通常状况是不能够的,可是若是当你决定要自定义一个Row的时候,确定是要使用它的。可是你能够间接的利用上面的三个条件来使你的Widget树某些地方拥有relayout boundary。好比如下用法
Row(children: <Widget>[
Expanded(child: Container(
height: 50.0, // add for test relayoutBoundary
child: LayoutBoundary(),
)),
Expanded(child: Text('You have pushed the button this many times:'))
]
复制代码
若是你想测试上面的三个条件成立时是否真的不会再layout,你能够自定义LayoutBoundaryDelegate来测试,好比
class LayoutBoundaryDelegate extends MultiChildLayoutDelegate {
LayoutBoundaryDelegate();
static const String title = 'title';
static const String summary = 'summary';
static const String paintBoundary = 'paintBoundary';
@override
void performLayout(Size size) {
print('TestLayoutDelegate performLayout ');
final BoxConstraints constraints = BoxConstraints(maxWidth: size.width);
final Size titleSize = layoutChild(title, constraints);
positionChild(title, Offset(0.0, 0.0));
final double summaryY = titleSize.height;
final Size descriptionSize = layoutChild(summary, constraints);
positionChild(summary, Offset(0.0, summaryY));
final double paintBoundaryY = summaryY + descriptionSize.height;
final Size paintBoundarySize = layoutChild(paintBoundary, constraints);
positionChild(
paintBoundary, Offset(paintBoundarySize.width / 2, paintBoundaryY));
}
@override
bool shouldRelayout(LayoutBoundaryDelegate oldDelegate) => false;
}
复制代码
自定义的MultiChildLayoutDelegate须要使用CustomMultiChildLayout来配合使用
Container(
child: CustomMultiChildLayout(
delegate: LayoutBoundaryDelegate(),
children: <Widget>[
LayoutId(
id: LayoutBoundaryDelegate.title,
child: Row(children: <Widget>[
Expanded(child: LayoutBoundary()),
Expanded(child: Text( 'You have pushed the button this many times:'))
])),
LayoutId(
id: LayoutBoundaryDelegate.summary,
child: Container(
child: InkWell(
child: Text(
_buttonText,
style: Theme.of(context).textTheme.display1),
onTap: () {
setState(() {
_index++;
_buttonText = 'onTap$_index';
});
},
))),
LayoutId(
id: LayoutBoundaryDelegate.paintBoundary,
child: Container(
width: 50.0,
height: 50.0,
child: PaintBoundary())),
]),
)
复制代码
咱们在performLayout方法里作了打印操做,若是CustomMultiChildLayout的children里的任意一个child的size变化,就会打印这条信息,因此这样的代码在每次点击onTap的时候,都会打印'TestLayoutDelegate performLayout'
paint的一个重要工做就是肯定哪些Element放在同一Layer
class PaintBoundary extends StatelessWidget {
@override
Widget build(BuildContext context) {
return CustomPaint(painter: CirclePainter(color: Colors.orange));
}
}
class CirclePainter extends CustomPainter {
final Color color;
const CirclePainter({this.color});
@override
void paint(Canvas canvas, Size size) {
print('CirclePainter paint');
var radius = size.width / 2;
var paint = Paint()
..color = color
..style = PaintingStyle.fill;
canvas.drawCircle(Offset(radius, size.height), radius, paint);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) => false;
}
复制代码
只是很简单的绘制一个橙色的圆,在RelayoutBoundary验证代码中已贴出使用。咱们只需看设置RepaintBoundary和不设置时候的区别。实验验证结果RelayoutBoundary确实能够避免CirclePainter发生重绘,即'CirclePainter paint'只会打印一次。 读者能够本身尝试验证。
relayout boundary和repaint boundary都是Flutter为了提升绘图性能而作的努力。 一般开发者可使用RepaintBoundary组件来提升应用的性能,也能够根据relayout boundary的几个规则来使relayout boundary生效,从而提升性能。
[测试代码传送门](http://link.zhihu.com/?target=https%3A//github.com/Dpuntu/RePaintBoundary-RelayoutBoundary)
本文版权属于再惠研发团队,欢迎转载,转载请保留出处。@Dpuntu