本文主要介紹Flutter佈局中的SizedOverflowBox、Transform、CustomSingleChildLayout三種控件,詳細介紹了其佈局行爲以及使用場景,並對源碼進行了分析。html
A widget that is a specific size but passes its original constraints through to its child, which will probably overflow.git
光看名稱,就能夠猜出,SizedOverflowBox是SizedBox與OverflowBox的結合體。github
SizedOverflowBox主要的佈局行爲有兩點:性能優化
Object > Diagnosticable > DiagnosticableTree > Widget > RenderObjectWidget > SingleChildRenderObjectWidget > SizedOverflowBox
Container( color: Colors.green, alignment: Alignment.topRight, width: 200.0, height: 200.0, padding: EdgeInsets.all(5.0), child: SizedOverflowBox( size: Size(100.0, 200.0), child: Container(color: Colors.red, width: 200.0, height: 100.0,), ), );
代碼運行的時候報出了下面的異常,很神奇。可是同窗們能夠本身看看代碼運行的效果,能夠超出,可是不太好用。app
══╡ EXCEPTION CAUGHT BY RENDERING LIBRARY ╞═════════════════════════════════════════════════════════
const SizedOverflowBox({ Key key, @required this.size, this.alignment = Alignment.center, Widget child, })
size:固定的尺寸。ide
alignment:對齊方式。函數
直接上佈局相關的代碼:佈局
size = constraints.constrain(_requestedSize); if (child != null) { child.layout(constraints); alignChild(); }
若是child存在的話,就將child設爲對應的尺寸,而後按照對齊方式進行對齊。可是在實際寫sample的時候,感受跟我預想中的表現不太一致,並且常常報出異常。不知道是我理解錯了,仍是樣例寫錯了,若是有了解的同窗,麻煩告知,在此感謝。性能
代碼的表現跟我預想中的不太一致,更推薦使用OverflowBox,場景也跟其比較一致。學習
A widget that applies a transformation before painting its child.
Transform在介紹Container的時候有提到過,就是作矩陣變換的。Container中矩陣變換就是使用的Transform。
有過其餘平臺經驗的,對Transform應該不會陌生。能夠對child作平移、旋轉、縮放等操做。
Object > Diagnosticable > DiagnosticableTree > Widget > RenderObjectWidget > SingleChildRenderObjectWidget > Transform
Center( child: Transform( transform: Matrix4.rotationZ(0.3), child: Container( color: Colors.blue, width: 100.0, height: 100.0, ), ), )
示例中將Container繞z軸旋轉了,代碼很簡單。Matrix4也提供了不少便捷的構造函數供你們使用,所以寫起來,並不會有太大的難度。
const Transform({ Key key, @required this.transform, this.origin, this.alignment, this.transformHitTests = true, Widget child, })
上面是其默認的構造函數,Transform也提供下面三種構造函數:
Transform.rotate Transform.translate Transform.scale
transform:一個4x4的矩陣。不難發現,其餘平臺的變換矩陣也都是四階的。一些複合操做,僅靠三維是不夠的,必須採用額外的一維來補充,感興趣的同窗能夠自行搜索瞭解。
origin:旋轉點,相對於左上角頂點的偏移。默認旋轉點事左上角頂點。
alignment:對齊方式。
transformHitTests:點擊區域是否也作相應的改變。
咱們來看看它的繪製代碼:
if (child != null) { final Matrix4 transform = _effectiveTransform; final Offset childOffset = MatrixUtils.getAsTranslation(transform); if (childOffset == null) context.pushTransform(needsCompositing, offset, transform, super.paint); else super.paint(context, offset + childOffset); }
整個繪製代碼不復雜,若是child有偏移的話,則將兩個偏移相加,進行繪製。若是child沒有偏移的話,則按照設置的offset、transform進行繪製。
這個控件算是較常見的控件,不少平移、旋轉、縮放均可以使用的到。若是隻是單純的進行變換的話,用Transform比用Container效率會更高。
A widget that defers the layout of its single child to a delegate.
一個經過外部傳入的佈局行爲,來進行佈局的控件,不一樣於其餘固定佈局的控件,咱們自定義一些單節點佈局控件的時候,能夠考慮使用它。
CustomSingleChildLayout提供了一個控制child佈局的delegate,這個delegate能夠控制這些因素:
Object > Diagnosticable > DiagnosticableTree > Widget > RenderObjectWidget > SingleChildRenderObjectWidget > CustomSingleChildLayout
class FixedSizeLayoutDelegate extends SingleChildLayoutDelegate { FixedSizeLayoutDelegate(this.size); final Size size; @override Size getSize(BoxConstraints constraints) => size; @override BoxConstraints getConstraintsForChild(BoxConstraints constraints) { return new BoxConstraints.tight(size); } @override bool shouldRelayout(FixedSizeLayoutDelegate oldDelegate) { return size != oldDelegate.size; } } Container( color: Colors.blue, padding: const EdgeInsets.all(5.0), child: CustomSingleChildLayout( delegate: FixedSizeLayoutDelegate(Size(200.0, 200.0)), child: Container( color: Colors.red, width: 100.0, height: 300.0, ), ), )
因爲SingleChildLayoutDelegate是一個抽象類,咱們須要單獨寫一個繼承自它的delegate,來進行相關的佈局操做。上面例子中是一個固定尺寸的佈局。
構造函數以下:
const CustomSingleChildLayout({ Key key, @required this.delegate, Widget child })
alignment:一個抽象類,咱們須要自行實現佈局的類。
咱們直接來看其佈局函數:
size = _getSize(constraints); if (child != null) { final BoxConstraints childConstraints = delegate.getConstraintsForChild(constraints); assert(childConstraints.debugAssertIsValid(isAppliedConstraint: true)); child.layout(childConstraints, parentUsesSize: !childConstraints.isTight); final BoxParentData childParentData = child.parentData; childParentData.offset = delegate.getPositionForChild(size, childConstraints.isTight ? childConstraints.smallest : child.size); }
其child的constraints是經過delegate傳入的,而這個delegate則是咱們經過外部繼承自SingleChildLayoutDelegate實現的。
咱們接下來看一下SingleChildLayoutDelegate提供了哪些接口:
Size getSize(BoxConstraints constraints) => constraints.biggest; BoxConstraints getConstraintsForChild(BoxConstraints constraints) => constraints; Offset getPositionForChild(Size size, Size childSize) => Offset.zero; bool shouldRelayout(covariant SingleChildLayoutDelegate oldDelegate);
其中前三個都是佈局相關的,包括尺寸、constraints、位置。最後一個函數是判斷是否須要從新佈局的。咱們在編寫delegate的時候,能夠選擇須要進行修改的屬性進行重寫,並給予相應的佈局屬性便可。
這種控件用起來可能會繁瑣一些,可是咱們能夠經過這個控件去封裝一些基礎的控件,供他人使用。
到目前爲止,Flutter中單子節點佈局控件,大體上都簡單的梳理了一遍。若是一直在關注這個系列文章的同窗,應該能夠發現我常常吐槽其控件設計。
Flutter中總共有18個單子節點佈局控件,18個啊,尚未算多子節點佈局控件,也沒有算可能會新增的。這樣的學習成本很是高。雖然常見的就那麼幾種,平時一直用那些也都沒有問題,可是佈局的時候老是會遇到那麼幾種解決不了的問題。並且梳理的時候,會發現,幾種控件都能解決的問題,並非說把效果實現出了就完事了,這中間確定會涉及到哪一種控件效率更高。若是要對Flutter項目作較深刻的性能優化,這些控件確定都得掌握。
Flutter的這種設計,把一些本來應該由它們承擔的成本,轉移到了開發者身上。不少控件在平常使用中幾乎都用不上,並無考慮太多實際的使用場景。固然了,也仍是得安慰本身,這畢竟只是初期,亂點就亂點,日子確定會愈來愈好的。
後面還會將多子節點控件所有梳理一遍,而後來個大總結,對什麼場景該使用哪一種控件,如何進行控件級別的優化,作一個總結。
筆者建了一個Flutter學習相關的項目,Github地址,裏面包含了筆者寫的關於Flutter學習相關的一些文章,會按期更新,也會上傳一些學習Demo,歡迎你們關注。