上一篇中我記錄了Flutter中經常使用的一些佈局,本篇開始開發基於Flutter的開源中國客戶端了。在本篇博客中,要實現的是一個App的總體框架,包括頁面底部的Tab導航菜單、頁面的側滑菜單以及跳轉到新的頁面這幾個功能。但願本身在記錄的同時能溫故知新,同時給初學者一些幫助。git
在咱們平常生活中常用的App好比微信、微博、QQ等,基本上都是使用首頁底部多個Tab可切換頁面,加上可側滑的菜單這種佈局方式來組合。基於Flutter的開源中國客戶端也是使用這種佈局組合來實現的App。本篇要實現的頁面效果以下圖所示:github
下面一步步來完成這個佈局框架的搭建。數組
在AndroidStudio中,經過File
-> New
-> New Flutter Project...
建立一個新的Flutter工程。微信
在新建立的Flutter工程中,刪除lib/main.dart
中的代碼,並編寫下面的代碼:app
import 'package:flutter/material.dart'; void main() { runApp(new MyApp()); } // MyApp是一個有狀態的組件,由於頁面標題,頁面內容和頁面底部Tab都會改變 class MyApp extends StatefulWidget { @override State<StatefulWidget> createState() => new MyOSCClientState(); } class MyOSCClientState extends State<MyApp> { @override Widget build(BuildContext context) { return new MaterialApp( theme: new ThemeData( // 設置頁面的主題色 primaryColor: const Color(0xFF63CA6C) ), home: new Scaffold( appBar: new AppBar( // 設置AppBar標題 title: new Text("My OSC", // 設置AppBar上文本的樣式 style: new TextStyle(color: Colors.white) ), // 設置AppBar上圖標的樣式 iconTheme: new IconThemeData(color: Colors.white) ), body: new Text("MyOSC Client") ), ); } }
上面的代碼中,爲MaterialApp設置了theme
參數,主要是爲了改變頁面主題顏色爲綠色,在Scaffold的appBar
屬性中,爲title
設置了顏色爲白色,若是不設置的話,默認爲黑色,appBar
的iconTheme
屬性也設置爲了白色主題,若是不設置的話,AppBar上的圖標默認爲黑色。框架
在新建的Flutter項目的lib/
目錄下,新建一個pages/
目錄,該目錄用於存放App中的全部頁面,而後分別建立四個.dart文件:NewsListPage.dart
TweetsListPage.dart
DiscoveryPage.dart
MyInfoPage.dart
,表明App中首頁底部4個Tab切換時分別顯示的頁面,這四個頁面暫時就在頁面正中間顯示一行文本,下面是資訊列表NewsListPage.dart
代碼:less
// pages/NewsListPage.dart import 'package:flutter/material.dart'; // 資訊列表頁面 class NewsListPage extends StatelessWidget { @override Widget build(BuildContext context) { return new Center( child: new Text("NewsListPage"), ); } }
其他三個頁面代碼跟上面的相似,只是換了類名和Text組件的文本。ide
在上一步中,咱們的Scaffold組件裏的body屬性只是一個Text組件,爲了加載上面的4個頁面,須要用一個容器組件將這4個頁面裝起來,而後在點擊Tab時切換頁面,這就用到了我以前的博文裏說到的IndexedStack組件了。IndexedStack中能夠有多個子組件,根據索引值來顯示其中某個組件而隱藏其他的組件。佈局
在第一步中MyOSCClientState
類中定義兩個變量:_tabIndex
和_body
,_tabIndex
表示當前頁面底部選中的Tab的索引,_body
表示首頁Scaffold組件的body屬性值,而後給_tabIndex
和_body
變量賦值,以下代碼所示:post
// 頁面當前選中的Tab的索引 int _tabIndex = 0; // 頁面body部分組件 var _body = new IndexedStack( children: <Widget>[ new NewsListPage(), new TweetsListPage(), new DiscoveryPage(), new MyInfoPage() ], index: _tabIndex, );
上面用IndexedStack加載了4個頁面用於切換Tab時顯示,可是Tab咱們尚未作出來,Flutter中爲頁面添加底部導航Tab菜單很簡單,已經有不少組件能夠用了。
給頁面添加底部導航Tab菜單隻須要給Scaffold組件添加一個bottomNavigationBar
屬性便可,這裏的bottomNavigationBar
咱們用Flutter提供的CupertinoTabBar組件。
CupertinoTabBar是Flutter內置的iOS風格的選項卡,用於在頁面底部顯示幾個Tab,要使用Cupertino風格的組件,必須先導入頭文件,以下代碼:
import 'package:flutter/cupertino.dart';
CupertinoTabBar組件的用法也比較簡單,代碼以下:
new CupertinoTabBar( items: getBottomNavItems(), currentIndex: _tabIndex, onTap: (index) { // 底部TabItem的點擊事件處理,點擊時改變當前選擇的Tab的索引值,則頁面會自動刷新 setState((){ _tabIndex = index; }); }, )
其中items
是一個List<BottomNavigationBarItem>
對象,currentIndex
表示當前選中的Tab的索引值,onTap
是TabItem點擊事件,上面的代碼中,getBottomNavItems()
方法代碼以下:
List<BottomNavigationBarItem> getBottomNavItems() { List<BottomNavigationBarItem> list = new List(); for (int i = 0; i < 4; i++) { list.add(new BottomNavigationBarItem( icon: getTabIcon(i), title: getTabTitle(i) )); } return list; } // 根據索引值肯定Tab是選中狀態的樣式仍是非選中狀態的樣式 TextStyle getTabTextStyle(int curIndex) { if (curIndex == _tabIndex) { return tabTextStyleSelected; } return tabTextStyleNormal; } // 根據索引值肯定TabItem的icon是選中仍是非選中 Image getTabIcon(int curIndex) { if (curIndex == _tabIndex) { return tabImages[curIndex][1]; } return tabImages[curIndex][0]; } // 根據索引值返回頁面頂部標題 Text getTabTitle(int curIndex) { return new Text( appBarTitles[curIndex], style: getTabTextStyle(curIndex) ); }
因爲TabItem是由一個圖標和一個文本組件構成,因此這裏還須要在MyOSCClientState類中定義兩個變量tabImages
和appBarTitles
。tabImages
是一個二維數組,表示TabItem中的圖標(包括選中和未選中狀態的圖標),appBarTitles
是一個字符串數組,表示每一個TabItem對應的頁面標題,這兩個變量的賦值代碼以下:
// 頁面底部TabItem上的圖標數組 var tabImages; // 頁面頂部的大標題(也是TabItem上的文本) var appBarTitles = ['資訊', '動彈', '發現', '個人']; // 數據初始化,包括TabIcon數據和頁面內容數據 void initData() { if (tabImages == null) { tabImages = [ [ getTabImage('images/ic_nav_news_normal.png'), getTabImage('images/ic_nav_news_actived.png') ], [ getTabImage('images/ic_nav_tweet_normal.png'), getTabImage('images/ic_nav_tweet_actived.png') ], [ getTabImage('images/ic_nav_discover_normal.png'), getTabImage('images/ic_nav_discover_actived.png') ], [ getTabImage('images/ic_nav_my_normal.png'), getTabImage('images/ic_nav_my_pressed.png') ] ]; } } // 傳入圖片路徑,返回一個Image組件 Image getTabImage(path) { return new Image.asset(path, width: 20.0, height: 20.0); }
上面的代碼中須要注意的是Image組件,要使用image/
目錄下的圖片,必須確保項目根目錄下的pubspec.yaml文件中已經添加了圖片的路徑,以下圖:
若是沒有上面的assets配置,直接加載圖片是會報錯的。
爲了達到點擊Tab切換不一樣的頁面的功能,咱們須要給CupertinoTabBar組件的onTap參數配置一個方法,該方法有一個index參數,咱們將這個index賦值給前面定義的_tabIndex
便可,並將這個賦值操做放到setState中執行,以下代碼:
onTap: (index) { // 底部TabItem的點擊事件處理,點擊時改變當前選擇的Tab的索引值,則頁面會自動刷新 setState((){ _tabIndex = index; }); },
最後放上MyOSCClientState類的build方法代碼:
@override Widget build(BuildContext context) { initData(); return new MaterialApp( theme: new ThemeData( // 設置頁面的主題色 primaryColor: const Color(0xFF63CA6C) ), home: new Scaffold( appBar: new AppBar( // 設置AppBar標題 title: new Text(appBarTitles[_tabIndex], // 設置AppBar上文本的樣式 style: new TextStyle(color: Colors.white)), // 設置AppBar上圖標的樣式 iconTheme: new IconThemeData(color: Colors.white) ), body: _body, // bottomNavigationBar屬性爲頁面底部添加導航的Tab,CupertinoTabBar是Flutter提供的一個iOS風格的底部導航欄組件 bottomNavigationBar: new CupertinoTabBar( items: getBottomNavItems(), currentIndex: _tabIndex, onTap: (index) { // 底部TabItem的點擊事件處理,點擊時改變當前選擇的Tab的索引值,則頁面會自動刷新 setState((){ _tabIndex = index; }); }, ) ), ); }
在上面的代碼中,body
屬性是_body
變量,而_body
變量是個IndexedStack對象,IndexedStack對象的index
值是_tabIndex
,因此當咱們在setState中改變了_tabIndex
後,IndexedStack就會自動切換顯示子組件了,也就達到了切換頁面的目的。
上面的代碼運行在模擬器中以下圖所示:
側滑菜單在Flutter中已有相關組件,因此爲首頁加上側滑菜單的方法很簡單:給Scaffold組件傳個drawer
參數便可,代碼以下:
new Scaffold( appBar: new AppBar( // 設置AppBar標題 title: new Text(appBarTitles[_tabIndex], // 設置AppBar上文本的樣式 style: new TextStyle(color: Colors.white)), // 設置AppBar上圖標的樣式 iconTheme: new IconThemeData(color: Colors.white) ), body: _body, // bottomNavigationBar屬性爲頁面底部添加導航的Tab,CupertinoTabBar是Flutter提供的一個iOS風格的底部導航欄組件 bottomNavigationBar: new CupertinoTabBar( items: getBottomNavItems(), currentIndex: _tabIndex, onTap: (index) { // 底部TabItem的點擊事件處理,點擊時改變當前選擇的Tab的索引值,則頁面會自動刷新 setState((){ _tabIndex = index; }); }, ), // drawer屬性用於爲當前頁面添加一個側滑菜單 drawer: new Drawer( child: new Center( child: new Text("this is a drawer") ), ), )
有了drawer以後的app運行效果以下圖:
在Flutter中實現頁面的跳轉很是簡單,使用Navigator的相關API便可,下面咱們改造一下NewsListPage頁面,在頁面中間加入一個按鈕,點擊按鈕跳轉到詳情頁。
首先咱們在pages/
目錄下新建一個NewsDetailPage表明資訊詳情頁,並添加以下代碼:
import 'package:flutter/material.dart'; class NewsDetailPage extends StatelessWidget { @override Widget build(BuildContext context) { return new Scaffold( appBar: new AppBar( title: new Text("資訊詳情", style: new TextStyle(color: Colors.white)), iconTheme: new IconThemeData(color: Colors.white) ), body: new Center( child: new Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ new Text("News Detail Page."), new RaisedButton( child: new Text("Back"), onPressed: () { Navigator.of(context).pop(); }, ) ], ) ), ); } }
上面的代碼中有以下幾點須要注意:
build
方法中咱們返回的是一個Scaffold組件,而不是像main.dart中那樣返回一個MaterialApp組件,這是由於咱們在使用Navigator從資訊列表頁跳轉到詳情頁時,會自動爲詳情頁的AppBar左邊添加返回按鈕,若是你在詳情頁仍是使用MaterialApp對象,則頁面左上角不會自動添加返回按鈕。mainAxisAlignment: MainAxisAlignment.center
,則頁面上的組件只會在水平方向居中而不會在垂直方向上居中。Navigator.of(context).pop()
來使頁面返回到上一級。下面須要修改NewsListPage的代碼,加入按鈕並完成跳轉到詳情頁的邏輯,代碼以下:
import 'package:flutter/material.dart'; import 'NewsDetailPage.dart'; // 資訊列表頁面 class NewsListPage extends StatelessWidget { @override Widget build(BuildContext context) { return new Center( child: new RaisedButton( child: new Text("to detail page"), onPressed: () { Navigator.of(context).push(new MaterialPageRoute(builder: (ctx) { return new NewsDetailPage(); })); } ) ); } }
使用Navigator.of(context).push()
方法來完成頁面的跳轉,push的參數是一個Route對象,這裏使用了Flutter提供的MaterialPageRoute對象,builder參數是一個方法,返回的就是詳情頁對象。
除了使用上面的方式作頁面的跳轉外,還能夠給MaterialApp配置一個route參數,該route參數相似於一個全局的路由表,根據一個name值導航到對應的頁面,這種方式須要定義一個類型爲Map<String, WidgetBuilder>
的變量_route
變量,並在initDate()方法中爲這個變量賦值,以下代碼:
_routes['newsDetail'] = (BuildContext) { return new NewsDetailPage(); };
在須要跳轉頁面的時候,調用以下方法完成頁面跳轉:
Navigator.of(context).pushNamed("newsDetail");
若是在頁面跳轉時須要給下一個頁面傳值,能夠在下一個頁面的構造方法中接收傳入的值,而後在Navigator調用push方法的時候new下一個頁面時,在組件的構造方法中設置傳入的值,具體用法能夠參考這裏
本篇相關的全部源碼都在GitHub上demo-flutter-osc項目的v0.1分支。
本篇主要記錄的是基於Flutter的開源中國客戶端總體佈局框架的搭建過程,經過使用Flutter內置的各類Widget,能夠很容易的實現這個佈局框架,下一篇準備記錄的是基於Flutter的開源中國客戶端各個靜態頁面的實現。