一,概述
Flutter
中擁有30多種預定義的布局widget
,常用的有Container
、Padding
、Center
、Flex
、Row
、Colum
、ListView
、GridView
。按照《Flutter技術入門與實戰》上面來說的話,大概分為四類
- 基礎布局組件:Container(容器布局),Center(居中布局),Padding(填充布局),Align(對齊布局),Colum(垂直布局),Row(水平布局),Expanded(配合Colum,Row使用),FittedBox(縮放布局),Stack(堆疊布局),overflowBox(溢出父視圖容器)。
- 寬高尺寸處理:SizedBox(設置具體尺寸),ConstrainedBox(限定最大最小寬高布局),LimitedBox(限定最大寬高布局),AspectRatio(調整寬高比),FractionallySizedBox(百分比布局)
- 列表和表格處理:ListView(列表),GridView(網格),Table(表格)
- 其它布局處理:Transform(矩陣轉換),Baseline(基准線布局),Offstage(控制是否顯示組件),Wrap(按寬高自動換行布局)
二,寬高尺寸處理
- SizedBox(設置具體尺寸)
- 介紹
比較常用的一個控件,設置具體尺寸。SizeBox組件是一個特定大小的盒子,這個組件強制它的chird有特定的寬度和高度,如果寬度和高度為null,則此組件將調整自身大小匹配該緯度中child的大小。 - 布局行為
SizedBox布局行為相對較簡單:
- child不為null時,如果設置了寬高,則會強制把child尺寸調到此寬高;如果沒有設置寬高,則會根據child尺寸進行調整;
- child為null時,如果設置了寬高,則自身尺寸調整到此寬高值,如果沒設置,則尺寸為0;
- 繼承關系
Diagnosticable > DiagnosticableTree > Widget > RenderObjectWidget > SingleChildRenderObjectWidget > SizedBox
- 構造方法
const SizedBox({
Key key,
this.width, //寬
this.height, //高
Widget child //子組件
}) - 參數解析
width:寬度值,如果具體設置了,則強制child寬度為此值,如果沒設置,則根據child寬度調整自身寬度。
height:同上。
- 介紹
- ConstrainedBox(限定最大最小寬高布局)
- 介紹
這個控件的作用是添加額外的限制條件(constraints)到child上,本身挺簡單的,可以被一些控件替換使用。Flutter的布局控件體系,梳理着發現確實有點亂,感覺總體思想是缺啥就造啥
- 布局行為
ConstrainedBox的布局行為非常簡單,取決於設置的限制條件,而關於父子節點的限制因素生效優先級,可以查看之前的文章,在這里就不做具體敘述了。
- 繼承關系
Object > Diagnosticable > DiagnosticableTree > Widget > RenderObjectWidget > SingleChildRenderObjectWidget > ConstrainedBox
- 構造函數
包含了一個constraints屬性,且不能為null。
ConstrainedBox({ Key key, @required this.constraints, Widget child })
- 參數解析
constraints:
添加到child上的額外限制條件,其類型為BoxConstraints。BoxConstraints的作用是干啥的呢?其實很簡單,就是限制各種最大最小寬高。說到這里插一句,double.infinity在widget布局的時候是合法的,也就說,例如. 想最大的擴展寬度,可以將寬度值設為double.infinity。
- 介紹
LimitedBox
-
(限定最大寬高布局)
-
介紹LimitedBox,通過字面意思,也可以猜測出這個控件的作用,是限制類型的控件。這種類型的控件前面也介紹了不少了,這個是對最大寬高進行限制的控件。
-
布局行為從布局的角度講,LimitedBox是將child限制在其設定的最大寬高中的,但這個限定是有條件的。當LimitedBox最大寬度不受限制時,child的寬度就會受到這個最大寬度的限制,高度同理。
- 繼承關系
Object > Diagnosticable > DiagnosticableTree > Widget > RenderObjectWidget > SingleChildRenderObjectWidget > LimitedBox
- 構造函數
const LimitedBox({ Key key, this.maxWidth = double.infinity, this.maxHeight = double.infinity, Widget child, })
- 參數解析
maxWidth:限定的最大寬度,默認值是double.infinity,不能為負數。
maxHeight:同上。
- AspectRatio(調整寬高比)
- 介紹
AspectRatio的作用是調整child到設置的寬高比,這種控件在其他移動端平台上一般都不會提供,Flutter之所以提供,我想最大的原因,可能就是自定義起來特別麻煩吧。 - 布局行為
AspectRatio的布局行為分為兩種情況:
- AspectRatio首先會在布局限制條件允許的范圍內盡可能的擴展,widget的高度是由寬度和比率決定的,類似於BoxFit中的contain,按照固定比率去盡量占滿區域。
- 如果在滿足所有限制條件過后無法找到一個可行的尺寸,AspectRatio最終將會去優先適應布局限制條件,而忽略所設置的比率。
- 繼承關系
Object > Diagnosticable > DiagnosticableTree > Widget > RenderObjectWidget > SingleChildRenderObjectWidget > AspectRatio
從繼承關系看,AspectRatio是基礎的布局控件。
- 構造函數
const AspectRatio({ Key key, @required this.aspectRatio, Widget child })
構造函數只包含了一個aspectRatio屬性,其中aspectRatio不能為null。
- 參數解析
aspectRatio:aspectRatio是寬高比,最終可能不會根據這個值去布局,具體則要看綜合因素,外層是否允許按照這種比率進行布局,只是一個參考值。
- 介紹
- FractionallySizedBox(百分比布局)
- 介紹
FractionallySizedBox控件會根據現有空間,來調整child的尺寸,所以說child就算設置了具體的尺寸數值,也不起作用。 - 布局行為
FractionallySizedBox的布局行為主要跟它的寬高因子兩個參數有關,當參數為null或者有具體數值的時候,布局表現不一樣。當然,還有一個輔助參數alignment,作為對齊方式進行布局。
- 當設置了具體的寬高因子,具體的寬高則根據現有空間寬高 * 因子,有可能會超出父控件的范圍,當寬高因子大於1的時候;
- 當沒有設置寬高因子,則填滿可用區域;
- 繼承關系
Object > Diagnosticable > DiagnosticableTree > Widget > RenderObjectWidget > SingleChildRenderObjectWidget > FractionallySizedBox
- 構造函數
const FractionallySizedBox({ Key key, this.alignment = Alignment.center, this.widthFactor, this.heightFactor, Widget child, })
- 參數解析
alignment:對齊方式,不能為null。
widthFactor:寬度因子,跟之前介紹的控件類似,寬度乘以這個值,就是最后的寬度。
heightFactor:高度因子,用作計算最后實際高度的。
其中widthFactor和heightFactor都有一個規則
- 如果不為null,那么實際的最大寬高度則為child的寬高乘以這個因子;
- 如果為null,那么child的寬高則會盡量充滿整個區域。
- 介紹
三,常用方法
- SizeBox(設置具體尺寸)
/** * MySizeBox */ class MySizeBox extends StatelessWidget { @override Widget build(BuildContext context) { // TODO: implement build return new Container( color: Colors.green, padding: const EdgeInsets.all(5.0), child: SizedBox( width: 200.0, height: 200.0, child: new Container( color: Colors.red, width: 100.0, height: 300.0, ), ), ); } }
效果圖:
源碼解析:
SizedBox內部是通過RenderConstrainedBox來實現的。具體的源碼就不解析了,總體思路是,根據寬高值算好一個constraints,然后強制應用到child上。 - ConstrainedBox(限定最大最小寬高布局)
/** * ConstrainedBox * 在一個寬高200.0的Container上添加一個約束最大最小寬高的ConstrainedBox,實際的顯示中,則是一個寬高為150.0的區域。 */ class MyConstrainedBox extends StatelessWidget { @override Widget build(BuildContext context) { // TODO: implement build return new ConstrainedBox( constraints: const BoxConstraints( minWidth: 100.0, minHeight: 100.0, maxWidth: 150.0, maxHeight: 150.0, ), child: new Container( width: 200.0, height: 200.0, color: Colors.red, ), ); } }
效果圖:
源碼解析:
@override RenderConstrainedBox createRenderObject(BuildContext context) { return new RenderConstrainedBox(additionalConstraints: constraints); }
RenderConstrainedBox實現其繪制。其具體的布局表現分兩種情況:
如果child不為null,則將限制條件加在child上;
如果child為null,則會盡可能的縮小尺寸。
具體代碼如下:@override void performLayout() { if (child != null) { child.layout(_additionalConstraints.enforce(constraints), parentUsesSize: true); size = child.size; } else { size = _additionalConstraints.enforce(constraints).constrain(Size.zero); } }
使用場景:
需要添加額外的約束條件可以使用此控件,例如設置最小寬高,盡可能的占用區域等等。筆者在實際開發中使用的倒不是很多,倒不是說這個控件不好使用,而是好多約束因素是綜合的,例如需要額外的設置margin、padding屬性能,因此單獨再套個這個就顯得很繁瑣了。
- LimitedBox(限定最大寬高布局)
class MyLimitedBox extends StatelessWidget { @override Widget build(BuildContext context) { // TODO: implement build return new Row( children: <Widget>[ new Container( color: Colors.red, width: 100.0, ), LimitedBox( maxWidth: 150.0, child: new Container( color: Colors.blue, width: 250.0, ), ) ], ); } }
效果圖:
源碼解析:先不說其源碼,單純從其作用,前面介紹的SizedBox、ConstrainedBox都類似,都是通過強加到child的constraint,來達到相應的效果。
我們直接看其計算constraint的代碼
minWidth: constraints.minWidth, maxWidth: constraints.hasBoundedWidth ? constraints.maxWidth : constraints.constrainWidth(maxWidth), minHeight: constraints.minHeight, maxHeight: constraints.hasBoundedHeight ? constraints.maxHeight : constraints.constrainHeight(maxHeight)
LimitedBox只是改變最大寬高的限定。具體的布局代碼如下:
if (child != null) { child.layout(_limitConstraints(constraints), parentUsesSize: true); size = constraints.constrain(child.size); } else { size = _limitConstraints(constraints).constrain(Size.zero); }
根據最大尺寸,限制child的布局,然后將自身調節到child的尺寸。
使用場景:
是不可能清楚了,光是找例子,就花了不少時間。Flutter的一些冷門控件,真的是除了官方的文檔,啥材料都木有。
谷歌說這個很有用,還是一臉懵逼。這種控件,也有其他的替代解決方案,LimitedBox可以達到的效果,ConstrainedBox都可以實現。 - AspectRatio(調整寬高比)
/** * AspectRatio * 定義了一個高度為200的區域,內部AspectRatio比率設置為1.5,最終AspectRatio的是寬300高200的一個區域。 */ class MyAspectRatio extends StatelessWidget { @override Widget build(BuildContext context) { // TODO: implement build return new Container( height: 200.0, child: new AspectRatio( aspectRatio: 1.5, child: new Container( color: Colors.red, ), ), ); } }
效果圖:
源碼解析:
@override RenderAspectRatio createRenderObject(BuildContext context) => new RenderAspectRatio(aspectRatio: aspectRatio);
經過前面一些控件的解析,我想大家對這種構造應該不會再陌生了,繪制都是交由RenderObject去完成的,這里則是由RenderAspectRatio去完成具體的繪制工作。
RenderAspectRatio的構造函數中會對aspectRatio做一些檢測(assert)
aspectRatio不能為null; aspectRatio必須大於0; aspectRatio必須是有限的。
接下來我們來看一下RenderAspectRatio的具體尺寸計算函數:
(1)如果限制條件為isTight,則返回最小的尺寸(constraints.smallest);
if (constraints.isTight) return constraints.smallest;
(2)如果寬度為有限的值,則將高度設置為width / _aspectRatio。 如果寬度為無限,則將高度設為最大高度,寬度設為height * _aspectRatio;
if (width.isFinite) { height = width / _aspectRatio; } else { height = constraints.maxHeight; width = height * _aspectRatio; }
(3)接下來則是在限制范圍內調整寬高,總體思想則是寬度優先,大於最大值則設為最大值,小於最小值,則設為最小值。
如果寬度大於最大寬度,則將其設為最大寬度,高度設為width / _aspectRatio;if (width > constraints.maxWidth) { width = constraints.maxWidth; height = width / _aspectRatio; }
如果高度大於最大高度,則將其設為最大高度,寬度設為height * _aspectRatio;
if (height > constraints.maxHeight) { height = constraints.maxHeight; width = height * _aspectRatio; }
如果寬度小於最小寬度,則將其設為最小寬度,高度設為width / _aspectRatio;
if (width < constraints.minWidth) { width = constraints.minWidth; height = width / _aspectRatio; }
如果高度小於最小高度,則將其設為最小高度,寬度設為height * _aspectRatio。
if (height < constraints.minHeight) { height = constraints.minHeight; width = height * _aspectRatio; }
- FractionallySizedBox(百分比布局)
/** * MyFractionallySizeBox */ class MyFractionallySizeBox extends StatelessWidget { @override Widget build(BuildContext context) { // TODO: implement build return new Container( color: Colors.blue, height: 150.0, width: 150.0, padding: const EdgeInsets.all(10.0), child: new FractionallySizedBox( alignment: Alignment.topLeft, widthFactor: 1.5, heightFactor: 0.5, child: new Container( color: Colors.red, ), ), ); } }
效果圖:
源碼解析:
FractionallySizedBox內部具體渲染是由RenderFractionallySizedOverflowBox來實現的,通過命名就可以看出,這個控件可能會Overflow。我們直接看實際計算尺寸的代碼
double minWidth = constraints.minWidth; double maxWidth = constraints.maxWidth;
if (_widthFactor != null) { final double width = maxWidth * _widthFactor; minWidth = width; maxWidth = width; }
double minHeight = constraints.minHeight; double maxHeight = constraints.maxHeight;
if (_heightFactor != null) { final double height = maxHeight * _heightFactor; minHeight = height; maxHeight = height; }
源代碼中,根據寬高因子是否存在,來進行相對應的尺寸計算。
使用場景:
當需要在一個區域里面取百分比尺寸的時候,可以使用這個,比方說,高度40%寬度70%的區域。當然,AspectRatio也可以達到近似的效果。