本文主要介紹Flutter佈局中的SizedOverflowBox、Transform、CustomSingleChildLayout三種控制元件,詳細介紹了其佈局行為以及使用場景,並對原始碼進行了分析。
1. SizedOverflowBox
A widget that is a specific size but passes its original constraints through to its child, which will probably overflow.
1.1 簡介
光看名稱,就可以猜出,SizedOverflowBox是SizedBox與OverflowBox的結合體。
1.2 佈局行為
SizedOverflowBox主要的佈局行為有兩點:
- 尺寸部分。通過將自身的固定尺寸,傳遞給child,來達到控制child尺寸的目的;
- 超出部分。可以突破父節點尺寸的限制,超出部分也可以被渲染顯示,與OverflowBox類似。
1.3 繼承關係
Object > Diagnosticable > DiagnosticableTree > Widget > RenderObjectWidget > SingleChildRenderObjectWidget > SizedOverflowBox
複製程式碼
1.4 示例程式碼
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,),
),
);
複製程式碼
程式碼執行的時候報出了下面的異常,很神奇。但是同學們可以自己看看程式碼執行的效果,可以超出,但是不太好用。
══╡ EXCEPTION CAUGHT BY RENDERING LIBRARY ╞═════════════════════════════════════════════════════════
複製程式碼
1.5 原始碼解析
const SizedOverflowBox({
Key key,
@required this.size,
this.alignment = Alignment.center,
Widget child,
})
複製程式碼
1.5.1 屬性解析
size:固定的尺寸。
alignment:對齊方式。
1.5.2 原始碼
直接上佈局相關的程式碼:
size = constraints.constrain(_requestedSize);
if (child != null) {
child.layout(constraints);
alignChild();
}
複製程式碼
如果child存在的話,就將child設為對應的尺寸,然後按照對齊方式進行對齊。但是在實際寫sample的時候,感覺跟我預想中的表現不太一致,而且經常報出異常。不知道是我理解錯了,還是樣例寫錯了,如果有了解的同學,麻煩告知,在此感謝。
1.6 使用場景
程式碼的表現跟我預想中的不太一致,更推薦使用OverflowBox,場景也跟其比較一致。
2. Transform
A widget that applies a transformation before painting its child.
2.1 簡介
Transform在介紹Container的時候有提到過,就是做矩陣變換的。Container中矩陣變換就是使用的Transform。
2.2 佈局行為
有過其他平臺經驗的,對Transform應該不會陌生。可以對child做平移、旋轉、縮放等操作。
2.3 繼承關係
Object > Diagnosticable > DiagnosticableTree > Widget > RenderObjectWidget > SingleChildRenderObjectWidget > Transform
複製程式碼
2.4 示例程式碼
Center(
child: Transform(
transform: Matrix4.rotationZ(0.3),
child: Container(
color: Colors.blue,
width: 100.0,
height: 100.0,
),
),
)
複製程式碼
示例中將Container繞z軸旋轉了,程式碼很簡單。Matrix4也提供了很多便捷的建構函式供大家使用,因此寫起來,並不會有太大的難度。
2.5 原始碼解析
const Transform({
Key key,
@required this.transform,
this.origin,
this.alignment,
this.transformHitTests = true,
Widget child,
})
複製程式碼
上面是其預設的建構函式,Transform也提供下面三種建構函式:
Transform.rotate
Transform.translate
Transform.scale
複製程式碼
2.5.1 屬性解析
transform:一個4x4的矩陣。不難發現,其他平臺的變換矩陣也都是四階的。一些複合操作,僅靠三維是不夠的,必須採用額外的一維來補充,感興趣的同學可以自行搜尋瞭解。
origin:旋轉點,相對於左上角頂點的偏移。預設旋轉點事左上角頂點。
alignment:對齊方式。
transformHitTests:點選區域是否也做相應的改變。
2.5.2 原始碼
我們來看看它的繪製程式碼:
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進行繪製。
2.6 使用場景
這個控制元件算是較常見的控制元件,很多平移、旋轉、縮放都可以使用的到。如果只是單純的進行變換的話,用Transform比用Container效率會更高。
3. CustomSingleChildLayout
A widget that defers the layout of its single child to a delegate.
3.1 簡介
一個通過外部傳入的佈局行為,來進行佈局的控制元件,不同於其他固定佈局的控制元件,我們自定義一些單節點佈局控制元件的時候,可以考慮使用它。
3.2 佈局行為
CustomSingleChildLayout提供了一個控制child佈局的delegate,這個delegate可以控制這些因素:
- 可以控制child的佈局constraints;
- 可以控制child的位置;
- 在parent的尺寸不依賴於child的情況下,可以決定parent的尺寸。
3.3 繼承關係
Object > Diagnosticable > DiagnosticableTree > Widget > RenderObjectWidget > SingleChildRenderObjectWidget > CustomSingleChildLayout
複製程式碼
3.4 示例程式碼
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,來進行相關的佈局操作。上面例子中是一個固定尺寸的佈局。
3.5 原始碼解析
建構函式如下:
const CustomSingleChildLayout({
Key key,
@required this.delegate,
Widget child
})
複製程式碼
3.5.1 屬性解析
alignment:一個抽象類,我們需要自行實現佈局的類。
3.5.2 原始碼
我們直接來看其佈局函式:
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的時候,可以選擇需要進行修改的屬性進行重寫,並給予相應的佈局屬性即可。
3.6 使用場景
這種控制元件用起來可能會繁瑣一些,但是我們可以通過這個控制元件去封裝一些基礎的控制元件,供他人使用。
4. 階段性小結
到目前為止,Flutter中單子節點佈局控制元件,大致上都簡單的梳理了一遍。如果一直在關注這個系列文章的同學,應該可以發現我經常吐槽其控制元件設計。
Flutter中總共有18個單子節點佈局控制元件,18個啊,還沒有算多子節點佈局控制元件,也沒有算可能會新增的。這樣的學習成本非常高。雖然常見的就那麼幾種,平時一直用那些也都沒有問題,但是佈局的時候總是會遇到那麼幾種解決不了的問題。而且梳理的時候,會發現,幾種控制元件都能解決的問題,並不是說把效果實現出了就完事了,這中間肯定會涉及到哪種控制元件效率更高。如果要對Flutter專案做較深入的效能優化,這些控制元件肯定都得掌握。
Flutter的這種設計,把一些原本應該由它們承擔的成本,轉移到了開發者身上。很多控制元件在日常使用中幾乎都用不上,並沒有考慮太多實際的使用場景。當然了,也還是得安慰自己,這畢竟只是初期,亂點就亂點,日子肯定會越來越好的。
後面還會將多子節點控制元件全部梳理一遍,然後來個大總結,對什麼場景該使用哪種控制元件,如何進行控制元件級別的優化,做一個總結。
5. 後話
筆者建了一個Flutter學習相關的專案,Github地址,裡面包含了筆者寫的關於Flutter學習相關的一些文章,會定期更新,也會上傳一些學習Demo,歡迎大家關注。