flutter實戰1:完成一個有側邊欄的主介面

燃燒的魚丸發表於2018-03-17

經過2周的學習,看過筆記1-8的小夥伴們已經有不少開始自己寫APP了,我也按耐不住這股熱情,想要自己開發個APP玩玩,so,從本篇起,仿造一個APP,專案從0開始,每篇增加一些內容,一點一點完成這個APP,每次迭代的程式碼都將上傳到我的git倉庫。

鑑於我2周多的Flutter程式碼經驗,程式碼結構的思維可能沒有多年開發經驗的老鳥穩,如果有寫的不好的地方請大家多多指教。

本篇需要完成的任務

如上圖所示, 本篇將搭建一個HomePage,再其左上角加入側邊欄入口,並且通過側邊欄可以進入其他頁面。

##第一步 建立專案和資料夾。開啟vscode,到一個路徑下輸入命令:

flutter create appbyflutter

根據圖中所示,將專案目錄準備好:

程式目錄

由於第一篇開發用到的東西不多,先簡單向專案目錄中新增一個images檔案,用於存放APP預設圖片。預設的lib資料夾下新增一個pages資料夾,用於存放每個頁面。

##第二步 將main.dart僅作為APP的入口,承擔頁面入口和路由的功能:

main.dart不寫頁面程式碼

由於APP不只有一個頁面,為了方便維護和管理,所有的頁面程式碼都轉移到pages資料夾下,main.dart中處理APP的主頁面入口、路由和一系列需要初始化(如自動登陸、入場動畫等)的任務。有過vue、react開發經驗的前端大神們應該不陌生,這樣做可以使主程式和頁面解耦,當然本篇還沒有用到路由,暫不書寫路由的程式碼,等不及要了解路由的同學可以參考前端高手偏羅第一個APP或者英文閱讀理解

##第三步 ###主頁面 如第一步的圖所示,在pages資料夾中新增了2個檔案:home_page.dartother_page.dart,其中home_page.dart是這個APP的主頁面,other_page.dart作為的以後再開發的頁面。

注意在第二步的runapp()函式中,用到了MaterialApp(),意味著程式APP所有的頁面控制元件預設配套_Material_風格。

由於主頁面會動態引用各種控制元件,因此_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

  • 賬號資訊區域中有賬號頭像、粉絲頭像、賬號文字資訊和背景圖,這塊我們可以使用Material控制元件庫的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(),   //點選後收起側邊欄
            ),
          ],
        ),
      )
複製程式碼

上面的程式碼,用到了很多陌生的控制元件,如UserAccountsDrawerHeaderGestureDetectorBoxDecorationNetworkImageExactAssetImage等等,這裡我就不一一介紹了,各自的特性和用法請參考官方閱讀理解題庫,剛開始我也是懵逼的,這些內建控制元件大家簡單背誦一下即可,有可能後面因為頁面複雜度的提高,單獨拿出來封裝也說不定,會使用就可以了。

大家可以試試從螢幕的左邊沿向右滑動的手勢,是不是發現可以拉出側邊欄?再向右滑動收回側邊欄。我並沒有新增任何手勢事件的程式碼,這是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 PageSecond 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)**共同成長,謝謝大家~

相關文章