緊接上一篇的有側邊欄APP,此次咱們向APP中加入上下Tab頁,使之跟趨近主流大部分APP的信息佈局套路,等不及看源碼的同窗能夠點擊進入個人git倉庫下載代碼。html
Tab
頁的控制器,用於定義Tab
標籤和內容頁的座標,還可配置標籤頁的切換動畫效果等。TabController通常放入有狀態控件中使用,以適應標籤頁數量和內容有動態變化的場景,若是標籤頁在APP中是靜態固定的格局,則能夠在無狀態控件中加入簡易版的DefaultTabController以提升運行效率,畢竟無狀態控件要比有狀態控件更省資源,運行效率更快。android
TabBar Tab
頁的Title
控件,切換Tab
頁的入口,通常放到AppBar
控件下使用,內部有**Title*屬性。其子元素按水平橫向排列布局,若是須要縱向排列,請使用Column
或ListView
控件包裝一下。子元素爲Tab
類型的數組。git
TabBarView Tab
頁的內容容器,其內放置Tab
頁的主體內容。子元素能夠是多個各類類型的控件。github
TabController
Tab
頁的切換搭配了動畫,所以到State
類上附加一個SingleTickerProviderStateMixin
:數組
//定義有狀態控件
class HomePage extends StatefulWidget {
@override
_HomePageState createState() => new _HomePageState();
}
//用於使用到了一點點的動畫效果,所以加入了SingleTickerProviderStateMixin
class _HomePageState extends State<HomePage> with SingleTickerProviderStateMixin{
...
}
複製代碼
而後到有狀態控件的子類State
中初始化控制器TabController
:app
@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
安放到須要的地方去,好比:less
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填充成下面這個樣式:ide
如上圖所示,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,並不是常任性的把項目目錄下的_android\app\src\main\AndroidManifest.xml_文件,把package和android:label都改爲了APPname2,因而不出意料的悲催了,命令flutter fun**報錯,沒法啓動APP,還原配置以後,沒法啓動APP,即使嘗試經過全文搜索APPname1,都按規定格式替換成APPname2,或者逆向改回去,都沒法啓動APP,此時已經是凌晨1點。。。妥妥的血淚史,因此鄭重的告誡你們:
不要在項目的各類配置文件中輕易改動項目名稱!不要在項目的各類配置文件中輕易改動項目名稱!不要在項目的各類配置文件中輕易改動項目名稱! 不然你就是下一個在電腦面前捶胸頓足的魚丸。什麼?問我怎麼恢復的?固然是託git的福。
感謝你們的支持,請關注個人Flutter圈子,多多投稿,也能夠加入**flutter 中文社區(官方QQ羣:338252156)**共同成長,謝謝你們~