本文的目的是爲了讓讀者掌握不一樣佈局類Widget的佈局特色,分享一些在實際使用過程遇到的一些問題,在《Flutter實戰》這本書中已經講解的很詳細了,本文主要是對其內容的濃縮及實際遇到的問題的補充。前端
佈局類Widget就是指直接或間接繼承(包含)MultiChildRenderObjectWidget的Widget,它們通常都會有一個children屬性用於接收子Widget。在Flutter中Element樹纔是最終的繪製樹,Element樹是經過widget樹來建立的(經過Widget.createElement()),widget其實就是Element的配置數據。它的最終佈局、UI界面渲染都是經過RenderObject對象來實現的,這裏的細節我就不詳細描述了,由於我也不懂。不過感興趣的小夥伴也能夠看看本專欄的Flutter視圖的Layout與Paint這篇文章。git
Flutter中主要有如下幾種佈局類的Widget:github
本文[Demo地址](https://github.com/xqqlv/flutte_layout_demo) bash
線性佈局實際上是指沿水平或垂直方向排布子Widget,Flutter中經過Row來實現水平方向的子Widegt佈局,經過Column來實現垂直方向的子Widget佈局。他們都繼承Flex,因此它們有不少類似的屬性。 markdown
在前端的Flex佈局中,默認存在兩根軸:水平的主軸(main axis)和垂直的交叉軸(cross axis)。主軸的開始位置(與邊框的交叉點)叫作main start,結束位置叫作main end;交叉軸的開始位置叫作cross start,結束位置叫作cross end。與Flutter中MainAxisAlignment和CrossAxisAlignment相似,分別表明主軸對齊和縱軸對齊。less
Row({ ..... 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({ ..... 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>[], }) 複製代碼
ListView( children: <Widget>[ Row( mainAxisAlignment: MainAxisAlignment.start, children: <Widget>[ Text("我是Row的子控件 "), Text("MainAxisAlignment.start") ], ), Row( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Text("我是Row的子控件 "), Text("MainAxisAlignment.center") ], ), Row( mainAxisAlignment: MainAxisAlignment.end, children: <Widget>[ Text("我是Row的子控件 "), Text("MainAxisAlignment.end") ], ), Row( crossAxisAlignment: CrossAxisAlignment.start, verticalDirection: VerticalDirection.up, children: <Widget>[ Text(" Hello World ", style: TextStyle(fontSize: 30.0),), Text(" I am Jack "), ], ], ) 複製代碼
ListView(children: <Widget>[ Column( crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[ Text("我是Colum的子控件"), Text("CrossAxisAlignment.start"), ], ), Column( crossAxisAlignment: CrossAxisAlignment.center, children: <Widget>[ Text("我是Colum的子控件"), Text("CrossAxisAlignment.center"), ], ), Column( crossAxisAlignment: CrossAxisAlignment.end, children: <Widget>[ Text("我是Colum的子控件"), Text("CrossAxisAlignment.end"), ], ), ],) 複製代碼
因爲篇幅有限,我就不詳細講解實際遇到的問題了,只說現象和解決辦法:ide
彈性佈局是一種容許子widget按照必定比例來分配父容器空間的佈局方式,若是你知道了它的主軸方向,那就能夠用Row或Column了,通常狀況下,能夠用Flex的地方均可以用Row或者Column一塊兒使用,一般配合Expanded Widget來使用,一樣Expanded也不能脫離Flex單首創建。oop
Expanded繼承自Flexible,Flexible是一個控制Row、Column、Flex等子組件如何佈局的組件,它能夠按比例「擴伸」Row、Column和Flex子widget所佔用的空間。佈局
const Expanded({
int flex = 1,
@required Widget child,
})
複製代碼
flex爲彈性係數,若是爲0或null,則child是沒有彈性的,即不會被擴伸佔用的空間。若是大於0,全部的Expanded按照其flex的比例來分割主軸的所有空閒空間。post
Row(children: <Widget>[ RaisedButton( onPressed: () { print('點擊紅色按鈕事件'); }, color: Colors.red, child: Text('紅色按鈕'), ), Expanded( flex: 1, child: RaisedButton( onPressed: () { print('點擊黃色按鈕事件'); }, color: Colors.yellow, child: Text('黃色按鈕'), ), ), RaisedButton( onPressed: () { print('點擊粉色按鈕事件'); }, color: Colors.green, child: Text('綠色按鈕'), ), ]) 複製代碼
流式佈局(Liquid)的特色(也叫"Fluid") 是頁面元素的寬度按照屏幕分辨率進行適配調整,但總體佈局不變。柵欄系統(網格系統),用戶標籤等。在Flutter中主要有Wrap和Flow兩種Widget實現。
在介紹Row和Colum時,若是子widget超出屏幕範圍,則會報溢出錯誤,在Flutter中經過Wrap和Flow來支持流式佈局,溢出部分則會自動折行。
Wrap({ ... 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>[], }) 複製代碼
上述有不少屬性和Row的相同,其意義其實也是相同的,這裏我就不一一介紹了,主要介紹下不一樣的屬性:
Wrap( spacing: 10.0, direction: Axis.horizontal, alignment: WrapAlignment.start, children: <Widget>[ _card('關注'), _card('推薦'), _card('新時代'), _card('小視頻'), _card('黨媒推薦'), _card('中國新唱將'), _card('歷史'), _card('視頻'), _card('遊戲'), _card('頭條號'), _card('數碼'), ], ) Widget _card(String title) { return Card(child: Text(title),); } } 複製代碼
咱們通常不多會使用Flow,由於其過於複雜,須要本身實現子widget的位置轉換,在不少場景下首先要考慮的是Wrap是否知足需求。Flow主要用於一些須要自定義佈局策略或性能要求較高(如動畫中)的場景。Flow有以下優勢:
咱們對六個色塊進行自定義流式佈局:
Flow( delegate: TestFlowDelegate(margin: EdgeInsets.all(10.0)), children: <Widget>[ new Container(width: 80.0, height:80.0, color: Colors.red,), new Container(width: 80.0, height:80.0, color: Colors.green,), new Container(width: 80.0, height:80.0, color: Colors.blue,), new Container(width: 80.0, height:80.0, color: Colors.yellow,), new Container(width: 80.0, height:80.0, color: Colors.brown,), new Container(width: 80.0, height:80.0, color: Colors.purple,), ], ) 複製代碼
實現TestFlowDelegate:
class TestFlowDelegate extends FlowDelegate { EdgeInsets margin = EdgeInsets.zero; TestFlowDelegate({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; } } } getSize(BoxConstraints constraints){ //指定Flow的大小 return Size(double.infinity,200.0); } @override bool shouldRepaint(FlowDelegate oldDelegate) { return oldDelegate != this; } } 複製代碼
層疊佈局和Web中的絕對定位、Android中的Frame佈局是類似的,子widget能夠根據到父容器四個角的位置來肯定自己的位置。絕對定位容許子widget堆疊(按照代碼中聲明的順序)。Flutter中使用Stack和Positioned來實現絕對定位,Stack容許子widget堆疊,而Positioned能夠給子widget定位(根據Stack的四個角)。
Stack({ this.alignment = AlignmentDirectional.topStart, this.textDirection, this.fit = StackFit.loose, this.overflow = Overflow.clip, List<Widget> children = const <Widget>[], }) 複製代碼
class Loading extends StatelessWidget { /// ProgressIndicator的padding,決定loading的大小 final EdgeInsets padding = EdgeInsets.all(30.0); /// 文字頂部距菊花的底部的距離 final double margin = 10.0; /// 圓角 final double cornerRadius = 10.0; final Widget _child; final bool _isLoading; final double opacity; final Color color; final String text; Loading({ Key key, @required child, @required isLoading, this.text, this.opacity = 0.3, this.color = Colors.grey, }) : assert(child != null), assert(isLoading != null), _child = child, _isLoading = isLoading, super(key: key); @override Widget build(BuildContext context) { List<Widget> widgetList = List<Widget>(); widgetList.add(_child); if (_isLoading) { final loading = [ Opacity( opacity: opacity, child: ModalBarrier(dismissible: false, color: color), ), _buildProgressIndicator() ]; widgetList.addAll(loading); } return Stack( children: widgetList, ); } Widget _buildProgressIndicator() { return Center( child: Container( padding: padding, child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.center, children: <Widget>[ CupertinoActivityIndicator(), Padding( padding: EdgeInsets.only(top: margin), child: Text(text ?? '加載中...')), ], ), decoration: BoxDecoration( borderRadius: BorderRadius.all(Radius.circular(cornerRadius)), color: Colors.white), ), ); } } 複製代碼
本控件使用Stack封裝,你傳入的主視圖在最下面一層,背景層在中間,最上面一層爲菊花和文字loading,用isLoading控制顯示
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),若是同時指定三個屬性則會報錯,垂直方向同理。
//經過ConstrainedBox來確保Stack佔滿屏幕 ConstrainedBox( constraints: BoxConstraints.expand(), child: Stack( alignment:Alignment.center , //指定未定位或部分定位widget的對齊方式 children: <Widget>[ Container(child: Text("Hello world",style: TextStyle(color: Colors.white)), color: Colors.red, ), Positioned( left: 18.0, child: Text("I am Jack"), ), Positioned( top: 18.0, child: Text("Your friend"), ) ], ), ); 複製代碼
本文版權屬於再惠研發團隊,歡迎轉載,轉載請保留出處。@xqqlv