緊接上一篇的有側邊欄APP,此次咱們向APP中加入上下Tab頁,使之跟趨近主流大部分APP的信息佈局套路,等不及看源碼的同窗能夠點擊進入個人git倉庫下載代碼。html
這是Tab
頁的控制器,用於定義Tab
標籤和內容頁的座標,還可配置標籤頁的切換動畫效果等。android
TabController通常放入有狀態控件中使用,以適應標籤頁數量和內容有動態變化的場景,若是標籤頁在APP中是靜態固定的格局,則能夠在無狀態控件中加入簡易版的 DefaultTabController以提升運行效率,畢竟無狀態控件要比有狀態控件更省資源,運行效率更快。
Tab
頁的Title
控件,切換Tab
頁的入口,通常放到AppBar
控件下使用,內部有*Title屬性。其子元素按水平橫向排列布局,若是須要縱向排列,請使用Column
或ListView
控件包裝一下。子元素爲Tab
類型的數組。git
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文件定義的控件頁面Tab
標籤頁Tab
頁的通用屬性能夠提早定義到數組List
中,在TabBar
和TabBarView
經過遍歷提取List
的值建立子元素能夠提升代碼的維護效率://在`StatefulWidget`控件中,可經過修改下面的數組,實現Tab頁的動態加載 final List<Tab> myTabs = <Tab>[ 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)共同成長,謝謝你們~