談到flutter第一個想到的一定是widget,我們在開發flutter app時90%的時間都是在撰寫widget,更明確的說應該是撰寫widget裡面的build method,我們也知道app啟動以後會按照我們寫進build裡的程式產生相應的畫面,但是到底framework是做了什麼事情才讓這一切都如此的理所當然的呢?
首先先看看官方怎麼說,它解釋widget是element的configuration。我們暫且先不管那個element(未來會介紹)到底是什麼東西好惹,光是把widget說成configuration本身的意思就很讓人耐人尋味。我當初看到這段描述時是完全摸不著頭緒,有種傷害性不大,污辱性極強的fu~WTF我90%的時間都是花在撰寫設定檔上??
但是當我開始理解widget所扮演的職責以及element與render object是什麼東東以後,我才漸漸有一種醍醐灌頂豁然開朗的感覺。接下來讓我用一個非常簡單的例子來狡辯解釋一下
class MyApp extends StatelessWidget {
const MyApp({super.key, required this.players});
final List<Player> players;
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(20),
width: 300,
height: 300,
color: Colors.green,
);
}
}
從上面代碼中我們主要就是在build方法裡寫一個Container widget把width和high設成300和把color設定為Colors.green,這樣畫面就會出現一個長寬都是300的綠色正方形。
怎麼樣,是不是有種撰寫config file味兒了?
其實畫面就是使用不同widget並且把想要的數值設定進去變成一份config file給framework產生畫面的過程罷了。
有了這層理解我們提高一點難度來看一個有趣的widget,一個所有開發者一定會使用到的widget Container。
在開始之前大家可以猜猜看container是如何實作的?它可以設定color/padding/size/...等等等,簡直是widget萬花筒可能以為裡面用了什麼屌炸天的黑魔法....
/// Creates a widget that combines common painting, positioning, and sizing widgets.
首先我們來看看Contianer source code裡寫的註解,裡面提到一個很關鍵的點combines多種不同的widgets,所以按照註解來猜測設計思路應該是把不同的widget都放在container中,依照呼叫端填入的參數,來決定使用哪些widget。
接下來我繼續看到Container build method(因為代碼不常我就全部貼出來了,大家可以花點時間看完它,如果有任何看不懂的歡迎在下面留言提問我會很榮幸地回答你們的)
@override
Widget build(BuildContext context) {
Widget? current = child;
if (child == null && (constraints == null || !constraints!.isTight)) {
current = LimitedBox(
maxWidth: 0.0,
maxHeight: 0.0,
child: ConstrainedBox(constraints: const BoxConstraints.expand()),
);
}
if (alignment != null)
current = Align(alignment: alignment!, child: current);
final EdgeInsetsGeometry? effectivePadding = _paddingIncludingDecoration;
if (effectivePadding != null)
current = Padding(padding: effectivePadding, child: current);
if (color != null)
current = ColoredBox(color: color!, child: current);
if (clipBehavior != Clip.none) {
assert(decoration != null);
current = ClipPath(
clipper: _DecorationClipper(
textDirection: Directionality.maybeOf(context),
decoration: decoration!,
),
clipBehavior: clipBehavior,
child: current,
);
}
if (decoration != null)
current = DecoratedBox(decoration: decoration!, child: current);
if (foregroundDecoration != null) {
current = DecoratedBox(
decoration: foregroundDecoration!,
position: DecorationPosition.foreground,
child: current,
);
}
if (constraints != null)
current = ConstrainedBox(constraints: constraints!, child: current);
if (margin != null)
current = Padding(padding: margin!, child: current);
if (transform != null)
current = Transform(transform: transform!, alignment: transformAlignment, child: current);
return current!;
}
從build method中我們可以很明顯的就是不同的widget一層一層包起來來達到效果。
之前我常常因為不懂Container的實作原理鬧出烏龍,例如說如果一個widget想要加20的Padding一般來說應該是這樣寫
Padding(
padding: const EdgeInsets.all(20),
child:
Container(
width: 300,
height: 300,
color: Colors.yellow,
child: Text("widget is config"),
),
)
如果widget恰好又是container你可能想說那乾脆把padding寫進container裡這樣還少包一個padding widget
Container(
padding: const EdgeInsets.all(20),
width: 300,
height: 300,
color: Colors.yellow,
child : Text("widget is config"),
)
此時你一定會感覺自己真是聰明讓代碼變更簡潔惹,殊不知Container是先包了Padding才包ConstrainedBox,所以Padding其實是包在child widget上。
如果想要有一般Padding的效果,要用Container的margin屬性,Padding會在ConstrainedBox包完之後才包,這就會呈現出你要的效果
總之透過Container的實作,我們可以理解widget的設計和特性也能理解為什麼widget會被視為config的原因了吧,今天大概就到這邊,如果有任合問題都歡迎提問我都會積極地回答,謝謝大家。