flutter系列之:UI layout簡介
簡介
對於一個前端框架來說,除了各個組件之外,最重要的就是將這些組件進行連接的布局了。布局的英文名叫做layout,就是用來描述如何將組件進行擺放的一個約束。
在flutter中,基本上所有的對象都是widget,對於layout來說也不例外。也就是說在flutter中layout也是用代碼來完成的,這和其他的用配置文件來描述layout的語言有所不同。
你可以把layout看做是一種看不見的widget,這些看不見的widget是用來作用在可見的widget對象上,給他們實施一些限制。
flutter中layout的分類
flutter中的layout widget有很多,他們大概可以分為三類,分別是只包含一個child的layout widget,可以包含多個child的layout widget和可滑動的Sliver widgets。
這三種layout也有很多種具體的實現,對於Single-child layout widgets來說,包含下面這些widgets:
- Align — 用來對其包含在其中的組件進行對其操作。
- AspectRatio — 對其中的組件進行比例縮放。
- Baseline — 通過使用子組件的baseline來進行定位。
- Center — 自組件位於中間。
- ConstrainedBox — 類似於IOS中的constrain,表示子組件的限制條件。
- Container — 一個常用的widget,可以用來包含多個其他的widget。
- CustomSingleChildLayout — 將其單個子項的布局推遲。
- Expanded — 將Row, Column 或者 Flex的child進行擴展。
- FittedBox — 根據fit來縮放和定位其child。
- FractionallySizedBox — 將child按照總可用空間進行調整。
- IntrinsicHeight — 一個將其child調整為child固有高度的小部件。
- IntrinsicWidth — 一個將其child調整為child固有寬度的小部件。
- LimitedBox — 限制一個box的size。
- Offstage — 將child放入render tree中,但是卻並不觸發任何重繪。
- OverflowBox — 允許child覆蓋父組件的限制。
- Padding — 為child提供padding。
- SizedBox — 給定size的box。
- SizedOverflowBox — 可以覆蓋父組件限制的box。
- Transform — 子組件可以變換。
以上是包含單個child的layout組件,下面是可以包含多個child的layout組件:
- Column — 表示一列child。
- CustomMultiChildLayout — 使用代理來定位和縮放子組件。
- Flow — 流式布局。
- GridView — 網格布局。
- IndexedStack — 從一系列的child中展示其中的一個child。
- LayoutBuilder — 可以依賴父組件大小的widget tree。
- ListBody — 根據給定的axis來布局child。
- ListView — 可滾動的列表。
- Row — 表示一行child。
- Stack — 棧式布局的組件。
- Table — 表格形式的組件。
- Wrap — 可以對子child進行動態調整的widget。
可滑動的Sliver widgets有下面幾種:
- CupertinoSliverNavigationBar — 是一種IOS風格的導航bar。
- CustomScrollView — 可以自定義scroll效果的ScrollView。
- SliverAppBar — material風格的app bar,其中包含了CustomScrollView。
- SliverChildBuilderDelegate — 使用builder callback為slivers提供child的委託。
- SliverChildListDelegate — 使用list來為livers提供child的委託。
- SliverFixedExtentList — 固定axis extent的sliver。
- SliverGrid — child是二維分佈的sliver。
- SliverList — child是線性布局的sliver。
- SliverPadding — 提供padding的sliver。
- SliverPersistentHeader — 可變size的sliver。
- SliverToBoxAdapter — 包含單個box widget的Sliver。
常用layout舉例
上面我們列出了所有的flutter layout,他們幾乎滿足了我們在程序中會用到的所有layout需求,這裡我們以兩個最基本和最常用的layout:Row和Column為例,來詳細講解layout的使用。
Row和Column都屬於上面講到的多個child的layout widget,它裏面可以包含多個其他的widget組件。
先看一下Row和column的定義。
class Row extends Flex {
Row({
Key? key,
MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start,
MainAxisSize mainAxisSize = MainAxisSize.max,
CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center,
TextDirection? textDirection,
VerticalDirection verticalDirection = VerticalDirection.down,
TextBaseline? textBaseline, // NO DEFAULT: we don't know what the text's baseline should be
List<Widget> children = const <Widget>[],
}) : super(
children: children,
key: key,
direction: Axis.horizontal,
mainAxisAlignment: mainAxisAlignment,
mainAxisSize: mainAxisSize,
crossAxisAlignment: crossAxisAlignment,
textDirection: textDirection,
verticalDirection: verticalDirection,
textBaseline: textBaseline,
);
}
class Column extends Flex {
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>[],
}) : super(
children: children,
key: key,
direction: Axis.vertical,
mainAxisAlignment: mainAxisAlignment,
mainAxisSize: mainAxisSize,
crossAxisAlignment: crossAxisAlignment,
textDirection: textDirection,
verticalDirection: verticalDirection,
textBaseline: textBaseline,
);
}
可以看到Row和Column都繼承自Flex,並且他們的構造方法都是調用了Flex的構造方法,兩者的區別就在於direction不同,Row的direction是Axis.horizontal,而Column的direction是Axis.vertical。
那麼什麼是Flex呢?
Flex是一個widget,在Flex中的子組件會按照某一個指定的方向進行展示。這個方向是可以控制的,比如橫向或者豎向,如果你已經提前知道了主軸的方向,那麼可以使用Row或者Column來替代Flex,因為這樣更加簡潔。
在Flex中,如果想要child在某個方向填滿可用空間,則可以將該child包裝在Expanded中。
要注意的是,Flex是不可滾動的,如果Flex中的child太多,超出了Flex中的可用空間,那麼Flex將會報錯,所以如果你需要展示很多child的情況下,可以考慮使用可滾動的組件,比如ListView。
如果你只有一個child,那麼就沒有必要使用Flex或者Row和Column了,可以考慮使用Align或者Center來對child進行定位。
在Flex中有幾個非常重要的參數,比如mainAxisAlignment表示的是子組件沿主軸方向的排列規則,mainAxisSize表示的是主軸的size大小,crossAxisAlignment表示的是和主軸垂直軸的子組件排列規則。當然還有它最最重要的children屬性,children是一個Widget的list列表,用來存儲要展示的子組件。
以Row為例,我們創建一個簡單的RowWidget:
class RowWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Row(
textDirection: TextDirection.ltr,
children: [
YellowBox(),
YellowBox(),
YellowBox(),
],
);
}
}
這裡我們返回了一個Row對象,設置了textDirection和children屬性。
children裏面是自定義的YellowBox:
class YellowBox extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
width: 50,
height: 50,
decoration: BoxDecoration(
color: Colors.yellow,
border: Border.all(),
),
);
}
}
YellowBox是一個長和寬都是50的正方形。我們這裡使用了BoxDecoration對其上色。
最後將RowWidget放到Scaffold的body裏面,如下所示:
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: RowWidget()
);
}
我們可以看到下面的圖像:
大家可以看到YellowBox是緊貼在一起的,如果我們想要均勻分別該如何做呢?
我們可以在Row中添加一個屬性叫做mainAxisAlignment,取值如下:
mainAxisAlignment: MainAxisAlignment.spaceEvenly
重新運行,生成的圖像如下:
上面我們還提到了一個Expanded組件,可以用來填充剩餘的可用空間,我們把最後一個YellowBox用Expanded圍起來,如下所示:
return Row(
textDirection: TextDirection.ltr,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
YellowBox(),
YellowBox(),
Expanded(
child: YellowBox(),
)
],
);
生成的圖像如下:
可以看到最後一個Box填充到了整個Row剩餘的空間。
大家要注意,這時候mainAxisAlignment是沒有效果的。
如果觀察Expanded的構造函數,可以看到Expanded還有一個flex屬性:
const Expanded({
Key? key,
int flex = 1,
required Widget child,
}) : super(key: key, flex: flex, fit: FlexFit.tight, child: child);
flex屬性表示的是flex factor,默認值是1,還是上面的例子,我們將flex調整為2,看看效果:
children: [
YellowBox(),
YellowBox(),
Expanded(
flex:2,
child: YellowBox(),
)
],
運行的結果和flex=1是一樣的,為什麼呢?
事實上這個flex表示的是相對於其他Expanded的組件所佔用的空間比例。我們可以講所有的子組件都用Expanded進行擴充,然後再看看效果:
children: [
Expanded(
child: YellowBox(),
),
Expanded(
child: YellowBox(),
),
Expanded(
flex: 2,
child: YellowBox(),
)
],
運行結果如下:
可以看到最後一個child佔用的空間是前面兩個的兩倍。
如果我們想要在YellowBox中間添加空格怎麼辦呢?有兩種方法。
第一種方法是使用SizedBox,如下:
children: [
Expanded(
child: YellowBox(),
),
SizedBox(
width: 100,
),
Expanded(
child: YellowBox(),
),
Expanded(
flex: 2,
child: YellowBox(),
)
],
SizedBox裏面可以包含子child,從而重新設置子child的長度和高度。如果不包含子child則會生成一個空格。
還有一種方式是使用Spacer,如下所示:
children: [
Expanded(
child: YellowBox(),
),
Spacer(flex: 2),
Expanded(
child: YellowBox(),
),
Expanded(
flex: 2,
child: YellowBox(),
)
],
生成的圖像如下:
Spacer和SizedBox都可以生成空白,不同的是Spacer可以和flex一起使用,而SizedBox必須固定size大小。
總結
以上就是fluter中layout和的分類和基本layout Row和Column的使用情況了。
本文的例子://github.com/ddean2009/learn-flutter.git
更多內容請參考 //www.flydean.com/07-flutter-ui-layout-overview/
最通俗的解讀,最深刻的乾貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!
歡迎關注我的公眾號:「程序那些事」,懂技術,更懂你!