Flutter佈局
Alignment
Container類裡有一個alignment屬性,翻譯過來應該叫對齊方式,這個屬性用來控制Container的子控制元件相對於它自身的一個位置。在我們iOS開發中,我們知道座標系的原點是在左上角。而在flutter中,座標系的原點在父控制元件的正中心,可以使用這個alignment屬性來控制子控制元件在父控制元件中的位置,它有兩個引數分別是double型別的x,y。取值是-1到1,當0,0的時候表示子控制元件在父控制元件的正中心;當1,0的時候,表示子控制元件位於x方向上的最右側,y方向上居中;當-1,-1的時候,表示子控制元件位於父控制元件的左上角位置。有點類似於CALayer的anchorPoint屬性。如圖程式碼如下:
Row
Row表示水平佈局,它有一個children屬性,用來存放它的子控制元件。程式碼如下:
最開始我們嘗試了alignment屬性的作用,當它是0,0的時候,Text的位置預設是在螢幕中央的。為什麼這裡換成我們的Row之後,Row的子控制元件位置不在螢幕中央呢?
mainAxisAlignment
Row 和 Column 都有一個mainAxisAlignment屬性,叫作主軸對齊方式,預設是MainAxisAlignment.start意思沿著主軸方向開始,Row佈局下就是從左至右,Column佈局下就是從上至下。
MainAxisAlignment.spaceAround
:將剩下的空間平均分配
MainAxisAlignment.spaceBetween
:將剩下的空間分配到子控制元件之間
MainAxisAlignment.spaceEvenly
:等間距分配子控制元件
crossAxisAlignment
交叉軸對齊方式,start,end,center這幾種方式試一下很好理解,stretch會將子控制元件拉伸。而baseline用的比較少,單獨使用它會報錯,需要和Text文字結合,還需要配合textBaseline
屬性一起使用。如下圖所示,如果不設定CrossAxisAlignment.baseline
和TextBaseline.alphabetic
就會根據控制元件高度水平對齊,而如果設定了就會根據控制元件內文字的基線對齊。
Column
這個和Row是對應的,Row是水平佈局,這個Column是垂直佈局
class LayoutDemo extends StatelessWidget {
const LayoutDemo({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
color: Colors.green,
alignment: Alignment(0, 0),
child: Column(
children: [
Container(
child: Icon(Icons.add, size: 180,),
color: Colors.red,
),
Container(
child: Icon(Icons.ac_unit, size: 120,),
color: Colors.yellow,
),
Container(
child: Icon(Icons.access_alarm, size: 60,),
color: Colors.blue,
),
],
),
);
}
}
複製程式碼
顯示效果如圖:
mainAxisAlignment
這個跟Row類似
crossAxisAlignment
這個跟Row類似
Stack
這個是用在Z軸上的佈局的,row是X軸,column是Y軸。children陣列第一個放在最底部,最後一個放在上面,離使用者最近的地方。
class LayoutDemo extends StatelessWidget {
const LayoutDemo({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
color: Colors.green,
alignment: Alignment(0, 0),
child: Stack(
children: [
Container(
child: Icon(Icons.add, size: 180,),
color: Colors.red,
),
Container(
child: Icon(Icons.ac_unit, size: 120,),
color: Colors.yellow,
),
Container(
child: Icon(Icons.access_alarm, size: 60,),
color: Colors.blue,
),
],
),
);
}
}
複製程式碼
APP顯示效果:
alignment
Stack裡有一個alignment屬性,它用來控制所有子控制元件相對於最大那個子控制元件的位置
class StackDemo extends StatelessWidget {
const StackDemo({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Stack(
alignment: Alignment.bottomRight,
children: [
Container(
child: Icon(Icons.add, size: 180,),
color: Colors.red,
),
Container(
child: Icon(Icons.ac_unit, size: 120,),
color: Colors.yellow,
),
Container(
child: Icon(Icons.access_alarm, size: 60,),
color: Colors.blue,
),
],
);
}
}
複製程式碼
Positioned
Stack裡配合Positioned類使用的話,跟我們iOS的約束有點類似了,可以設定上,左間距之類的
class StackDemo extends StatelessWidget {
const StackDemo({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Stack(
children: [
Positioned(
child: Container(
child: Icon(
Icons.add,
size: 180,
),
color: Colors.red,
),
),
Positioned(
child: Container(
child: Icon(
Icons.ac_unit,
size: 120,
),
color: Colors.yellow,
),
),
Positioned(
top: 20,
left: 20,
right: 20,
child: Container(
child: Icon(
Icons.access_alarm,
size: 60,
),
color: Colors.blue,
),
),
],
);
}
}
複製程式碼
Expanded
Expanded是一個類似Container的常用的佈局容器,它用來填充佈局,使用了填充佈局在主軸方向上是不會有間隔的,所以Expanded用在Row裡面的時候,子控制元件的寬度設定就沒有意義了,而在Column裡面使用的使用,子控制元件的高度設定就沒有意義了。這裡以Column為例:
class LayoutDemo extends StatelessWidget {
const LayoutDemo({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
color: Colors.green,
alignment: Alignment(0, 0),
child: Column(
children: [
Expanded(
child: Container(
child: Icon(Icons.add, size: 180,),
color: Colors.red,
),
),
Expanded(
child: Container(
child: Icon(Icons.ac_unit, size: 120,),
color: Colors.yellow,
),
),
Expanded(
child: Container(
child: Icon(Icons.access_alarm, size: 60,),
color: Colors.blue,
),
),
],
),
);
}
}
複製程式碼
AspectRatio
AspectRatio是一個容器類,它有一個屬性aspectRatio表示寬高比。如果指定了寬度,根據這個aspectRatio可以自動算出高度;如果指定了高度,根據aspectRatio可以自動算出寬度。如下面程式碼指定了父檢視高度為100,aspectRatio寬高比為2,子檢視寬度就是200,再把父檢視撐起來也是200。
class LayoutDemo extends StatelessWidget {
const LayoutDemo({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
color: Colors.green,
alignment: Alignment(0, 0),
child: Container(
color: Colors.blue,
height: 100,
child: AspectRatio(
aspectRatio: 2,
child: Icon(
Icons.add,
),
),
));
}
}
複製程式碼
Flutter狀態管理
之前介紹的這麼多類都是無狀態的,意思是顯示之後沒辦法更新UI的,如果想要實時更新UI的話,就不能繼承無狀態的類了。我們先來看一個例子:明明count變化了,但是介面顯示沒有變化 記得修改APP的home檢視 然後點選螢幕右下角的加號按鈕,可以發現明明控制檯列印了count的值已經發生了變化,但是介面顯示依然是0 下面我們解決這個問題,將StateManagerDemo繼承改為StatefulWidget,實現createState方法返回一個自定義的State物件,自定義的State物件裡面實現build方法。還需要注意在按鈕的點選方法裡呼叫一下setState方法。這樣每次點選加號按鈕就能實時更新UI了。改造完之後如下圖所示:
專案搭建之底部TabBar
到目前為止,我們對flutter的一些基礎知識就算是介紹的差不多了。接下來我們開始做一個簡單的仿微信APP。我們應該都有經驗,理論的知識學得再多,不動手開始敲程式碼,不在專案中運用,是很難真正掌握一門知識的。
新建一個flutter工程,命名wechat_demo:
刪掉多餘的程式碼,可以全部重新自己寫:
建立底部的TabBar和item,預設的type是白色的,顯示效果很難看所以改為fixed,還可以設定fixedColor:
BottomNavigationBar有一個屬性currentIndex即代表了當前選中的下標。我們可以通過設定它的值來控制哪個按鈕被選中。既然需要改變UI了,說明我們需要將StatelessWidget改為StatefulWidget了。還有一個引數onTap是用來回撥點選事件的。實現點選事件,切換currentIndex,重新setState就可以實現,點選切換了。我們將bottomNavigationBar相關程式碼放到一個新的檔案rootPage中。程式碼如下: 記得修改main.dart檔案中 這樣就實現了APP的底部TabBar的展示,點選功能。點選每個item的時候,會發現flutter的bottomNavigationBar還自帶了動畫效果...
我們知道Scaffold還有一個body的屬性,表示展示在螢幕上的內容。我們每個item對應的介面都需要一個AppBar,那麼也許意味著,body屬性還需要一個Scaffold來展示我們的每個item對應的內容。
可以看到微信首頁就已經大概出來了,但是點選的時候只會顯示這個微信頁面,怎麼實現切換不同的頁面呢,肯定需要一個陣列,來存放對應的每個頁面了。
然後body裡,根據我們的_currentIndex返回對應的body
這樣點選每個item都會跳轉到對應的介面了,APP的主框架的搭建好了。
總結
今天主要講了flutter的三大布局類Row,Column,Stack以及他們的一些屬性。然後是有狀態的Widget和無狀態的Widget,最後搭建了一下我們要做的仿微信APP的底部bottomNavigationBar和切換頁面功能