佈局
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,實現的途徑有很多中。本篇就重點涉及幾個常用的部件。