flutter實(shí)戰(zhàn)1:完成一個(gè)有側(cè)邊欄的主界面

經(jīng)過(guò)2周的學(xué)習(xí),看過(guò)筆記1-8的小伙伴們已經(jīng)有不少開(kāi)始自己寫APP了,我也按耐不住這股熱情,想要自己開(kāi)發(fā)個(gè)APP玩玩,so,從本篇起,仿造一個(gè)APP,項(xiàng)目從0開(kāi)始,每篇增加一些內(nèi)容,一點(diǎn)一點(diǎn)完成這個(gè)APP,每次迭代的代碼都將上傳到我的git倉(cāng)庫(kù)。

鑒于我2周多的Flutter代碼經(jīng)驗(yàn),代碼結(jié)構(gòu)的思維可能沒(méi)有多年開(kāi)發(fā)經(jīng)驗(yàn)的老鳥(niǎo)穩(wěn),如果有寫的不好的地方請(qǐng)大家多多指教。

本篇需要完成的任務(wù)

如上圖所示, 本篇將搭建一個(gè)HomePage,再其左上角加入側(cè)邊欄入口,并且通過(guò)側(cè)邊欄可以進(jìn)入其他頁(yè)面。

第一步

創(chuàng)建項(xiàng)目和文件夾。打開(kāi)vscode,到一個(gè)路徑下輸入命令:

flutter create appbyflutter

根據(jù)圖中所示,將項(xiàng)目目錄準(zhǔn)備好:


程序目錄

由于第一篇開(kāi)發(fā)用到的東西不多,先簡(jiǎn)單向項(xiàng)目目錄中添加一個(gè)images文件,用于存放APP默認(rèn)圖片。默認(rèn)的lib文件夾下添加一個(gè)pages文件夾,用于存放每個(gè)頁(yè)面。

第二步

main.dart僅作為APP的入口,承擔(dān)頁(yè)面入口和路由的功能:

main.dart不寫頁(yè)面代碼

由于APP不只有一個(gè)頁(yè)面,為了方便維護(hù)和管理,所有的頁(yè)面代碼都轉(zhuǎn)移到pages文件夾下,main.dart中處理APP的主頁(yè)面入口、路由和一系列需要初始化(如自動(dòng)登陸、入場(chǎng)動(dòng)畫等)的任務(wù)。有過(guò)vue、react開(kāi)發(fā)經(jīng)驗(yàn)的前端大神們應(yīng)該不陌生,這樣做可以使主程序和頁(yè)面解耦,當(dāng)然本篇還沒(méi)有用到路由,暫不書(shū)寫路由的代碼,等不及要了解路由的同學(xué)可以參考前端高手偏羅第一個(gè)APP或者英文閱讀理解。

第三步

主頁(yè)面

如第一步的圖所示,在pages文件夾中添加了2個(gè)文件:home_page.dartother_page.dart,其中home_page.dart是這個(gè)APP的主頁(yè)面,other_page.dart作為的以后再開(kāi)發(fā)的頁(yè)面。

注意在第二步的runapp()函數(shù)中,用到了MaterialApp(),意味著程序APP所有的頁(yè)面控件默認(rèn)配套Material風(fēng)格。

由于主頁(yè)面會(huì)動(dòng)態(tài)引用各種控件,因此StatefulWidget類型才可以滿足頁(yè)面需求。從下圖中分解一下頁(yè)面結(jié)構(gòu):

主頁(yè)面結(jié)構(gòu)圖解

先看圖左中有狀態(tài)控件HomePage為整個(gè)頁(yè)面的最頂層包裹,其內(nèi)放入了一個(gè)Scaffold腳手架,Scaffold中有非常豐富的屬性,可以放入側(cè)邊欄按鈕Drawer控件、頁(yè)面標(biāo)題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,),  //頭部的標(biāo)題AppBar
      drawer: new Drawer(),  //側(cè)邊欄按鈕Drawer
      body: new Center(  //中央內(nèi)容部分body
        child: new Text('HomePage',style: new TextStyle(fontSize: 35.0),),
      ),
    );
  }
}

OK,左圖的頁(yè)面就這么輕松搭建完畢。要實(shí)現(xiàn)右圖中的展開(kāi)的側(cè)邊欄,很簡(jiǎn)單,向Drawer控件中塞東西吧。

側(cè)邊欄

我們先圖解一下側(cè)邊欄的結(jié)構(gòu):


側(cè)邊欄結(jié)構(gòu)圖解
  • 整個(gè)側(cè)邊欄主從上到下按區(qū)塊分別放置了賬號(hào)若干功能項(xiàng)+分割線的列表,很容易想到使用布局控件ListView。

  • 賬號(hào)信息區(qū)域中有賬號(hào)頭像、粉絲頭像、賬號(hào)文字信息和背景圖,這塊我們可以使用Material控件庫(kù)的UserAccountsDrawerHeader控件實(shí)現(xiàn)。

  • 下面的功能列表項(xiàng)目不用多說(shuō),ListTitle控件妥妥的,分割線直接Divider即可。

于是,我們向new Drawer()中加入如下代碼:

//側(cè)邊欄填充內(nèi)容
drawer: new Drawer(     //側(cè)邊欄按鈕Drawer
        child: new ListView(
          children: <Widget>[
            new UserAccountsDrawerHeader(   //Material內(nèi)置控件
              accountName: new Text('CYC'), //用戶名
              accountEmail: new Text('example@126.com'),  //用戶郵箱
              currentAccountPicture: new GestureDetector( //用戶頭像
                onTap: () => print('current user'),
                child: new CircleAvatar(    //圓形圖標(biāo)控件
                  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'),//圖片調(diào)取自網(wǎng)絡(luò)
                ),
              ),
              otherAccountsPictures: <Widget>[    //粉絲頭像
                new GestureDetector(    //手勢(shì)探測(cè)器,可以識(shí)別各種手勢(shì),這里只用到了onTap
                  onTap: () => print('other user'), //暫且先打印一下信息吧,以后再添加跳轉(zhuǎn)頁(yè)面的邏輯
                  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(    //用一個(gè)BoxDecoration裝飾器提供背景圖片
                image: new DecorationImage(
                  fit: BoxFit.fill,
                  // image: new NetworkImage('https://raw.githubusercontent.com/flutter/website/master/_includes/code/layout/lakes/images/lake.jpg')
                  //可以試試圖片調(diào)取自本地。調(diào)用本地資源,需要到pubspec.yaml中配置文件路徑
                  image: new ExactAssetImage('images/lake.jpg'),
                ),
              ),
            ),
            new ListTile(   //第一個(gè)功能項(xiàng)
              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(   //第二個(gè)功能項(xiàng)
              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(   //第二個(gè)功能項(xiàng)
              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(),   //點(diǎn)擊后收起側(cè)邊欄
            ),
          ],
        ),
      )

上面的代碼,用到了很多陌生的控件,如UserAccountsDrawerHeader、GestureDetector、BoxDecoration、NetworkImageExactAssetImage等等,這里我就不一一介紹了,各自的特性和用法請(qǐng)參考官方閱讀理解題庫(kù),剛開(kāi)始我也是懵逼的,這些內(nèi)置控件大家簡(jiǎn)單背誦一下即可,有可能后面因?yàn)轫?yè)面復(fù)雜度的提高,單獨(dú)拿出來(lái)封裝也說(shuō)不定,會(huì)使用就可以了。

大家可以試試從屏幕的左邊沿向右滑動(dòng)的手勢(shì),是不是發(fā)現(xiàn)可以拉出側(cè)邊欄?再向右滑動(dòng)收回側(cè)邊欄。我并沒(méi)有添加任何手勢(shì)事件的代碼,這是Drawer控件自帶的屬性,和控件自帶Material風(fēng)格動(dòng)效一樣,內(nèi)置控件也自帶了默認(rèn)手勢(shì),隱隱聽(tīng)到~原生開(kāi)發(fā)的程序員哭暈在廁所,哈哈哈

第四步

功能按鈕觸發(fā)頁(yè)面跳轉(zhuǎn)。

首先我們要?jiǎng)?chuàng)建一個(gè)子頁(yè)面,于是乎pages文件夾下,我又創(chuàng)建了一個(gè)other_page.dart文件。要從HomePage.dart中跳轉(zhuǎn)到other_page.dart,還需要在HomePage.dart中引一下other_page.dart。于是:

頁(yè)面文件引用

然后到other_page.dart中敲入代碼:

import 'package:flutter/material.dart';

class OtherPage extends StatelessWidget {

  final String pageText;    //定義一個(gè)常量,用于保存跳轉(zhuǎn)進(jìn)來(lái)獲取到的參數(shù)

  OtherPage(this.pageText);    //構(gòu)造函數(shù),獲取參數(shù)

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(title: new Text(pageText),),    //將參數(shù)當(dāng)作頁(yè)面標(biāo)題
      body: new Center(
        child: new Text('pageText'),
      ),
    );
  }
}

Flutter要求轉(zhuǎn)入的頁(yè)面必須提前定義一個(gè)常量分配好空間,且在構(gòu)造函數(shù)中植入這個(gè)參數(shù),才可捕捉外部傳過(guò)來(lái)的參數(shù)值。

觸發(fā)跳轉(zhuǎn)

First PageSecond Page這兩個(gè)ListTile控件中加入點(diǎn)擊跳轉(zhuǎn)頁(yè)面的代碼:

new ListTile(
    title: new Text('First Page'),
    trailing: new Icon(Icons.arrow_upward),
    onTap: () {
        Navigator.of(context).pop();  //點(diǎn)擊后收起側(cè)邊欄
        Navigator.of(context).push(new MaterialPageRoute(builder: (BuildContext context) => new OtherPage('First Page')));  //進(jìn)入OtherPage頁(yè)面,傳入?yún)?shù)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();,意味著先收起側(cè)邊欄,再進(jìn)入新頁(yè)面。如果沒(méi)有這句代碼,即使進(jìn)入了新頁(yè)面,再返回來(lái),側(cè)邊欄依然處于展開(kāi)的樣子,這個(gè)體驗(yàn)是反人類的,所以寫上它吧~少年。

總結(jié)

由于我沒(méi)有詳細(xì)的去定位和設(shè)計(jì)產(chǎn)品到底是干什么的,大家可能會(huì)覺(jué)得有點(diǎn)懵逼,為什么是這種側(cè)邊欄的布局,而不是很多社交APP常用的頂部+底部Tab欄的樣式,不著急,我們下一篇實(shí)現(xiàn)。側(cè)邊欄有什么好處呢?節(jié)省空間,如果底部需要放置更重要的功能控件(比如音樂(lè)播放器)時(shí),往側(cè)邊欄放入頁(yè)面切換邏輯是個(gè)不錯(cuò)的應(yīng)對(duì)方案。本篇內(nèi)容其實(shí)非常簡(jiǎn)單,主要就是介紹大家認(rèn)識(shí)幾個(gè)常用控件,不用調(diào)CSS,不用思考因?yàn)槊芭菔录?dǎo)致復(fù)雜的交互邏輯實(shí)現(xiàn),這就是Flutter的魅力,簡(jiǎn)約而不簡(jiǎn)單,相信大家看過(guò)之后,自行開(kāi)發(fā)APP的信心更足了,好勒,今天就到這里,感謝大家的支持,請(qǐng)關(guān)注我的Flutter圈子,多多投稿,也可以加入flutter 中文社區(qū)(官方QQ群:338252156)共同成長(zhǎng),謝謝大家~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 內(nèi)容抽屜菜單ListViewWebViewSwitchButton按鈕點(diǎn)贊按鈕進(jìn)度條TabLayout圖標(biāo)下拉刷新...
    皇小弟閱讀 47,133評(píng)論 22 665
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫(kù)、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 15,037評(píng)論 4 61
  • 原文鏈接:https://github.com/opendigg/awesome-github-android-u...
    IM魂影閱讀 33,143評(píng)論 6 472
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,733評(píng)論 25 709
  • 人生太長(zhǎng),總要跑著才能抵達(dá)目的地,用走,有時(shí)候是來(lái)不及的。雨天跑過(guò)屋檐,炎夏跑進(jìn)樹(shù)蔭,春天跑贏萬(wàn)物生長(zhǎng)的時(shí)光間隙。...
    不賣勇氣的販賣機(jī)閱讀 375評(píng)論 0 0

友情鏈接更多精彩內(nèi)容