精講Flutter官網的第一個例子


原文連接

前言

學習Flutter你必定會看到官網的第一個例子:中文版英文版。可是做爲新手,或許你看的會很費勁,這篇文章的目的是幫助你更好的理解這個例子。android

最終的效果圖:數組

最終效果

咱們先分析一下如何實現上圖中的效果:app

Android開發者less

1. 準備數據:列表數據和選中的數據能夠分別使用兩個List或者數組存儲。 2. 界面列表:使用ListView或RecyclerView 3. 界面跳轉:可使用Intent攜帶數據到新的列表頁

iOS開發者dom

1. 準備數據:列表數據和選中的數據能夠分別使用兩個數組存儲。 2. 界面列表:使用TableView或CollectionView 3. 界面跳轉:使用NavigationController,能夠把值直接賦值給新的頁面對象

結論

咱們發現,不管是原生的Android仍是iOS開發,都須要作的步驟是:ide

  1. 存儲要展現的數據,存儲選中的數據
  2. 展現列表,並把數據展現出來
  3. 設置跳轉到新頁面

因此在Flutter開發中,也遵守這幾個步驟會更好的理解函數

Flutter開發工具

* 準備數據:列表數據使用數組存儲,選中的數據可使用Set存儲(由於set能夠自動去重)。 * 界面列表:使用ListView * 界面跳轉:可使用Navigator

拆解分析官方代碼,帶你快速理解

官網上使用大概110行代碼實現上面的例子,咱們把這些代碼拆解成主要的三部分來幫助咱們學習:學習

前提:你首先應該會用Android studio或者其餘開發工具建立一個Flutter的工程,若是你須要學習關於這個步驟,能夠在 這裏快速學習開發工具

當你建立一個全新的Flutter工程並運行,界面上會出現熟悉的「Hello world」。 爲了更容易的理解Flutter的代碼,咱們先分析一下建立初始的代碼,至少要知道咱們須要從哪裏開始動手:

Snip20190104_19.png

咱們要編輯的就是這裏的 main.dart 文件,跟其餘語言同樣,Flutter的入口函數是main函數:

import 'package:flutter/material.dart';

void main() => runApp(new MyApp());   //分析 1

class MyApp extends StatelessWidget {  //分析 2 (StatelessWidget)
  @override
  Widget build(BuildContext context) {   //分析 3
    return new MaterialApp(
      title: 'Welcome to Flutter',
      home: new Scaffold(      //分析 4
        appBar: new AppBar(
          title: new Text('Welcome to Flutter'),
        ),
        body: new Center(  //分析 5
          child: new Text('Hello World'),
        ),
      ),
    );
  }
}

複製代碼

分析

  1. 這裏的 => 是Dart中單行函數的簡寫,等價於:
void main() {
  runApp(new MyApp());
} 
複製代碼
  1. StatelessWidget 表明只有一種狀態的組件,與之對應的是StatefulWidget(表示可能有多種狀態)。這裏先不用深究其原理,只需知道這個跟flutter的刷新等相關。

  2. 在Widget組件中都是經過build方法來描述本身的內部結構。這裏的build表示構建MyApp中使用的是MaterialApp的系統組件。

  3. home標籤的值:Scaffold是Material library 中提供的一個組件,咱們能夠在裏面設置導航欄、標題和包含主屏幕widget樹的body屬性。能夠看到這裏是在頁面上添加了AppBar和一個Text。

  4. Center是一個能夠把子組件放在中心的組件

開始改造

咱們的目標是把頁面中顯示hello_world的TextView換成一個ListView。由上面的分析可知,將上面第4點的home標籤的值,換成一個ListView就能改變頁面顯示的內容。不過在此以前,須要先準備一下要顯示的數據,這裏是使用一個叫 english_words 的三方包,能夠幫助咱們生成顯示的單詞數據。先學習一下如何添加依賴包:

  1. 打開pubspec.yaml文件添加三方庫:
dependencies:
  flutter:
    sdk: flutter

  cupertino_icons: ^0.1.0
  english_words: ^3.1.0
複製代碼
  1. 點擊 Packages get 獲取剛添加的包。

添加english_words庫以後,能夠這樣使用這個庫創造數據:

//創造5個隨機詞組,並返回詞組的迭代器
generateWordPairs().take(5)
複製代碼

學習使用可變狀態的組件 StatefulWidget

查看ListView的源碼,發現其最終是繼承自 StatelessWidget,因此它的狀態是惟一的。可是要實現的ListView中的數據是動態變化的,因此須要使用StatefulWidget來動態改變ListView中的數據。

使用StatefulWidget組件須要本身控制在不一樣狀況下的顯示狀態,因此須要實現State類來告訴StatefulWidget類不一樣狀況下如何展現。

建立一個動態變化的組件類,用於表示要顯示的ListView:

class RandomWords extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {  //分析1
    return new RandomWordsState();
  }
}
複製代碼

分析:

  1. 建立State類的方法,這裏繼承系統的State創造一個名叫RandomWordsState的類,來控制ListView各個狀態下的展現。
class RandomWordsState extends State<RandomWords> {
}
複製代碼

要完成展現數據和保存點擊後的數據,這裏分別用數組和set來存儲(用set存儲點擊後的數據是由於set能夠去重,你也能夠選擇其餘的存儲方式)

class RandomWordsState extends State<RandomWords> {
  final _suggestions = <WordPair>[];   //分析 1
  final _saved = new Set<WordPair>();
  final _biggerFont = const TextStyle(fontSize: 18.0);   //分析 2
}
複製代碼

分析:

  1. Dart中加上 _表示私有化
  2. 表示字體大小的常量

創造數據集和構建ListView

添加以下兩個方法到RandomWordsState類中,表示創造數據集和構建ListView:

Widget _buildSuggestions() {   
    return new ListView.builder(
      padding: const EdgeInsets.all(16.0),
      // 對於每一個建議的單詞對都會調用一次itemBuilder,而後將單詞對添加到ListTile行中
      // 在偶數行,該函數會爲單詞對添加一個ListTile row.
      // 在奇數行,該函數會添加一個分割線widget,來分隔相鄰的詞對。
      // 注意,在小屏幕上,分割線看起來可能比較吃力。
      itemBuilder: (context, i) {          
        // 在每一列以前,添加一個1像素高的分隔線widget
        if (i.isOdd) return new Divider();

        // 語法 "i ~/ 2" 表示i除以2,但返回值是整形(向下取整),好比i爲:1, 2, 3, 4, 5
        // 時,結果爲0, 1, 1, 2, 2, 這能夠計算出ListView中減去分隔線後的實際單詞對數量
        final index = i ~/ 2;
        // 若是是建議列表中最後一個單詞對
        if (index >= _suggestions.length) {
          // ...接着再生成10個單詞對,而後添加到建議列表
          _suggestions.addAll(generateWordPairs().take(10));   
        }
        return _buildRow(_suggestions[index]);
      }
    );
  }

  Widget _buildRow(WordPair pair) {
    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);
          }
        });
      },
    );
  }
複製代碼

代碼中有詳細的註釋,可是爲了方便理解,這裏仍是給出一點解釋:

  1. _buildSuggestions方法就是返回一個ListView
  2. _buildRow方法就是返回ListView中的一行(ListTile)如何展現

_buildSuggestions方法中:

  • 代碼中的itemBuilder就是如何顯示一行(ListTile)的配置,其返回值是_buildRow方法
  • _suggestions.addAll(generateWordPairs().take(10));就是每次添加10個數據到顯示數組中

_buildRow方法中:

  • 設置了一行(ListTile表明一行內容,在iOS和android中叫cell)如何展現
  • ListTile設置了title、trailing(右邊的圖標)和點擊事件onTap()
  • 會根據有沒有被保存過,決定右邊顯示什麼圖標
  • 當點擊時,會把沒有點擊過的內容保存到_saved容器中。

添加跳轉邏輯

添加_pushSaved方法表示如何跳轉到新的頁面並展現選中的數據:

main.dart

void _pushSaved() {
    Navigator.of(context).push(  // 分析 1
      new MaterialPageRoute(  // 分析 2
        builder: (context) {
          final tiles = _saved.map(  //數據
            (pair) {
              return new ListTile(
                title: new Text(
                  pair.asPascalCase,
                  style: _biggerFont,
                ),
              );
            },
          );
          final divided = ListTile.divideTiles(
            context: context,
            tiles: tiles,
          ).toList();

          return new Scaffold(  // 分析 3
            appBar: new AppBar(
              title: new Text('Saved Suggestions'),
            ),
            body: new ListView(children: divided),
          );
        },
      ),
    );
  }
複製代碼

分析: 1.使用Navigator.of(context).push的方式來處理跳轉,須要的參數是一個Route 2.建立頁面Route 3.返回一個新的裏面,裏面的body內容是一個ListView,展現的是_saved中讀取出來的數據

最終,全部代碼整合是以下的樣子:

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: 'Welcome to Flutter',
      theme: new ThemeData(
        primaryColor: Colors.red,
      ),
      home: RandomWords(),
    );
  }
}

class RandomWords extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return new RandomWordsState();
  }
}

class RandomWordsState extends State<RandomWords> {
  final _suggestions = <WordPair>[];
  final _saved = new Set<WordPair>();
  final _biggerFont = const TextStyle(fontSize: 18.0);

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text('Startup Name Generator'),
        actions: <Widget>[
          new IconButton(icon: new Icon(Icons.list), onPressed: _pushSaved),
        ],
      ),
      body: _buildSuggestions(),
    );
  }

  void _pushSaved() {
    Navigator.of(context).push(
      new MaterialPageRoute(
        builder: (context) {
          final tiles = _saved.map(
            (pair) {
              return new ListTile(
                title: new Text(
                  pair.asPascalCase,
                  style: _biggerFont,
                ),
              );
            },
          );
          final divided = ListTile.divideTiles(
            context: context,
            tiles: tiles,
          ).toList();

          return new Scaffold(
            appBar: new AppBar(
              title: new Text('Saved Suggestions'),
            ),
            body: new ListView(children: divided),
          );
        },
      ),
    );
  }

  Widget _buildSuggestions() {
    return new ListView.builder(
        padding: const EdgeInsets.all(16.0),
        itemBuilder: (context, i) {
          if (i.isOdd) return new Divider();
          final index = i ~/ 2;
          if (index >= _suggestions.length) {
            _suggestions.addAll(generateWordPairs().take(10));
          }
          return _buildRow(_suggestions[index]);
        });
  }

  Widget _buildRow(WordPair pair) {
    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);
          }
        });
      },
    );
  }
}

複製代碼

運行吧,就能看到最上方的效果。

謝謝觀看這篇文章,若是讓您發現了錯誤或者有好的建議,歡迎在下方評論給我留言。

相關文章
相關標籤/搜索