緊接上一篇的有側邊欄APP,這次我們向APP中加入上下Tab頁,使之跟趨近主流大部分APP的資訊佈局套路,等不及看原始碼的同學可以點選進入我的git倉庫下載程式碼。
Tab關鍵元素
- TabController
這是Tab
頁的控制器,用於定義Tab
標籤和內容頁的座標,還可配置標籤頁的切換動畫效果等。
TabController一般放入有狀態控制元件中使用,以適應標籤頁數量和內容有動態變化的場景,如果標籤頁在APP中是靜態固定的格局,則可以在無狀態控制元件中加入簡易版的DefaultTabController以提高執行效率,畢竟無狀態控制元件要比有狀態控制元件更省資源,執行效率更快。
-
TabBar
Tab
頁的Title
控制元件,切換Tab
頁的入口,一般放到AppBar
控制元件下使用,內部有**Title*屬性。其子元素按水平橫向排列布局,如果需要縱向排列,請使用Column
或ListView
控制元件包裝一下。子元素為Tab
型別的陣列。 -
TabBarView
Tab
頁的內容容器,其內放置Tab
頁的主體內容。子元素可以是多個各種型別的控制元件。
Tab使用方法
有狀態控制元件搭配TabController
Tab
頁的切換搭配了動畫,因此到State
類上附加一個SingleTickerProviderStateMixin
:
//定義有狀態控制元件
class HomePage extends StatefulWidget {
@override
_HomePageState createState() => new _HomePageState();
}
//用於使用到了一點點的動畫效果,因此加入了SingleTickerProviderStateMixin
class _HomePageState extends State<HomePage> with SingleTickerProviderStateMixin{
...
}
複製程式碼
然後到有狀態控制元件的子類State
中初始化控制器TabController
:
@override
void initState() {
super.initState();
_tabController = new TabController(
vsync: this, //動畫效果的非同步處理,預設格式,背下來即可
length: 3 //需要控制的Tab頁數量
);
}
//當整個頁面dispose時,記得把控制器也dispose掉,釋放記憶體
@override
void dispose() {
_tabController .dispose();
super.dispose();
}
複製程式碼
然後到TabBar
和TabBarView
中的controller屬性中呼叫控制器_tabController
//標籤頁標題
new TabBar(
tabs: [ //注意TabBar的子元素為Tab型別的陣列
new Tab(icon: new Icon(Icons.directions_car)),
new Tab(icon: new Icon(Icons.directions_transit)),
new Tab(icon: new Icon(Icons.directions_bike)),
]
//標籤頁內容區域
new TabBarView(
children: [
new Icon(Icons.directions_car),
new Icon(Icons.directions_transit),
new Icon(Icons.directions_bike),
]
複製程式碼
最後,我們把定義好的TabBar
和TabBarView
安放到需要的地方去,比如:
new Scaffold(
appBar: new AppBar(
backgroundColor: Colors.deepOrange,
title: new Text(`title`),
),
....
body: new TabBarView( //TabBarView呈現內容,因此放到Scaffold的body中
controller: _bottomNavigation, //配置控制器
children: [ //注意順序與TabBar保持一直
new News(data: `引數值`), //上一篇定義好的子頁面
new TabPage2(),
new TabPage3(),
]
),
bottomNavigationBar: new Material( //為了適配主題風格,包一層Material實現風格套用
color: Colors.deepOrange, //底部導航欄主題顏色
child: new TabBar( //TabBar導航標籤,底部導航放到Scaffold的bottomNavigationBar中
controller: _bottomNavigation, //配置控制器
tabs: _bottomTabs,
indicatorColor: Colors.white, //tab標籤的下劃線顏色
),
)
);
複製程式碼
無狀態控制元件搭配DefaultTabController
DefaultTabController
要簡單很多,由於應用在無狀態控制元件中,使用DefaultTabController
包裹需要用到Tab
的頁面即可:
class TabPage3 extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new DefaultTabController(
length: 3,
child: new Scaffold(
appBar: new AppBar(
backgroundColor: Colors.orangeAccent,
title: new TabBar(
tabs: [
new Tab(icon: new Icon(Icons.directions_car)),
new Tab(icon: new Icon(Icons.directions_transit)),
new Tab(icon: new Icon(Icons.directions_bike)),
],
indicatorColor: Colors.white,
),
),
body: new TabBarView(
children: [
new Icon(Icons.directions_car),
new Icon(Icons.directions_transit),
new Icon(Icons.directions_bike),
],
),
),
);
}
}
複製程式碼
DefaultTabController
和TabController
的用法差異主要在控制器的定義上,TabBar
和TabBarView
的使用方法完全相同,通常上下Tab
頁的標籤分別安放在Scaffold
控制元件的appBar和bottomNavigationBar屬性上,然後我們把APP填充成下面這個樣式:
程式碼結構
如上圖所示,APP以底部Tab
導航欄為主入口,底部每個Tab
中又各自有自己的頂部次級Tab
頁,因此我們把程式碼結構整理一下:
-
_HomePageState
是APP的主頁面HomePage
的子類State
-
通過
Scaffold
底部的bottomNavigationBar屬性擺放TabBar
,搭建Tab
頁的標籤欄 -
放入
Scaffold
的body屬性放入TabBarView
,TabBarView
的children是三個外部dart檔案定義的控制元件頁面 -
外部dart檔案定義的控制元件頁面分別又有各自風格的
Tab
標籤頁 -
Tab
頁的通用屬性可以提前定義到陣列List
中,在TabBar
和TabBarView
通過遍歷提取List
的值建立子元素可以提高程式碼的維護效率://在
StatefulWidget
控制元件中,可通過修改下面的陣列,實現Tab頁的動態載入
final List myTabs = [
new Tab(text: `Tab1`),
new Tab(text: `Tab2`),
new Tab(text: `Tab3`),
new Tab(text: `Tab4`),
new Tab(text: `Tab5`),
new Tab(text: `Tab6`),
new Tab(text: `Tab7`),
new Tab(text: `Tab8`),
new Tab(text: `Tab9`),
new Tab(text: `Tab10`),
new Tab(text: `Tab11`),
];Widget build(BuildContext context) { return new Scaffold( appBar: new AppBar( backgroundColor: Colors.orangeAccent, title: new TabBar( controller: _tabController, tabs: myTabs, //使用Tab型別的陣列呈現Tab標籤 indicatorColor: Colors.white, isScrollable: true, ), ), body: new TabBarView( controller: _tabController, children: myTabs.map((Tab tab) { //遍歷List<Tab>型別的物件myTabs並提取其屬性值作為子控制元件的內容 return new Center(child: new Text(tab.text+` `+widget.data)); //使用引數值 }).toList(), ), ); } 複製程式碼
使用獲取到的引數
由於StatelessWidget
和StatefulWidget
的頁面構建不同,使用從外部獲取到的引數的方式也略有差異,在這裡簡單總結下。
-
StatelessWidget
的獲參和用參方式
定義StatelessWidget
控制元件時,新增一個final
型別的變數如pageText
,用於為引數值預留空間,並在建構函式中加入引數值:class SidebarPage extends StatelessWidget {
final String pageText; //定義一個常量,用於儲存跳轉進來獲取到的引數
SidebarPage(this.pageText); //建構函式,獲取引數
…
}
使用引數時直接引用即可:
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(title: new Text(pageText),), //將引數當作頁面標題
body: new Center(
child: new Text(`pageText`),
),
);
}
複製程式碼
從外部傳入引數時,直接向建構函式中填入引數值即可:
Navigator.of(context).push(new MaterialPageRoute(builder:
(BuildContext context) => new SidebarPage(`First Page`))); //在new方法時呼叫控制元件的建構函式傳入引數值
複製程式碼
-
StatefulWidget
的獲參和用參方式
相比StatelessWidget
略複雜。定義建構函式時需要預設宣告key:class TabPage1 extends StatefulWidget {
const TabPage1({ Key key , this.data}) : super(key: key); //建構函式中增加引數
final String data; //為引數分配空間
@override
_MyTabbedPageState createState() => new _MyTabbedPageState();
}
使用時,由於在State
子類中實現具體的頁面內容,因此State
子類使用父類TabPage1
的引數時需要在引數名前增加一個***widget***關鍵字:
class _MyTabbedPageState extends State<TabPage1> {
...
new Center(child: new Text(tab.text+` `+widget.data)); //使用引數值,需在引數名前增加widget字首
...
}
複製程式碼
從外部傳入引數時,需要宣告引數名:
new TabBarView
controller: _bottomNavigation,
children: [
new TabPage1(data: `引數值`), //new方法呼叫建構函式時,還需要宣告引數名稱
new TabPage2(),
new TabPage3(),
]
)
複製程式碼
好勒,今天就講到這裡,大家去下載我的git原始碼試試效果吧,程式碼中有附加的註釋,對一些控制元件屬性的特性也有單獨的描述,相信看完原始碼之後,大家也可以自行實現效果了。
順便分享一個雷區,由於當初建立這個專案時,我使用命令**flutter create [APPname1]的方式建立了這個專案,但我發現這個APPname1(代稱,並非真實名稱)不好聽,想把專案改名為APPname2,於是參考之前寫的安卓怎麼打包?,把專案資料夾改名為APPname2,並非常任性的把專案目錄下的_androidappsrcmainAndroidManifest.xml_檔案,把package和android:label都改成了APPname2,於是不出意料的悲催了,命令flutter fun**報錯,無法啟動APP,還原配置之後,無法啟動APP,即便嘗試通過全文搜尋APPname1,都按規定格式替換成APPname2,或者逆向改回去,都無法啟動APP,此時已是凌晨1點。。。妥妥的血淚史,所以鄭重的告誡大家:
不要在專案的各種配置檔案中輕易改動專案名稱!不要在專案的各種配置檔案中輕易改動專案名稱!不要在專案的各種配置檔案中輕易改動專案名稱! 否則你就是下一個在電腦面前捶胸頓足的魚丸。什麼?問我怎麼恢復的?當然是託git的福。
感謝大家的支援,請關注我的Flutter圈子,多多投稿,也可以加入**flutter 中文社群(官方QQ群:338252156)**共同成長,謝謝大家~