通過2周的學習,看過筆記1-8的小夥伴們已經有很多開始本身寫APP了,我也按耐不住這股熱情,想要本身開發個APP玩玩,so,從本篇起,仿造一個APP,項目從0開始,每篇增長一些內容,一點一點完成這個APP,每次迭代的代碼都將上傳到個人git倉庫。html
鑑於我2周多的Flutter代碼經驗,代碼結構的思惟可能沒有多年開發經驗的老鳥穩,若是有寫的很差的地方請你們多多指教。前端
如上圖所示, 本篇將搭建一個HomePage,再其左上角加入側邊欄入口,而且經過側邊欄能夠進入其餘頁面。vue
建立項目和文件夾。打開vscode,到一個路徑下輸入命令:react
flutter create appbyflutter
根據圖中所示,將項目目錄準備好:
git
因爲第一篇開發用到的東西很少,先簡單向項目目錄中添加一個images文件,用於存放APP默認圖片。默認的lib文件夾下添加一個pages文件夾,用於存放每一個頁面。程序員
將main.dart僅做爲APP的入口,承擔頁面入口和路由的功能:
github
因爲APP不僅有一個頁面,爲了方便維護和管理,全部的頁面代碼都轉移到pages文件夾下,main.dart中處理APP的主頁面入口、路由和一系列須要初始化(如自動登錄、入場動畫等)的任務。有過vue、react開發經驗的前端大神們應該不陌生,這樣作可使主程序和頁面解耦,固然本篇尚未用到路由,暫不書寫路由的代碼,等不及要了解路由的同窗能夠參考前端高手偏羅的第一個APP或者英文閱讀理解。web
如第一步的圖所示,在pages文件夾中添加了2個文件:home_page.dart和other_page.dart,其中home_page.dart是這個APP的主頁面,other_page.dart做爲的之後再開發的頁面。網絡
注意在第二步的runapp()
函數中,用到了MaterialApp()
,意味着程序APP全部的頁面控件默認配套_Material_風格。app
因爲主頁面會動態引用各類控件,所以_StatefulWidget_類型才能夠知足頁面需求。從下圖中分解一下頁面結構:
先看圖左中有狀態控件HomePage
爲整個頁面的最頂層包裹,其內放入了一個Scaffold
腳手架,Scaffold
中有很是豐富的屬性,能夠放入側邊欄按鈕Drawer
控件、頁面標題AppBar
控件和body
部分,因而貼入如下代碼:
import 'package:flutter/material.dart'; class HomePage extends StatefulWidget { @override _HomePageState createState() => new _HomePageState(); } class _HomePageState extends State<HomePage> { @override Widget build(BuildContext context) { return new Scaffold( appBar: new AppBar(title: new Text("CYC"), backgroundColor: Colors.redAccent,), //頭部的標題AppBar drawer: new Drawer(), //側邊欄按鈕Drawer body: new Center( //中央內容部分body child: new Text('HomePage',style: new TextStyle(fontSize: 35.0),), ), ); } }
OK,左圖的頁面就這麼輕鬆搭建完畢。要實現右圖中的展開的側邊欄,很簡單,向Drawer
控件中塞東西吧。
咱們先圖解一下側邊欄的結構:
ListView
。UserAccountsDrawerHeader
控件實現。ListTitle
控件妥妥的,分割線直接Divider
便可。因而,咱們向new Drawer()
中加入以下代碼:
//側邊欄填充內容 drawer: new Drawer( //側邊欄按鈕Drawer child: new ListView( children: <Widget>[ new UserAccountsDrawerHeader( //Material內置控件 accountName: new Text('CYC'), //用戶名 accountEmail: new Text('example@126.com'), //用戶郵箱 currentAccountPicture: new GestureDetector( //用戶頭像 onTap: () => print('current user'), child: new CircleAvatar( //圓形圖標控件 backgroundImage: new NetworkImage('https://upload.jianshu.io/users/upload_avatars/7700793/dbcf94ba-9e63-4fcf-aa77-361644dd5a87?imageMogr2/auto-orient/strip|imageView2/1/w/240/h/240'),//圖片調取自網絡 ), ), otherAccountsPictures: <Widget>[ //粉絲頭像 new GestureDetector( //手勢探測器,能夠識別各類手勢,這裏只用到了onTap onTap: () => print('other user'), //暫且先打印一下信息吧,之後再添加跳轉頁面的邏輯 child: new CircleAvatar( backgroundImage: new NetworkImage('https://upload.jianshu.io/users/upload_avatars/10878817/240ab127-e41b-496b-80d6-fc6c0c99f291?imageMogr2/auto-orient/strip|imageView2/1/w/240/h/240'), ), ), new GestureDetector( onTap: () => print('other user'), child: new CircleAvatar( backgroundImage: new NetworkImage('https://upload.jianshu.io/users/upload_avatars/8346438/e3e45f12-b3c2-45a1-95ac-a608fa3b8960?imageMogr2/auto-orient/strip|imageView2/1/w/240/h/240'), ), ), ], decoration: new BoxDecoration( //用一個BoxDecoration裝飾器提供背景圖片 image: new DecorationImage( fit: BoxFit.fill, // image: new NetworkImage('https://raw.githubusercontent.com/flutter/website/master/_includes/code/layout/lakes/images/lake.jpg') //能夠試試圖片調取自本地。調用本地資源,須要到pubspec.yaml中配置文件路徑 image: new ExactAssetImage('images/lake.jpg'), ), ), ), new ListTile( //第一個功能項 title: new Text('First Page'), trailing: new Icon(Icons.arrow_upward), onTap: () { Navigator.of(context).pop(); Navigator.of(context).push(new MaterialPageRoute(builder: (BuildContext context) => new SidebarPage())); } ), new ListTile( //第二個功能項 title: new Text('Second Page'), trailing: new Icon(Icons.arrow_right), onTap: () { Navigator.of(context).pop(); Navigator.of(context).push(new MaterialPageRoute(builder: (BuildContext context) => new SidebarPage())); } ), new ListTile( //第二個功能項 title: new Text('Second Page'), trailing: new Icon(Icons.arrow_right), onTap: () { Navigator.of(context).pop(); Navigator.of(context).pushNamed('/a'); } ), new Divider(), //分割線控件 new ListTile( //退出按鈕 title: new Text('Close'), trailing: new Icon(Icons.cancel), onTap: () => Navigator.of(context).pop(), //點擊後收起側邊欄 ), ], ), )
上面的代碼,用到了不少陌生的控件,如UserAccountsDrawerHeader、GestureDetector、BoxDecoration、NetworkImage、ExactAssetImage等等,這裏我就不一一介紹了,各自的特性和用法請參考官方閱讀理解題庫,剛開始我也是懵逼的,這些內置控件你們簡單背誦一下便可,有可能後面由於頁面複雜度的提升,單獨拿出來封裝也說不定,會使用就能夠了。
你們能夠試試從屏幕的左邊沿向右滑動的手勢,是否是發現能夠拉出側邊欄?再向右滑動收回側邊欄。我並無添加任何手勢事件的代碼,這是
Drawer
控件自帶的屬性,和控件自帶
Material風格動效同樣,內置控件也自帶了默認手勢,隱隱聽到~原生開發的程序員哭暈在廁所,哈哈哈
首先咱們要建立一個子頁面,因而乎pages文件夾下,我又建立了一個other_page.dart文件。要從HomePage.dart中跳轉到other_page.dart,還須要在HomePage.dart中引一下other_page.dart。因而:
而後到other_page.dart中敲入代碼:
import 'package:flutter/material.dart'; class OtherPage extends StatelessWidget { final String pageText; //定義一個常量,用於保存跳轉進來獲取到的參數 OtherPage(this.pageText); //構造函數,獲取參數 @override Widget build(BuildContext context) { return new Scaffold( appBar: new AppBar(title: new Text(pageText),), //將參數看成頁面標題 body: new Center( child: new Text('pageText'), ), ); } }
Flutter要求轉入的頁面必須提早定義一個常量分配好空間,且在構造函數中植入這個參數,纔可捕捉外部傳過來的參數值。
向First Page和Second Page這兩個ListTile
控件中加入點擊跳轉頁面的代碼:
new ListTile( title: new Text('First Page'), trailing: new Icon(Icons.arrow_upward), onTap: () { Navigator.of(context).pop(); //點擊後收起側邊欄 Navigator.of(context).push(new MaterialPageRoute(builder: (BuildContext context) => new OtherPage('First Page'))); //進入OtherPage頁面,傳入參數First Page } ), new ListTile( title: new Text('Second Page'), trailing: new Icon(Icons.arrow_right), onTap: () { Navigator.of(context).pop(); Navigator.of(context).push(new MaterialPageRoute(builder: (BuildContext context) => new OtherPage('Second Page'))); } ),
上面的代碼中onTap()
事件裏有一句Navigator.of(context).pop();
,意味着先收起側邊欄,再進入新頁面。若是沒有這句代碼,即便進入了新頁面,再返回來,側邊欄依然處於展開的樣子,這個體驗是反人類的,因此寫上它吧~少年。
因爲我沒有詳細的去定位和設計產品究竟是幹什麼的,你們可能會以爲有點懵逼,爲何是這種側邊欄的佈局,而不是不少社交APP經常使用的頂部+底部Tab欄的樣式,不着急,咱們下一篇實現。側邊欄有什麼好處呢?節省空間,若是底部須要放置更重要的功能控件(好比音樂播放器)時,往側邊欄放入頁面切換邏輯是個不錯的應對方案。本篇內容其實很是簡單,主要就是介紹你們認識幾個經常使用控件,不用調CSS,不用思考由於冒泡事件致使複雜的交互邏輯實現,這就是Flutter的魅力,簡約而不簡單,相信你們看過以後,自行開發APP的信心更足了,好勒,今天就到這裏,感謝你們的支持,請關注個人Flutter圈子,多多投稿,也能夠加入flutter 中文社區(官方QQ羣:338252156)共同成長,謝謝你們~