flutter筆記4:使用material原生控件開發一個APP

接着上一篇,咱們作一個這樣的APP:
flutter官方案例html

開始以前,我發現了一個好玩的東西,每次咱們在終端中輸入命令:vue

flutter run

終端裏會有這個東西:
APP調試器react

按照上圖所示,咱們的進入這個網頁看看是個啥:
調試器主頁編程

好高大上的感受,具體是幹嗎的,我也不知道,有興趣的同窗能夠點進去把玩把玩,之後搞明白了再更吧。數組

第一步

先建立一個列表。微信

回到main.dart中,把原來的代碼所有清空,複製如下代碼:app

import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';

void main() => runApp(new MyApp());

class MyApp extends StatelessWidget {
  @override
  //構建一個容器
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Startup Name Generator',
      home: new RandomWords(),//定義子組件爲有狀態控件RandomWords類的實例
    );
  }
}

//定義有狀態控件RandomWords類
class RandomWords extends StatefulWidget {
  @override
  createState() => new RandomWordsState();//建立有狀態控件RandomWords的狀態類實例:RandomWordsState
}

//定義狀態類RandomWordsState
class RandomWordsState extends State<RandomWords> {
  @override
  final _suggestions = <WordPair>[];  //用於保存隨機字符串詞組,注意這是一個數組變量
  final _biggerFont = const TextStyle(fontSize: 18.0);   //用於標識字符串的樣式

  //構建一個腳手架,裏面塞入前面定義好的_buildSuggestions類
  Widget build(BuildContext context) {
    return new Scaffold (
      appBar: new AppBar(
        title: new Text('Startup Name Generator'),
      ),
      body: _buildSuggestions(), 
    );
  }

  //定義一個子控件,這個控件就是放置隨機字符串詞組的列表
  Widget _buildSuggestions() {
    return new ListView.builder(  //ListView(列表視圖)是material.dart中的基礎控件
      padding: const EdgeInsets.all(16.0),  //padding(內邊距)是ListView的屬性,配置其屬性值
      //經過ListView自帶的函數itemBuilder,向ListView中塞入行,變量 i 是從0開始計數的行號
      //此函數會自動循環並計數,咋結束的我也不知道,走着瞧咯
      itemBuilder: (context, i) {
        if (i.isOdd) return new Divider();//奇數行塞入分割線對象
        final index = i ~/ 2;  //當前行號除以2取整,獲得的值就是_suggestions數組項索引號
        // 若是計算獲得的數組項索引號超出了_suggestions數組的長度,那_suggestions就再生10個隨機組合的字符串詞組
        if (index >= _suggestions.length) {
          _suggestions.addAll(generateWordPairs().take(10));
        }
        return _buildRow(_suggestions[index]);//把這個數據項塞入ListView中
      }
    );
  }

  //定義的_suggestions數組項屬性
  Widget _buildRow(WordPair pair) {
    //ListTile和Text都是material.dart中的基礎控件
    return new ListTile(
      title: new Text(
        pair.asPascalCase,  //使用駝峯樣式
        style: _biggerFont,
      ),
    );
  }
    
}

看到這裏,是否是有點暈,各類聲明、各類引用,還有回調,把上面的代碼,用下面的圖解析下結構,看看到底怎麼個狀況:
代碼圖解less

能夠發如今StatelessWidget和State類中都有一個Widget類型的函數build(),感受有點像類的初始化方法construct(),而RandomWords對象爲何只使用了createState()卻沒有build(),我也不知道,走着瞧吧。當對象實例化的時候,首先執行Widget build(BuildContext context){}函數,函數中BuildContext類型的參數context,到目前爲止還不知道幹嗎用的,暫且忽略其意義吧。dom

material類型的子控件都經過回調函數的方式建立,我讀起來有些不習慣,但經過回調,免去了先聲明再使用的麻煩,而且能夠直接對對象的屬性進行配置,經過build()一層層回調,代碼簡潔很多,而代碼中使用到的material內置控件,我就不一一介紹了,有興趣的同窗請參考material官方API,注意material控件索引在頁面右邊的列表,別找到左邊去了。ide

注意,遇到這種聲明類屬性的格式:_[變量名]。按官方的意思是,若是變量名的前綴有_下劃線,表示強制轉換爲私有變量,至關於聲明變量爲private,但使用這個變量的時候,仍是要將下劃線進行完整的書寫。

保存代碼後運行一下,能夠看到APP變成了這個樣子:
無限滾動的列表

向下滾動試試,發現能夠一直滾下去~

第二步

向列表里加個_收藏_標籤按鈕,使每行能夠標記收藏或取消收藏。這個_收藏_標籤就是狀態,既然要修改狀態,確定要到state中進行啦。

到對象RandomWordsState中定義一個對象,用於存儲標記。爲何要單獨存儲標記呢?由於這樣就不須要往行對象(ListTile)中添加標記,下降了對象的複雜度。以下:

class RandomWordsState extends State<RandomWords> {
  final _suggestions = <WordPair>[];

  final _saved = new Set<WordPair>();  //新加這一句

  final _biggerFont = const TextStyle(fontSize: 18.0);
  ...
}

爲何存儲標記的是對象而不是一個數組呢?大概是想順便教咱們使用一下Set對象吧,聽說Set對象不容許有重複的項目,方便後面模擬堆棧的效果,很是適合這種場景。而後咱們到每一個行添加一個標記收藏的控件:

Widget _buildRow(WordPair pair) {
    //定義一個布爾變量,用於判斷行控件ListTile是否被標記爲收藏
    final alreadySaved = _saved.contains(pair);
    return new ListTile(
      title: new Text(
        pair.asPascalCase,
        style: _biggerFont,
      ),
      //安放圖標控件
      trailing: new Icon(
        alreadySaved ? Icons.favorite : Icons.favorite_border,
        color: alreadySaved ? Colors.red : null,
      ),
      //定義點擊事件,控制圖標的樣式的切換
      onTap: () {
        setState(() {
          if (alreadySaved) {
            _saved.remove(pair);
          } else {
            _saved.add(pair);
          }
        });
    },
    );
  }

注意,在onTap事件中,使用到了setState()方法(用過vue或react的玩家是否是很熟悉呀),在這個方法裏修改變量值,便可觸發state對象執行build()方法重繪對象。這裏每次變動對象_saved後,都會重繪ListTile對象,而靜態變量alreadySaved也被從新定義,所以不用擔憂alreadySaved值不被更新的問題,若是去除final關鍵字,Dart語法會報錯,還請路過大神點撥一下緣由。

保存代碼後,能夠看到APP刷新了,每一行都添加了一個心型圖標,反覆戳這個行,還自動配有動畫效果:
實現標記按鈕

第三步

加入一個導航欄樣式的堆棧。

先往主頁面控件(AppBar)中添加一個能夠進入收藏列表的入口:

class RandomWordsState extends State<RandomWords> {
  ...
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text('Startup Name Generator'),
        //爲AppBar對象的actions屬性添加一個IconButton對象,actions屬性值能夠是Widget類型的數組
        actions: <Widget>[
          new IconButton(icon: new Icon(Icons.list), onPressed: _pushSaved), 
          //能夠試試添加這兩行後APP上有什麼效果
          new IconButton(icon: new Icon(Icons.add), onPressed: _pushSaved), 
          new IconButton(icon: new Icon(Icons.create), onPressed: _pushSaved), 
        ],
      ),
      body: _buildSuggestions(),
    );
  }
  ...
}

因爲沒有定義_pushSaved函數,直接複製上面註釋說明的代碼會報錯,所以,定義一個void類型的函數_pushSaved:

class RandomWordsState extends State<RandomWords> {
  ...
  void _pushSaved() {
  }
}

這時候能夠看到右上角多了3個圖標,如圖:

建立收藏夾入口圖標

而後咱們往_pushSaved()函數中添加代碼,讓收藏夾玩起來:

void _pushSaved() {
    //建立導航欄控件Navigator,而後往裏面塞入MaterialPageRoute控件
    Navigator.of(context).push(
      new MaterialPageRoute(
        builder: (context) {
          //經過遍歷_saved對象,獲取已收藏的行對象
          final tiles = _saved.map(
                (pair) {
              return new ListTile(
                title: new Text(
                  pair.asPascalCase,
                  style: _biggerFont,
                ),
              );
            },
          );
          //函數的的鏈式調用,獲取到添加好分割線的ListTile控件
          final divided = ListTile
              .divideTiles(            //divideTiles()函數,向每一個tile間隔插入一個1像素寬的邊框
                context: context, 
                tiles: tiles,
              )
              .toList();  //不要漏掉這個函數,不然進入收藏夾直接崩潰
          return new Scaffold(
            appBar: new AppBar(
              title: new Text('收藏的列表項目'),
            ),
            body: new ListView(children: divided),  //直接將準備好的ListTile塞入其中,完成內容填充
          );
        },
      ),
    );
  }

保存代碼後,刷新APP,如圖:
兩個頁面相互跳轉

如上圖所示,紅色的箭頭表示點擊按鈕後頁面的切換,綠色箭頭表示收藏夾的值的對照,有沒有發現咱們並無寫返回主頁按鈕的代碼,這個返回按鈕從哪來的呢?是由Navigator對象自動生成的,而且自動指向到主頁面的路由,不過遺憾的是,沒有加入返回手勢的支持,若有須要,還得本身寫,具體怎麼寫,跟着個人筆記走吧,我如今也不知道。

你們注意看主頁列表和收藏夾列表的區別,二者都是列表,都是使用的ListView和ListTile對象,但實現的方式徹底不一樣,插入行對象和分割線的差別頗有意思,有興趣的同窗能夠自行修改下代碼,看看能不能將兩種列表的構建方法對調一下,參考官方資料ListViewListTile

第四步

變動UI主題風格。

這一步超級簡單,往MaterialApp對象裏添加theme屬性值便可:

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Startup Name Generator',
      //添加theme屬性值,塞入ThemeData對象
      theme: new ThemeData(
        primaryColor: Colors.white,
      ),
      home: new RandomWords(),
    );
  }
}

再保存,刷新APP試試,主題變成了白色風格:

變動風格

ThemeData()方法自己提供了不少配色方案,玩家能夠參考ThemeData官方說明,掌握其強大功能。沒想到變動主題如此輕鬆,在當前APP界愈來愈追求視覺體驗升級的趨勢下,使用flutter開發APP的玩家應該欣慰很多吧~

好勒,此次官方萌新課程的搬運就到這裏,我是真的超喜歡這個萌新課程,連課程總結都幫我寫好了:

  • 從頭開始建立一個Flutter應用程序。
  • 書寫了Dart語言的代碼。
  • 學會了調用外部的第三方庫。
  • 體驗熱更新帶來的開發週期加速。
  • 學會使用有狀態控件,加強了應用的交互。
  • 使用ListView和ListTiles建立了一個支持懶加載的無限滾動列表。
  • 建立了一組路由並實現了主路由和新路由之間的跳轉邏輯。
  • 瞭解如何使用主題更改應用UI的外觀。

自我總結一下,flutter是一個控件高度集成化的開發平臺,控件的完整度極高,控件之間的交互實現也傾向於傻瓜化,好比自動生成返回按鈕、主題風格全局可控。只要把握好狀態值和控件之間的嵌套關係,開發者幾乎不須要單獨敲代碼實現跳轉邏輯,代碼簡直不要太簡潔,不知不覺間就寫好了一個APP。而VScode中的Dart Code插件也實在太好用,代碼提示、函數用法和參數都有詳盡的說明,看得出谷歌拿出了十足的誠意要在跨平臺開發上面大搞特搞一番。

Dart Code插件提示

固然了,滿屏幕的回調函數讓我這種編程思惟還停留在C語言時代的菜鳥來講,扶牆~ 頭有點暈,還須要點時間慢慢適應一下下,也因爲我沒有那麼深厚的技術功底,對這個教程的理解還比較有限,可能有寫的不對或很差的地方,也歡迎你們指正,尤爲我花了一天一晚上寫了這篇稿也是蠻不容易了,有路過的高手不說兩句也是哪啥了是吧。固然,我有空的時候抓緊讀一讀Dart 語法基礎官方原版,有了新的發現也會寫稿分享出來。

好啦就寫到這裏,廣告時間,對flutter感興趣的小夥伴能夠關注我,歡迎你們到Flutter圈子中投稿,也能夠聯繫管理員加入咱們的flutter微信羣嗨聊,謝謝捧場~!
flutter 中文社區(官方QQ羣:338252156)

相關文章
相關標籤/搜索