Flex布局
Flutter中的Flex布局和Web的CSS中的Flex布局類似。
在Flutter 中用于控制Flex布局的有Row,Column,Expanded,Flexible,Spacer,Flex這些控件。
row水準布局

屬性名 | 類型 | 預設值 | 說明 |
mainAxisAlignment | MainAxisAlignment | MainAxisAlignment.start | 主軸的排列方式 |
mainAxisSize | MainAxisSize | MainAxisSize.max | 主軸占空間的大小 |
crossAxisAlignment | CrossAxisAlignment | CrossAxisAlignment.center | 次軸的排列方式 |
textDirection | TextDirection | null | 确定children在水準方向的擺放順序 |
verticalDirection | VerticalDirection | VerticalDirection.down | 确定children在垂直方向的擺放順序 |
textBaseline | TextBaseline | null | 文字基準線對齊 |
我們首先建立三個大小不一的Container
class LyoutRowDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("水準布局"),
),
body: Container(
child: Row(
children: <Widget>[
Container(
height: 100,
width: 50,
color: Colors.redAccent,
),
Container(
height: 50,
width: 50,
color: Colors.blueAccent,
),
Container(
color: Colors.black,
height: 75,
width: 75,
)
],
),
));
}
}
複制代碼
主軸排列方式MainAxisAlignment
子元素children的排列方式由這兩個屬性決定textDirection和verticalDirection。textDirection決定水準方向的排列方式
TextDirection.ltr
從左往右排列(把左當作起始位置),
TextDirection.rtl
從右往左排列(把右當作起始位置)。verticalDirection時水準方向的排列方式。當值為down(向下排列)時起始位置在頂部。當值為up(向上排列)是起始位置在底部。
-
start (預設值) 根據textDirection屬性排列方向。
将children放置在主軸的起點
textDirection屬性值為rtl時右圖
-
end
根據textDirection屬性排列方向。
将children放置在主軸的末尾
textDirection屬性值為rtl時右圖
-
center
根據textDirection屬性排列方向。
将children放置在主軸的中心
textDirection屬性值為rtl時右圖
-
spaceBetween
根據textDirection屬性排列方向。
将主軸方向上的空白區域均分,使得children之間的空白區域相等,首尾child都靠近首尾,沒有間隙
textDirection屬性值為rtl時右圖
-
spaceAround
根據textDirection屬性排列方向。
将主軸方向上的空白區域均分,使得children之間的空白區域相等,但是首尾空白區域為一半
textDirection屬性值為rtl時右圖
-
spaceEvenly
根據textDirection屬性排列方向
将主軸方向上的空白區域均分,使得children之間的空白區域相等,包括首尾空白區域
textDirection屬性值為rtl時右圖
交叉軸的排列方式crossAxisAlignment
都以
mainAxisAlignment: MainAxisAlignment.start
為例
-
start
将子元素在交叉軸上起點對齊。設定verticalDirection為VerticalDirection.up右圖。
-
end
将子元素在交叉軸上末尾對齊。設定verticalDirection為VerticalDirection.up右圖。
-
center
将子元素在交叉軸上居中對齊。設定verticalDirection無改變。
-
strentch
将子元素在交叉軸上拉伸
-
baseline
基準線對其适用于文字,我們首先建立文字的start。使用baseline。必須設定textBaseline屬性。不同文字的文字基準線。主要是字母文字(如:英語)和表意文字(如:漢語)的基準線是不同的。必須告訴Flutter你使用的文字。
Row(
crossAxisAlignment: CrossAxisAlignment.start,
textBaseline: TextBaseline.alphabetic,
children: [
Text(
'Flutter',
style: TextStyle(
color: Colors.yellow,
fontSize: 30.0
),
),
Text(
'Flutter',
style: TextStyle(
color: Colors.blue,
fontSize: 20.0
),
),
],
);
複制代碼
設定對齊方式為基準線
主軸占用的空間mainAxisSize
mainAxisSize隻有兩個值一個是min一個是max。預設是max。
當值為min時候。主軸縮緊,變為最小。
如圖:即使這時mainAxisAlignment:為MainAxisAlignment.spaceAround。主軸的長度仍為最小狀态
Column垂直布局
垂直布局與水準布局的屬性和方法一緻,唯一需要注意的是textDirection(水準排列方式)和verticalDirection(垂直排列方式)
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: "垂直布局",
home: Scaffold(
appBar: AppBar(title: Text("垂直布局")),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[
Text("1",style: TextStyle(fontSize: 100),),
Expanded(
child: Text("2"),
),
Text("33333"),
Text("4")
],
),
)));
}
}
複制代碼
為了便于觀看我們将1擴大
Flex布局
我們檢視以下源碼便于了解
可以看到
Row
和
Column
都繼承與
Flex
。
class Row extends Flex
class Column extends Flex
複制代碼
而
Row
的構造函數可選命名參數(即
{}
包裹的參數)有8個。
傳入父級的
super
構造函數卻有9個,多出了
direction: Axis.horizontal
。
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>[],
}) : super(
children: children,
key: key,
direction: Axis.horizontal,
mainAxisAlignment: mainAxisAlignment,
mainAxisSize: mainAxisSize,
crossAxisAlignment: crossAxisAlignment,
textDirection: textDirection,
verticalDirection: verticalDirection,
textBaseline: textBaseline,
);
}
複制代碼
再Flex中屬性
direction
實際上時主軸的方向。且被
@required
标注。它是必選的。
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>[],
}) : assert(direction != null),
assert(mainAxisAlignment != null),
assert(mainAxisSize != null),
assert(crossAxisAlignment != null),
assert(verticalDirection != null),
assert(crossAxisAlignment != CrossAxisAlignment.baseline || textBaseline != null),
super(key: key, children: children);
複制代碼
Expanded
上面的例子我們遇到從未見過的一個widget。很容易就可以看出。Expanded會忽略子元素的大小并強制自動擴充使主軸填充父級可用的空白區域。Expanded必須是Row、Column、Flex的children。
下面兩個例子便于更好了解:
//當子元素隻有一個Expanded時
class LyoutExpanded extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Expanded"),
),
body: Center(
child: Container(
child: Column(
children: <Widget>[
Expanded(
child: Container(
color: Colors.blue,
width: 100,//這個被忽略掉了
),
),
],
),
),
),
);
}
}
複制代碼
class LyoutExpanded extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Expanded"),
),
body: Center(
child: Container(
height: 50,//填充使主軸擴充至父級高度
child: Column(
children: <Widget>[
Expanded(
child: Container(
color: Colors.blue,
height: 100,//
width: 100,
),
),
],
),
),
),
);
}
}
複制代碼
Expanden還有一個屬性為
flex
預設值為1。代表權重。當子元素有多個Expanden。按照權重值占據父級的高度(row時為水準寬度)
<Widget>[
Expanded(
flex: 2,
child: Container(
color: Colors.blue,
width: 100,
),
),
Expanded(
child: Container(
color: Colors.red,
width: 100,
),
),
Expanded(
child: Container(
color: Colors.yellow,
width: 100,
),
),
],
複制代碼
我們可以看到藍色的塊時紅和黃的二倍。
Flexible
Flexible元件可以使Row、Column、Flex等子元件在主軸方向有填充可用空間的能力(例如,Row在水準方向,Column在垂直方向),但是它與Expanded元件不同,它不強制子元件填充可用空間。同樣Flexible元件必須是Row、Column、Flex等元件的children。
Flexiible 有屬性
fit
當屬性值FlexFit.tight時。Flexible和Expanded沒有差別。
從源碼可以看出Expanded繼承與Flexible
且引用 上級構造函數傳入fit的值為FlexFit.tight。
class Expanded extends Flexible
//省區其他源碼
const Expanded({
Key key,
int flex = 1,
@required Widget child,
}) : super(key: key, flex: flex, fit: FlexFit.tight, child: child);
}
複制代碼
在Column的children中傳入。屬性fit:為FlexFit.tight,按照權值填充空白區域。
<Widget>[
Flexible(
fit: FlexFit.tight,
child: Container(
color: Colors.blue,
height: 100,
width: 100,
),
),
Flexible(
fit: FlexFit.tight,
flex: 2,
child: Container(
color: Colors.yellow,
height: 100,
width: 100,
),
),
],
複制代碼
一旦fit的值為FlexFit.loose(預設值)先按flex的值分主軸确定占主軸的大小,再按child調整元素的高度。不強制拉伸
例如第一個為tight強制擴充。第二個為loose不強制擴充。
<Widget>[
Flexible(
fit: FlexFit.tight,
child: Container(
color: Colors.blue,
height: 100,
width: 100,
),
),
Flexible(
fit: FlexFit.loose,
flex: 2,
child: Container(
color: Colors.yellow,
height: 100,
width: 100,
),
),
],
複制代碼
因為第二個為loose是以黃顔色的Container高度為100。而藍色Container的 fit值為 tight,他的大小和上一個例子一緻。
Spacer
Spacer建立一個可以調整的空白區域,可用于調整Flex容器(Row或者Colum)中widget之間的間距。
一旦children裡面包含了Spacer。
mainAxisAlignment
的屬性值将起不到作用。Spacer已經占據了所有額外的空間,是以沒有剩餘的空間可以重新配置設定。
Row(
mainAxisAlignment: MainAxisAlignment.end,//起不到作用
children: <Widget>[
Container(
height: 100,
width: 50,
color: Colors.redAccent,
),
Spacer(),
Container(
height: 50,
width: 50,
color: Colors.blueAccent,
),
Container(
color: Colors.black,
height: 75,
width: 75,
)
],
),
複制代碼
縮放布局
絕大多數flutter的widget是盒子。可以将他們疊放,堆疊,嵌套。
我們可以層級嵌套盒子,但是如果一個盒子不适合(大小不适合)另一個盒子。該如何解決?
為了解決這個問題Flutter提供了FittedBox,這個和移動端的ImageView類似。
它實作的功能是使子元素縮放(fit)或者調整位置(alignment)
- BoxFit.contain(預設值)等比例擴大或縮小,但内容不會超過容器範圍
- BoxFit.cover按照比例逐漸擴大至充滿容器,内容有能會超過容器範圍。當child比例與容器不同時,要麼高度溢出容器,要麼寬度溢出容器。
- BoxFit.fill不保留比例強制拉伸(縮小)填充容器。
- BoxFit.fitHeight保持比例確定高度在容器中顯示完整。
- BoxFit.fitWidth保持比例確定寬度在容器中顯示完整。
- BoxFit.none将child對齊在目标框内(預設劇中),并丢棄位于框外的部分,源檔案不放大也不縮小。
- BoxFit.scaleDown将child對齊在目标框内(預設劇中),當child大于容器,則與contain一緻。如果child小于容器,則與none一緻
為了便于了解我們可以找一個圖檔進行測試
class Lyoutfitdemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("data")),
body: Container(
height: 250,
width: 250,
color: Colors.indigo[200],
child: FittedBox(
child: Image.asset('images/fittedbox.png')),
),
);
}
}
複制代碼
- BoxFit.contain
- BoxFit.cover
- BoxFit.fill
- BoxFit.fitHeight
5.BoxFit.fitWidth
- BoxFit.none
- BoxFit.scaleDown
備注:不用建立FittedBox,Image含有屬性fit。值效果和FittedBox一緻。
堆疊布局
Stack與web絕對定位布局模型類似。Stack不是按行或者列來布局的,而是按照特定順序和位置堆疊。可以使用
Positioned
和
Align
作為Stack的的定位。
示例:使用層疊布局實作一個 圖檔漸變效果
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: "漸變狀态欄",
home: Scaffold(
body: Container(
height: 400,
child: Stack(
fit: StackFit.expand,
children: <Widget>[
Image.asset(
'images/Stack.png',
fit: BoxFit.cover,
),
const DecoratedBox(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment(0.0, -1.0),
end: Alignment(0.0, -0.4),
colors: <Color>[Color(0x90000000), Color(0x00000000)],
),
),
),
],
),
),
),
);
}
}
複制代碼
圖檔漸變效果
這是兩個控件堆疊在一起的例子。當然子部件可以出現在父級内的任何位置。通過兩種widget實作對位置的控制。
Align布局
Align為對齊部件,設定child的在父級的(如container、stack等)對齊方式,并根據child的尺寸調整自身的尺寸。
本文的父級容器選用stack
Alignment
其中有這幾種屬性
topLeft(左上),topCenter(頂部中央),topRight(右上),centerLeft,center,centerRight,bottomLeft,bottomCenter,bottomRight
class LayoutAlignDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("align部件"),
),
body: Stack(
children: <Widget>[
Align(
alignment: Alignment.topLeft,
child: Container(
width: 100,
height: 100,
color: Colors.red,
),
),
],
));
}
}
複制代碼
Alignment.lerp(Alignment a, Alignment b, double t)
lerp方法有三個參數,前兩個參數為Alignment類型,第三個參數為小數。
當t為0時,這個方法傳回的值為a。
當t為1時,傳回b的值。
當t為0.5時,這個widget位置就位于a和b指定的位置中間。
是以這個t為一個偏移量。指定為a到b之間的偏移。
//在Stack children中添加一個align
Align(
alignment: Alignment.lerp(Alignment.bottomCenter, Alignment.center,0.5),//位于Alignment.bottomCenter和Alignment.center的中央
child: Container(
width: 100,
height: 100,
color: Colors.yellow,
),
),
複制代碼
偏移量對齊
上面介紹了相對于對其方式a和b的偏移對其。
FractionalOffset 是另外一個偏移方法,它是相對于父部件左上角的偏移。
建立偏移量FractionalOffset (dx,dy)。dx和dy的取值都是0~1。左上的位置為dx和dy都為0。
Align(
alignment: FractionalOffset(0, 0.5),
child: Container(
width: 100,
height: 100,
color: Colors.red,
),
),
複制代碼
另外在源碼中:
class FractionalOffset extends Alignment
複制代碼
FractionalOffset
繼承于Alignment是以Alignment的屬性都可以使用,這樣我們要使得widget位于左上也可以用使用
FractionalOffset.topLeft
class LayoutAlignDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("align部件"),
),
body: Stack(
children: <Widget>[
Align(
alignment: FractionalOffset(0, 0),
child: Container(
width: 100,
height: 100,
color: Colors.red,
),
),
Align(
alignment: FractionalOffset(0, 0.5),
child: Container(
width: 100,
height: 100,
color: Colors.red,
),
),
Align(
alignment: FractionalOffset(0, 1),
child: Container(
width: 100,
height: 100,
color: Colors.red,
),
),
Align(
alignment: Alignment.lerp(Alignment.bottomCenter, Alignment.center,0.5),
child: Container(
width: 100,
height: 100,
color: Colors.yellow,
),
),
Align(
alignment: FractionalOffset.topRight,
child: Container(
width: 100,
height: 100,
color: Colors.yellow,
),
),
],
));
}
}
複制代碼
與之相類似的還有Alignment(dx, dy),以父級容器的中心為坐标系原點。dx的取值範圍為-1~1,dy的取值範圍為-1~1。設定子元素的位置。
Positioned
Positioned 部件可以控制
Stack
中子元素的位置。Positioned 與Align不同的是Position必須是Stack的Children。
Position有
top
、
bottom
、
left
、
right
、
height
,
width
。
top
、
bottom
、
left
、
right
這些量都是子元素邊界與父級某一邊界的距離。
也就是說當一個高度和長度固定容器,一旦我們确定left或right的一個量和bottom或top的一個量,其位置就可以确定。
部分代碼
Stack(
children: <Widget>[
Positioned(
top: 100,
right: 100,
width: 100,
height: 100,
child: Container(
color: Colors.red,
),
),
],
)
複制代碼
源碼分析
const Positioned({
Key key,
this.left,
this.top,
this.right,
this.bottom,
this.width,
this.height,
@required Widget child,
}) : assert(left == null || right == null || width == null),
assert(top == null || bottom == null || height == null),
super(key: key, child: child);
複制代碼
在水準方向上如果assert(false)會抛出錯誤
而括号裡的值為
left == null || right == null || width == null
left ,right ,width 至少有一個值為null這個程式才不會報錯。
也就是說當width未指定(為空)使left和right是可以同時存在的。這時的容器的寬度會以據邊界的距離(left和right)自動調整。
Stack(
children: <Widget>[
Positioned(
top: 100,
right: 100,
left: 100,
// width: 100,
height: 100,
child: Container(
color: Colors.red,
),
),
],
)
複制代碼
IndexedStack
IndexedStack繼承于Stack
class IndexedStack extends Stack
複制代碼
他和Stack不同的是有一個index的屬性,表示隻顯示第幾個元素。
IndexedStack(
index: 1,//隻顯示第二個
children: <Widget>[
Positioned(
top: 100,
right: 100,
left: 100,
height: 100,
child: Container(
color: Colors.red,
),
),
Positioned(
top: 500,
right: 100,
left: 100,
height: 100,
child: Container(
color: Colors.red,
),
),
],
)
複制代碼
容器布局
Container可以建立一個矩形元素。可以用BoxDecoration進行裝飾。背景,邊框,陰影。
class LyoutContainerdemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Container容器"),
),
body: Container(
height: 200,
width: 200,
child: Text("這是一個Container容器"),
decoration: BoxDecoration(
color: Colors.red[200],
// shape: BoxShape.circle, //形狀
border: Border.all(width: 10.0),
boxShadow: [
BoxShadow(
offset: Offset(0.0, 100.0), //模糊偏移量
color: Color.fromRGBO(16, 20, 188, 1.0), //顔色
blurRadius: 10, //模糊
spreadRadius: -9.0, //在應用模糊之前陰影的膨脹量
),
],
),
),
);
}
}
複制代碼
為了示範功能,這個圖檔效果做的比較誇張。
附上一個好看的效果
相似于Css的盒子布局,我們也可以通過Margin給盒子設定外邊距。
Container(
margin: EdgeInsets.all(50.0),//外邊距50.0
height: 200,
width: 200,
decoration: BoxDecoration(
color: Colors.red[600],
border: Border.all(width: 2.0),
boxShadow: [
BoxShadow(
offset: Offset(2.0, 9.0), //偏移量
color: Colors.red[200], //顔色
blurRadius: 10, //模糊
spreadRadius: -1.0, //在應用模糊之前陰影的膨脹量
),
],
),
),
複制代碼
Padding布局
用于處理容器與子元素之間的距離。與Padding對應的是margin屬性。margin用于處理與其他元件之間的距離。Padding部件和容器内的pading屬性的效果實際上是一緻的,當同時出現,真實的padding将是兩者相加。
class LayoutPaddingDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Padding 布局"),
),
body: Container(
padding: EdgeInsets.all(20.0),//#1這兩處的屬性效果是一緻的
decoration: BoxDecoration(
color: Colors.yellow,
border: Border.all(color: Colors.white, width: 8.0)),//白色邊框
child: Padding(
child: Container(
decoration: BoxDecoration(
color: Colors.red,
border: Border.all(color: Colors.white, width: 8.0)),//白色邊框
),
padding: EdgeInsets.all(10.0),//#1這兩處的屬性效果是一緻的
),
),
);
}
}
複制代碼
寫在後面
Flutter中涉及到布局的Widget有30多種,一樣的效果的ui,實作的途徑有很多中。本篇就重點涉及幾個常用的部件。
參考連結
youtu.be/T4Uehk3_wlY