界面佈局主要分爲兩大類:佈局類組件和定位裝飾權重組件,咱們佈局的時候基本都是相互嵌套的git
如下是這個 UI 的 widget 樹形圖:github
咱們這裏講由淺及深的聊一下Flutter佈局問題markdown
Container是flutter中普遍使用的容器類組件less
構造函數ide
Container({
this.alignment,
this.padding, //容器內補白,屬於decoration的裝飾範圍
Color color, // 背景色
Decoration decoration, // 背景裝飾
Decoration foregroundDecoration, //前景裝飾
double width,//容器的寬度
double height, //容器的高度
BoxConstraints constraints, //容器大小的限制條件
this.margin,//容器外補白,不屬於decoration的裝飾範圍
this.transform, //變換
this.child,
})
複製代碼
屬性函數
Container自身尺寸的調節分兩種狀況:oop
Container(
child: Text(
"Hello Flutter", // 文字內容
style: TextStyle(fontSize: 20.0,color: Colors.amber), // 字體樣式 字體大小
),
alignment: Alignment.topLeft, // 字內容的對齊方式 center居中,centerLeft居中左側 centerRight居中右側
// bottomCenter 下居中對齊 ,bottomLeft 下左對齊,bottomRight 下右對齊
// topCenter 上居中對齊,topLeft 上左對齊,topRight 上右對齊
width: 200, // 寬
height: 200, // 高
color: Colors.red, //顏色 color和decoration不能夠同時存在
padding: const EdgeInsets.fromLTRB(20.0,20.0,20.0,20.0), // 邊距 all 包括上下左右 fromLTRB 上下左右分別設置邊距fromLTRB(20.0,20.0,20.0,20.0)
margin: const EdgeInsets.all(30.0), // 外間距
);
複製代碼
decoration的屬性很強大,能夠支持背景圖線性或者徑向的漸變,邊框,圓角,陰影等屬性佈局
Flutter的Decoration能夠設置:背景色 背景圖 邊框 圓角 陰影 漸變色 的等屬性,Decoration 是基類,它的子類有下面這些字體
const BoxDecoration({
this.color,//背景色
this.image,//圖片
this.border,//描邊
this.borderRadius,//圓角大小
this.boxShadow,//陰影
this.gradient,//漸變色
this.backgroundBlendMode,//圖像混合模式
this.shape = BoxShape.rectangle,//形狀,BoxShape.circle和borderRadius不能同時使用
})
複製代碼
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.only(top: 50.0, left: 120.0), //容器外填充
constraints: BoxConstraints.tightFor(width: 200.0, height: 150.0), //卡片大小
decoration: BoxDecoration(//背景裝飾
gradient: RadialGradient( //背景徑向漸變
colors: [Colors.red, Colors.orange],
center: Alignment.topLeft,
radius: .98
),
boxShadow: [ //卡片陰影
BoxShadow(
color: Colors.black54,
offset: Offset(2.0, 2.0),
blurRadius: 4.0
)
]
),
transform: Matrix4.rotationZ(.2), //卡片傾斜變換
alignment: Alignment.center, //卡片內文字居中
child: Text( //卡片文字
"Flutter Demo",
style: TextStyle(color: Colors.white, fontSize: 40.0),
),
);
}
}
複製代碼
SizedBox: 兩種用法:一是可用來設置兩個widget之間的間距,二是能夠用來限制子組件的大小。flex
Column(
children: <Widget>[
SizedBox(height: 30,),
SizedBox(width: 200,height: 200,
child: Container(
width: 200,
height: 200,
color: Colors.red,
),
),
SizedBox(height: 30,),
SizedBox(width: 100,height: 100,
child: Container(
width: 200,
height: 200,
color: Colors.red,
),
)
],
);
複製代碼
能夠看到,代碼中共有4個SizedBox組件,兩個是設置間距的功能,兩個是具備設置約束的功能
所謂線性佈局,即指沿水平或垂直方向排布子組件。Flutter中經過Row和Column來實現線性佈局。Row和Column都繼承自Flex,咱們將在彈性佈局一節中詳細介紹Flex。
項目中 90% 的頁面佈局均可以經過 Row 和 Column 來實現。
Row(
children: <Widget>[
Container(
height: 50,
width: 100,
color: Colors.red,
),
Container(
height: 50,
width: 100,
color: Colors.green,
),
Container(
height: 50,
width: 100,
color: Colors.blue,
),
],
);
複製代碼
Column(
children: <Widget>[
Container(
height: 50,
width: 100,
color: Colors.red,
),
Container(
height: 50,
width: 100,
color: Colors.green,
),
Container(
height: 50,
width: 100,
color: Colors.blue,
),
],
)
複製代碼
在 Row 和 Column 中有一個很是重要的概念:主軸( MainAxis ) 和 交叉軸( CrossAxis ),主軸就是與組件佈局方向一致的軸,交叉軸就是與主軸方向垂直的軸。
具體到 Row 組件,主軸 是水平方向,交叉軸 是垂直方向。而 Column 與 Row 正好相反,主軸 是 垂直方向,交叉軸 是水平方向。
明白了 主軸 和 交叉軸 概念,咱們來看下 mainAxisAlignment 屬性,此屬性表示主軸方向的對齊方式,默認值爲 start,表示從組件的開始處佈局,此處的開始位置和 textDirection 屬性有關,textDirection 表示文本的佈局方向,其值包括 ltr(從左到右) 和 rtl(從右到左),當 textDirection = ltr 時,start 表示左側,當 textDirection = rtl 時,start 表示右側,
spaceAround 和 spaceEvenly 區別是:
Container(
decoration: BoxDecoration(border: Border.all(color: Colors.black)),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(
height: 50,
width: 100,
color: Colors.red,
),
Container(
height: 100,
width: 100,
color: Colors.green,
),
Container(
height: 150,
width: 100,
color: Colors.blue,
),
],
),
)
複製代碼
Row與Column是繼承自Flex的,Flex的大部分功能都在上一個線性佈局中介紹過了。咱們這裏在補充幾個知識點。
Flexible 組件能夠控制 Row、Column、Flex 的子控件佔滿父組件,好比,Row 中有3個子組件,兩邊的寬是100,中間的佔滿剩餘的空間
Row(
children: <Widget>[
Container(
color: Colors.blue,
height: 50,
width: 100,
),
Flexible(
child: Container(
color: Colors.red,
height: 50,
)
),
Container(
color: Colors.blue,
height: 50,
width: 100,
),
],
)
複製代碼
仍是有3個子組件,第一個佔1/6,第二個佔2/6,第三個佔3/6,代碼以下
Column(
children: <Widget>[
Flexible(
flex: 1,
child: Container(
color: Colors.blue,
alignment: Alignment.center,
child: Text('1 Flex/ 6 Total',style: TextStyle(color: Colors.white),),
),
),
Flexible(
flex: 2,
child: Container(
color: Colors.red,
alignment: Alignment.center,
child: Text('2 Flex/ 6 Total',style: TextStyle(color: Colors.white),),
),
),
Flexible(
flex: 3,
child: Container(
color: Colors.green,
alignment: Alignment.center,
child: Text('3 Flex/ 6 Total',style: TextStyle(color: Colors.white),),
),
),
],
)
複製代碼
子組件佔比 = 當前子控件 flex / 全部子組件 flex 之和。 Flexible中 fit 參數表示填滿剩餘空間的方式,說明以下:
這2個看上去不是很好理解啊,什麼叫儘量大的填滿剩餘空間?何時填滿,看下面的例子
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Row(
children: <Widget>[
Container(
color: Colors.blue,
height: 50,
width: 100,
),
Flexible(
child: Container(
color: Colors.red,
height: 50,
child: Text('Container',style: TextStyle(color: Colors.white),),
)
),
Container(
color: Colors.blue,
height: 50,
width: 100,
),
],
);
}
}
複製代碼
這段代碼是在最上面代碼的基礎上給中間的紅色Container添加了Text子控件,此時紅色Container就不在充滿空間,再給Container添加對齊方式,代碼以下:
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Row(
children: <Widget>[
Container(
color: Colors.blue,
height: 50,
width: 100,
),
Flexible(
child: Container(
color: Colors.red,
height: 50,
alignment: Alignment.center,
child: Text('Container',style: TextStyle(color: Colors.white),),
)
),
Container(
color: Colors.blue,
height: 50,
width: 100,
),
],
);
}
}
複製代碼
class Expanded extends Flexible {
/// Creates a widget that expands a child of a [Row], [Column], or [Flex]
/// so that the child fills the available space along the flex widget's
/// main axis.
const Expanded({
Key key,
int flex = 1,
@required Widget child,
}) : super(key: key, flex: flex, fit: FlexFit.tight, child: child);
}
複製代碼
Expanded 繼承字 Flexible,fit 參數固定爲 FlexFit.tight,也就是說 Expanded 必須(強制)填滿剩餘空間
Spacer 也是一個權重組件,源代碼以下:
@override
Widget build(BuildContext context) {
return Expanded(
flex: flex,
child: const SizedBox.shrink(),
);
}
複製代碼
Spacer 的本質也是 Expanded 的實現的,和Expanded的區別是:Expanded 能夠設置子控件,而 Spacer 的子控件尺寸是0,所以Spacer適用於撐開 Row、Column、Flex 的子控件的空隙,用法以下
Row(
children: <Widget>[
Container(width: 100,height: 50,color: Colors.green,),
Spacer(flex: 2,),
Container(width: 100,height: 50,color: Colors.blue,),
Spacer(),
Container(width: 100,height: 50,color: Colors.red,),
],
)
複製代碼
三個權重組建總結以下:
Wrap 爲子組件進行水平或者垂直方向佈局,且當空間用完時,Wrap 會自動換行,也就是流式佈局
Wrap(
children: List.generate(10, (i) {
double w = 50.0 + 10 * i;
return Container(
color: Colors.primaries[i],
height: 50,
width: w,
child: Text('$i'),
);
}),
)
複製代碼
咱們查看Wrap源碼,咱們發現了一個runAlignment屬性,感受和alignment好像同樣。
runAlignment 屬性控制 Wrap 的交叉抽方向上每一行的對齊方式,下面直接看 runAlignment 6中方式對應的效果圖
runAlignment 和 alignment 的區別:
spacing 和 runSpacing 屬性控制Wrap主軸方向和交叉軸方向子控件之間的間隙
Wrap(
spacing: 30,
runSpacing: 10,
children: List.generate(10, (i) {
double w = 50.0 + 10 * i;
return Container(
color: Colors.primaries[i],
height: 50,
width: w,
alignment: Alignment.center,
child: Text('$i'),
);
}),
)
複製代碼
疊加布局組件包含 Stack 和 IndexedStack,Stack 組件將子組件疊加顯示,根據子組件的順利依次向上疊加
Stack({
Key key,
this.alignment = AlignmentDirectional.topStart,
this.textDirection,
this.fit = StackFit.loose,
this.overflow = Overflow.clip,
List<Widget> children = const <Widget>[],
})
複製代碼
Stack(
children: <Widget>[
Container(
height: 300,
width: 300,
color: Colors.red,
),
Container(
height: 200,
width: 200,
color: Colors.blue,
),
Container(
height: 100,
width: 100,
color: Colors.yellow,
)
],
);
複製代碼
IndexedStack 是 Stack 的子類,Stack 是將全部的子組件疊加顯示,而 IndexedStack 經過 index 只顯示指定索引的子組件,用法以下:
class IndexedStackDemo extends StatefulWidget {
@override
_IndexedStackDemoState createState() => _IndexedStackDemoState();
}
class _IndexedStackDemoState extends State<IndexedStackDemo> {
int _index = 0;
@override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
SizedBox(height: 50,),
_buildIndexedStack(),
SizedBox(height: 30,),
_buildRow(),
],
);
}
_buildIndexedStack() {
return IndexedStack(
index: _index,
children: <Widget>[
Center(
child: Container(
height: 300,
width: 300,
color: Colors.red,
alignment: Alignment.center,
child: Text('1'),
),
),
Center(
child: Container(
height: 300,
width: 300,
color: Colors.green,
alignment: Alignment.center,
child: Text('2'),
),
),
Center(
child: Container(
height: 300,
width: 300,
color: Colors.yellow,
alignment: Alignment.center,
child: Text('3'),
),
),
],
);
}
_buildRow() {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
RaisedButton(
child: Text('1'),
onPressed: (){
setState(() {
_index = 0;
});
},
),
RaisedButton(
child: Text('2'),
onPressed: (){
setState(() {
_index = 1;
});
},
),
RaisedButton(
child: Text('3'),
onPressed: (){
setState(() {
_index = 2;
});
},
),
],
);
}
}
複製代碼
AspectRatio 是固定寬高比的組件
Container(
height: 300,
width: 300,
color: Colors.blue,
alignment: Alignment.center,
child: AspectRatio(
aspectRatio: 2 / 1,
child: Container(color: Colors.red,),
),
)
複製代碼
FittedBox 組件主要作兩件事,縮放(Scale)和位置調整(Position)。
FittedBox 會在本身的尺寸範圍內縮放並調整 child 的位置,使 child 適合其尺寸。FittedBox 和 Android 中的 ImageView 有些相似,將圖片在其範圍內按照規則進行縮放和位置調整。
佈局分爲兩種狀況:
const FittedBox({
Key key,
this.fit = BoxFit.contain,
this.alignment = Alignment.center,
this.clipBehavior = Clip.hardEdge,
Widget child,
})
複製代碼
這裏有一個新的屬性fit
Container(
color: Colors.amberAccent,
width: 300.0,
height: 300.0,
child: FittedBox(
fit: BoxFit.none,
alignment: Alignment.topLeft,
child: new Container(
color: Colors.red,
child: new Text("FittedBox"),
)
),
);
複製代碼
FractionallySizedBox 是一個相對父組件尺寸的組件,用途是基於寬度縮放因子和高度縮放因子來調整佈局大小,大小可能超過父組件位置。
const FractionallySizedBox({
Key key,
this.alignment = Alignment.center,
this.widthFactor,
this.heightFactor,
Widget child,
})
複製代碼
1.widthFactor:FractionallySizedBox組件的寬度因子
2.heightFractor: FractionallySizedBox組件的高度因子
Container(
height: 200,
width: 200,
color: Colors.blue,
child: FractionallySizedBox(
widthFactor: .8,
heightFactor: .3,
child: Container(
color: Colors.red,
),
),
)
複製代碼
ContainedBox是一種有約束限制的佈局,在其約定的範圍內,好比最大高度,最小寬度,其子組件是不能逾越的
ConstrainedBox({
Key key,
@required this.constraints,
Widget child,
})
複製代碼
constraints:添加到child上的額外限制條件,其類型爲BoxConstraints。BoxConstraints的做用是幹啥的呢?其實很簡單,就是限制各類最大最小寬高。說到這裏插一句,double.infinity在widget佈局的時候是合法的,也就說,例如想最大的擴展寬度,能夠將寬度值設爲double.infinity。
這個案例來自:flutter.cn/docs/develo…
咱們準備作一下這個界面
具體代碼以下
class demoWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
height: 300,
child: Card(
color: Colors.white,
margin: EdgeInsets.fromLTRB(10, 0, 10, 0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
topWidget(),
starWidget(),
iconWidget()],
),
)
);
}
}
class topWidget extends StatelessWidget {
final mainImage = Container(
margin: EdgeInsets.fromLTRB(10, 10, 0, 0),
child: Image.asset(
'images/pavlova.jpg',
fit: BoxFit.fill,
width: 130,
height: 130,
),
);
@override
Widget build(BuildContext context) {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
mainImage,
Flexible(
child: rightWidget()
),
],
);
}
}
class rightWidget extends StatelessWidget {
final titleText = Container(
padding: EdgeInsets.fromLTRB(10, 15, 0, 0),
child: Text(
'Strawberry Pavlova',
style: TextStyle(
letterSpacing: 0.5,
fontSize: 17,
),
),
);
final subTitle = Container(
padding: EdgeInsets.fromLTRB(10, 10, 10, 10),
child: Text(
'Pavlova is a meringue-based dessert named after the Russian ballerina '
'Anna Pavlova. Pavlova features a crisp crust and soft, light inside, '
'topped with fruit and whipped cream.',
style: TextStyle(
fontSize: 12,
),
),
);
@override
Widget build(BuildContext context) {
return Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [titleText,subTitle],
),
);
}
}
class starWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.fromLTRB(10, 10, 10, 10),
child: Row(
children: [
Icon(Icons.star, color: Colors.green[500]),
Icon(Icons.star, color: Colors.green[500]),
Icon(Icons.star, color: Colors.green[500]),
Icon(Icons.star, color: Colors.black),
Icon(Icons.star, color: Colors.black),
],
),
);
}
}
class iconWidget extends StatelessWidget {
final descTextStyle = TextStyle(
color: Colors.black,
fontSize: 18,
height: 1.2,
);
@override
Widget build(BuildContext context) {
return Container(
child: Container(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Column(
children: [
Icon(Icons.kitchen, color: Colors.green[500]),
Text('PREP:',style: descTextStyle),
Text('25 min',style: descTextStyle),
],
),
Column(
children: [
Icon(Icons.timer, color: Colors.green[500]),
Text('COOK:',style: descTextStyle),
Text('1 hr',style: descTextStyle),
],
),
Column(
children: [
Icon(Icons.restaurant, color: Colors.green[500]),
Text('FEEDS:',style: descTextStyle),
Text('4-6',style: descTextStyle),
],
),
],
),
),
);
}
}
複製代碼