好比用戶一個輸入操做,能夠理解發出爲Vsunc信號,這時,fliutter會先作Animation相關工做,而後Build當前UI,以後視圖開始佈局和繪製。生成視圖數據,可是隻會生成Layer Tree,並不能直接使用,仍是須要Composite合成爲一個Layer進行Rasterize光柵化處理。層級合併的緣由是由於通常flutter的層級不少,直接把每一層傳給GPU傳遞,效率很低,因此會先作Composite,提升效率。 光柵化以後纔會給Flutter-Engine處理,這裏只是Framework層面的工做,因此看不到Engine,而咱們分析的也只是Framework中的一小部分。html
經過上面的講解,咱們大概已經瞭解了flutter的繪圖的基本流程,可是咱們並不清楚layout和paint作了什麼,而Widget是如何變成Layou Tree的。可是這裏內容太多,一句話說不清,因此咱們仍是先看下咱們平時寫的大量Widget在flutter繪圖時的究竟是啥用吧。在這以前,咱們要先了解幾個概念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
而以上這三個概念也對應着三種樹結構:模型樹、呈現樹、渲染樹。 在解釋他們的概念和關係之後,咱們已經認識到RenderObject的重要性,由於如下Layout、Paint包括relayout boundary和repaint boundary都是在這裏發生的。 通常一個Widget被更新,那麼持有該Widget的節點的Element會被標記爲dirtyElement,在下一次更新界面時,Element樹的這一部分子樹便會被觸發performRebuild,在Element樹更新完成後,便能得到RenderObject樹,接下來會進入Layout和Paint的流程。它的目的是提升flutter的繪圖性能,它的做用是設置測量邊界,邊界內的Widget作任何改變都不會致使邊界外從新計算並繪製。性能優化
固然它是有條件的,當知足如下三個條件的任意一個就會觸發Relayout boundary什麼是isTight呢?用BoxConstraints爲例bash
它有四個屬性,分別是minWidth,maxWidth,minHeight,maxHeighttight 若是最小約束(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'
因此爲了達到有RelayoutBoundary的效果,能夠將代碼中的Container添加寬高以達到constraints.isTight條件,這個實驗就留給讀者本身測試吧。paint的一個重要工做就是肯定哪些Element放在同一Layer
佈局size計算是自下而上的,可是paint是自上而下的。在layout以後,全部的Widget的大小、位置都已經肯定,這時不須要再作遍歷。 Paint也是按照深度優先的順序,並且老是先繪製自身,再是子節點,好比節點 2是一個背景色綠色的視圖,在繪製完自身後,繪製子節點3和4。當繪製完之後,Layer是按照深度優先的倒敘進行返回,相似Size的計算,而每一個Layer就是一層,最後的結果是一個Layer Tree。 也許你已注意到在2節點因爲一些其餘緣由致使它的部分UI5與6處於了同一層,這樣的結果會致使當2須要重繪的時候,與其不想相關的6實際上也會被重繪,而存在性能損耗。Flutter的工程師固然不會做出這麼愚蠢的設計。因此爲了提升性能,與relayout boundary相應的存在repaint boundary。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