Demo
地址: GitHub地址Widget
都會包含一個或多個子widget
,不一樣的佈局類Widget
對子widget
排版(layout
)方式不一樣Widget
實際上就是Element
的配置數據, Widget
的功能是描述一個UI
元素的一個配置數據, 而真正的UI
渲染是由Element
構成Flutter
中,根據Widget
是否須要包含子節點將Widget
分爲了三類,分別對應三種Element
,以下表Widget | 對應的Element | 用途 |
---|---|---|
LeafRenderObjectWidget |
LeafRenderObjectElement |
Widget 樹的葉子節點,用於沒有子節點的widget ,一般基礎widget 都屬於這一類,如Text 、Image |
SingleChildRenderObjectWidget |
SingleChildRenderObjectElement |
包含一個子Widget ,如:ConstrainedBox 、DecoratedBox 等 |
MultiChildRenderObjectWidget |
MultiChildRenderObjectElement |
包含多個子Widget ,通常都有一個children 參數,接受一個Widget 數組。如Row 、Column 、Stack 等 |
Widget
就是指直接或間接繼承(包含)MultiChildRenderObjectWidget
的Widget
,它們通常都會有一個children
屬性用於接收子Widget
Widget
的繼承關係以下:
Widget
> RenderObjectWidget
> (Leaf/SingleChild/MultiChild)RenderObjectWidget
RenderObjectWidget
類中定義了建立、更新RenderObject
的方法,子類必須實現他們Widget
來講,其佈局算法都是經過對應的RenderObject
對象來實現的Flutter
中主要有如下幾種佈局類的Widget
:
Row
和Column
Flex
Wrap
、Flow
Stack
、Positioned
Row
和Column
是一種現行佈局的Widget
, 都繼承自Flex
子Widget
Row
的主軸即爲水平方向, Column
的主軸是垂直方向, 切二者的屬性和使用都同樣Row({
Key key,
MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start,
MainAxisSize mainAxisSize = MainAxisSize.max,
CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center,
TextDirection textDirection,
VerticalDirection verticalDirection = VerticalDirection.down,
TextBaseline textBaseline,
List<Widget> children = const <Widget>[],
})
Column({
Key key,
MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start,
MainAxisSize mainAxisSize = MainAxisSize.max,
CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center,
TextDirection textDirection,
VerticalDirection verticalDirection = VerticalDirection.down,
TextBaseline textBaseline,
List<Widget> children = const <Widget>[],
})
複製代碼
mainAxisAlignment
子Widget
在主軸方向的排列方式, 爲方便如下皆稱Widget
爲組件html
// 默認值
MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start
複製代碼
start
: 子widgets
向主軸起點對其, 依次排列end
: 子widgets
向主軸終點對其, 依次排列center
: 全部子widgets
居中排列spaceBetween
: 均勻分配,相鄰widgets
間距離相同。每行第一個widgets
與行首對齊,每行最後一個widgets
與行尾對齊spaceAround
: 均勻分配,相鄰widgets
間距離相同。每行第一個widgets
到行首的距離和每行最後一個widgets
到行尾的距離將會是相鄰widgets
之間距離的一半spaceEvenly
: 均勻分配,相鄰widgets
間距離相同。每行第一個widgets
到行首的距離和每行最後一個widgets
到行尾的距離和相鄰widgets
之間距離相同屬性 | 效果 |
---|---|
start |
![]() |
end |
![]() |
center |
![]() |
spaceBetween |
![]() |
spaceAround |
![]() |
spaceEvenly |
![]() |
mainAxisSize
// 默認值
MainAxisSize mainAxisSize = MainAxisSize.max
複製代碼
Row
在主軸(水平)方向佔用的空間,默認是MainAxisSize.max
max
表示儘量多的佔用水平方向的空間,此時不管子widgets
實際佔用多少水平空間,Row
的寬度始終等於水平方向的最大寬度;MainAxisSize.min
表示儘量少的佔用水平空間,當子widgets
沒有佔滿水平剩餘空間,則Row
的實際寬度等於全部子widgets
佔用的的水平空間verticalDirection
表示Row縱軸(垂直)的對齊方向, 默認值down
,表示從上到下; up
表示從下到上git
// 默認值
VerticalDirection verticalDirection = VerticalDirection.down
複製代碼
crossAxisAlignment
Widgets
在縱軸方向的對齊方式,Row
的高度等於子Widgets
中最高的子元素高度crossAxisAlignment
的參考系是verticalDirection
// 默認值
CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center
/** * VerticalDirection.down時, crossAxisAlignment.start指頂部對齊 * VerticalDirection.up時,crossAxisAlignment.start指底部對齊 * crossAxisAlignment.end和crossAxisAlignment.start正好相反 */
複製代碼
VerticalDirection.down
時, crossAxisAlignment
個枚舉值以下start
: 頂部對其end
: 底部對其center
: 居中對其stretch
: 側軸方向上, 子Widget
的高度拉伸至和Row
的高度相同baseline
: 不論VerticalDirection
取值如何, 子Widget
的頂部和Row
的頂部對其textDirection
表示水平方向子widget的佈局順序(是從左往右仍是從右往左),默認爲系統當前Locale環境的文本方向(如中文、英語都是從左往右,而阿拉伯語是從右往左)github
TextDirection textDirection
/** * ltr: 從左往右 * rtl: 從右往左 */
複製代碼
textBaseline
用於對其文本的水平線, 詳情可參考算法
TextBaseline textBaseline
/** * alphabetic: 用於對齊普通的字母基線 * ideographic: 用於對齊表意基線 */
複製代碼
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
textDirection: TextDirection.ltr,
verticalDirection: VerticalDirection.down,
textBaseline: TextBaseline.ideographic,
children: <Widget>[
new Container(width: 80.0, height:80.0, color: Colors.red,),
new Container(width: 80.0, height:90.0, color: Colors.green,),
new Container(width: 80.0, height:100.0, color: Colors.blue,),
],
)
複製代碼
特別注意canvas
在Row
和Column
中, 若是子widget
超出屏幕範圍,則會報溢出錯誤數組
widget
按照必定比例來分配父容器空間Flutter
中的彈性佈局主要經過Flex
和Expanded
來配合實現Flex
能夠沿着水平或垂直方向排列子widget
Row
或Column
,由於Row
和Column
都繼承自Flex
,參數基本相同,因此能使用Flex
的地方必定可使用Row
或Column
Flex
自己功能是很強大的,它也能夠和Expanded
配合實現彈性佈局,接下來咱們只討論Flex
和彈性佈局相關的屬性(其它屬性已經在介紹Row
和Column
時介紹過了)Flex({
Key key,
//彈性佈局的方向
@required this.direction,
this.mainAxisAlignment = MainAxisAlignment.start,
this.mainAxisSize = MainAxisSize.max,
this.crossAxisAlignment = CrossAxisAlignment.center,
this.textDirection,
this.verticalDirection = VerticalDirection.down,
this.textBaseline,
List<Widget> children = const <Widget>[],
})
// direction
// 水平方向
Axis direction = Axis.horizontal
// 垂直方向, 默認爲垂直方向
Axis direction = Axis.vertical
複製代碼
Flex
繼承自MultiChildRenderObjectWidget
,對應的RenderObject
爲RenderFlex
,RenderFlex
中實現了其佈局算法微信
能夠按比例縮放Row
、Column
和Flex
子widget
所佔用的空間markdown
class Expanded extends Flexible {
const Expanded({
Key key,
int flex = 1,
@required Widget child,
}) : super(key: key, flex: flex, fit: FlexFit.tight, child: child);
}
複製代碼
flex
爲彈性係數,若是爲0或null
,則child
是沒有彈性的,即不會被擴伸佔用的空間 若是大於0,全部的Expanded
按照其flex
的比例來分割主軸的所有空閒空間less
Row(
children: <Widget>[
Container(width: 80.0, height:80.0, color: Colors.red,),
Expanded(
flex: 1,
child: Container(width: 80.0, height:80.0, color: Colors.blue,),
),
Expanded(
flex: 1,
child: Container(width: 80.0, height:80.0, color: Colors.yellow,),
)
],
),
複製代碼
Row
和Column
中, 若是子widget
超出屏幕範圍,則會報溢出錯誤Row
默認只有一行,若是超出屏幕不會折行Flutter
中經過Wrap
和Flow
來支持流式佈局Wrap({
Key key,
this.direction = Axis.horizontal,
this.alignment = WrapAlignment.start,
this.spacing = 0.0,
this.runAlignment = WrapAlignment.start,
this.runSpacing = 0.0,
this.crossAxisAlignment = WrapCrossAlignment.start,
this.textDirection,
this.verticalDirection = VerticalDirection.down,
List<Widget> children = const <Widget>[],
})
複製代碼
能夠看到Wrap
中的不少屬性和Row
中相同, 這裏就不在贅述了, 這裏主要看一下Wrap
中特有的屬性ide
子Widget
在主軸上的對其方式
// 默認值
this.alignment = WrapAlignment.start
// 取值: start, end, center, spaceBetween, spaceAround, spaceEvenly
複製代碼
子Widget
在縱軸上的對其方式
// 默認值
this.runAlignment = WrapAlignment.start
// 取值: start, end, center, spaceBetween, spaceAround, spaceEvenly
複製代碼
主軸方向子widget
的間距: spacing: 10
縱軸方向子widget
的間距: runSpacing: 10
Flow
,由於其過於複雜,須要本身實現子widget
的位置轉換,在不少場景下首先要考慮的是Wrap
是否知足需求Flow
主要用於一些須要自定義佈局的UI或性能要求較高(如動畫中)的場景Flow
有以下優勢:
Flow
是一個對child
尺寸以及位置調整很是高效的控件,Flow
用轉換矩陣對child
進行位置調整的時候進行了優化Flow
定位事後,若是child
的尺寸或者位置發生了變化,在FlowDelegate
中的paintChildren()
方法中調用context.paintChild
進行重繪,而context.paintChild
在重繪時使用了轉換矩陣,並無實際調整Widget
位置。FlowDelegate
的paintChildren()
方法,因此咱們須要本身計算每個widget
的位置,所以,能夠實現自定義佈局。widget
大小,必須經過指定父容器大小或重寫FlowDelegate
的getSize
返回固定大小class FlowWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
return Container(
color: Colors.orange,
child: Flow(
delegate: ShowFlowDelegate(margin: EdgeInsets.all(10)),
children: <Widget>[
Container(width: 100.0, height:100.0, color: Colors.red),
Container(width: 100.0, height:100.0, color: Colors.yellow),
Container(width: 100.0, height:100.0, color: Colors.blue),
Container(width: 100.0, height:100.0, color: Colors.cyan),
Container(width: 100.0, height:100.0, color: Colors.pink)
],
),
);
}
}
複製代碼
實現一個繼承自FlowDelegate
的類, 並重寫響應的方法
class ShowFlowDelegate extends FlowDelegate {
EdgeInsets margin =EdgeInsets.zero;
ShowFlowDelegate({this.margin});
@override
void paintChildren(FlowPaintingContext context) {
var x = margin.left;
var y = margin.top;
//計算每個子widget的位置
for (int i = 0; i < context.childCount; i++) {
var w = context.getChildSize(i).width + x + margin.right;
if (w < context.size.width) {
context.paintChild(i,
transform: new Matrix4.translationValues(
x, y, 0.0));
x = w + margin.left;
} else {
x = margin.left;
y += context.getChildSize(i).height + margin.top + margin.bottom;
//繪製子widget(有優化)
context.paintChild(i,
transform: new Matrix4.translationValues(
x, y, 0.0));
x += context.getChildSize(i).width + margin.left + margin.right;
}
}
}
@override
Size getSize(BoxConstraints constraints) {
// 設置Flow的大小
return Size(double.infinity, 300);
}
@override
bool shouldRepaint(FlowDelegate oldDelegate) {
return oldDelegate !=this;
}
}
複製代碼
Web
中的絕對定位、iOS
中的Frame
佈局是類似的,子widget
能夠根據到父容器四個角的位置來肯定自己的位置widget
堆疊(按照代碼中聲明的順序)Flutter
中使用Stack
和Positioned
來實現絕對定位,Stack
容許子widget
堆疊,而Positioned
能夠給子widget
定位Stack({
Key key,
this.alignment = AlignmentDirectional.topStart,
this.textDirection,
this.fit = StackFit.loose,
this.overflow = Overflow.clip,
List<Widget> children = const <Widget>[],
})
複製代碼
決定子Widget
在Stack
中的定位
// 默認值
this.alignment = AlignmentDirectional.topStart
// 取值以下, start和end爲水平方向, top和bottom是垂直方向
static const AlignmentDirectional topStart = AlignmentDirectional(-1.0, -1.0);
static const AlignmentDirectional topCenter = AlignmentDirectional(0.0, -1.0);
static const AlignmentDirectional topEnd = AlignmentDirectional(1.0, -1.0);
static const AlignmentDirectional centerStart = AlignmentDirectional(-1.0, 0.0);
static const AlignmentDirectional center = AlignmentDirectional(0.0, 0.0);
static const AlignmentDirectional centerEnd = AlignmentDirectional(1.0, 0.0);
static const AlignmentDirectional bottomStart = AlignmentDirectional(-1.0, 1.0);
static const AlignmentDirectional bottomCenter = AlignmentDirectional(0.0, 1.0);
static const AlignmentDirectional bottomEnd = AlignmentDirectional(1.0, 1.0);
// 還可使用具體數值比例定位, 設置值在0~1之間
AlignmentDirectional(0.8, 0.9)
複製代碼
決定alignment
對齊的參考系
// 默認ltr
textDirection: TextDirection.ltr
// textDirection的值爲TextDirection.ltr,則alignment的start表明左,end表明右
// textDirection的值爲TextDirection.rtl,則alignment的start表明右,end表明左
複製代碼
用於決定沒有定位的子widget
如何去適應Stack
的大小
// 默認值
this.fit = StackFit.loose
// StackFit.loose表示使用子widget的大小
// StackFit.expand表示擴伸到Stack的大小
複製代碼
決定如何顯示超出Stack
顯示空間的子widget
// 默認值
this.overflow = Overflow.clip
// Overflow.clip時,超出部分會被剪裁(隱藏)
// Overflow.visible時,時則不會被剪裁
複製代碼
Stack(
// alignment: AlignmentDirectional.center,
alignment: AlignmentDirectional(0.8, 0.8),
textDirection: TextDirection.ltr,
fit: StackFit.loose,
overflow: Overflow.visible,
children: <Widget>[
Container(width: 100.0, height:100.0, color: Colors.red),
Container(width: 100.0, height:100.0, color: Colors.yellow),
],
)
複製代碼
Positioned
和iOS
中的Frame
設置位置和大小同樣, 根據上下左右和寬高設置Widget
的定位和大小
const Positioned({
Key key,
this.left,
this.top,
this.right,
this.bottom,
this.width,
this.height,
@required Widget child,
})
複製代碼
left、top 、right、 bottom
分別表明離Stack左、上、右、底四邊的距離,width
和height
用於指定定位元素的寬度和高度
注意,此處的width、height 和其它地方的意義稍微有點區別,此處用於配合left、top 、right、 bottom來定位widget,舉個例子,在水平方向時,你只能指定left、right、width三個屬性中的兩個,如指定left和width後,right會自動算出(left+width),若是同時指定三個屬性則會報錯,垂直方向同理
child: Stack(
alignment: AlignmentDirectional.center,
children: <Widget>[
// 這個widget會根據alignment的設置展現
Container(child: Text('https', style: TextStyle(color: Colors.red)), color: Colors.yellow,),
// 這個widget會根據left和top和width的設置顯示和alignment無關了, 實際width爲80
Positioned(
left: 10,
top: 30,
width: 80,
child: Container(width: 100.0, height:100.0, color: Colors.red),
),
Positioned(
right: 10,
bottom: 50,
child: Container(width: 100.0, height:100.0, color: Colors.blue),
)
],
),
複製代碼
至此,
Flutter
中佈局相關的Widget
也都學習完了......接下來就是容器類Widget
了
歡迎您掃一掃下面的微信公衆號,訂閱個人博客!