編寫第一個Flutter App(翻譯)

博客搬遷至https://blog.wangjiegulu.comhtml

RSS訂閱:https://blog.wangjiegulu.com/feed.xmlgit

如下代碼 Github 地址:https://github.com/wangjiegulu/flutter_test_01github

編寫你的第一個Flutter App

原文:https://flutter.io/get-started/codelab/web

這個你建立第一個Flutter app的指南。若是你熟悉面向對象的代碼,基本的編程概念,好比變量,循環,和條件,你就能夠完成本教程。你不須要以前有Dart或者手機的編程經驗。編程

你將構建什麼數組

你將要實現一個簡單的手機 app,爲一個初創公司去生成一些推薦的名字。用戶能夠選擇和取消選擇這些名字,並保存最好的一些名字。代碼一次生成10個名字。當用戶滾動時,新的一批名字就會被生成。用戶能夠點擊 app bar 右上角的按鈕進入一個新的頁面來僅展現被喜歡的名字。app

Gif 動圖展現了 app 完成以後的運行效果。框架

你將學到什麼less

  • Flutter app 的基礎結構。
  • 查詢和使用包來擴展特性。
  • 使用熱重載來實現快速的開發週期。
  • 怎麼去實現一個 stateful widget 。
  • 怎麼去建立一個無限,懶加載的列表。
  • 怎麼去建立和導航到第二個頁面。
  • 怎麼去使用 Theme 來改變 app 的外觀。

你將使用什麼dom

  • Flutter SDK:Flutter SDK 包括 Flutter 的引擎,framework, widget ,工具和 Dart SDK。這個 codelab 須要 v0.1.4 或者更新。
  • Android Studio IDE:這個 codelab 具有 Android Studio IDE,可是你也可使用其它的 IDE,或者使用命令行工做。
  • 你的 IDE 插件:你的 IDE 上面必須分別安裝 Flutter 和 Dart 插件。除了 Android Studio,Flutter 和 Dart 插件在 VS CodeIntelliJ IDE。

關於怎麼搭建你的環境,能夠在 查看更多信息。

第1步:建立啓動 Flutter app

根據 開始你的第一個 Flutter app 的介紹,建立一個簡單,模版的 Flutter app。給項目取名爲 startup_namer (替換掉 myapp)。您將修改這個 app 來建立完成的 app。

在這個 codelab 中,你主要編輯 dart 代碼存放處的 lib/main.dart

提示:當複製代碼到你的 app 中,縮進可能會歪斜。你可使用 Flutter 工具來自動修正它們:

  • Android Studio / IntelliJ IDEA: 在 dart 代碼上右鍵並選擇 Reformat Code with dartfmt
  • VS Code: 右鍵並選擇 Format Document
  • Ternimal: 運行 flutter format
  1. 替換 lib/main.dart。

    刪除 lib/main.dart 中的全部代碼。使用下面的代碼進行替換,它會在屏幕的中央展現 "Hello World"。

    import 'package:flutter/material.dart';
    
    void main() => runApp(new MyApp());
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return new MaterialApp(
          title: 'Welcome to Flutter',
          home: new Scaffold(
            appBar: new AppBar(
              title: new Text('Welcome to Flutter'),
            ),
            body: new Center(
              child: new Text('Hello World'),
            ),
          ),
        );
      }
    }
  2. 運行App,你將會看到以下的屏幕

觀察

  • 這個例子建立了一個 Material app。Material 是手機和 web 上的標準的設計語言。Flutter 提供了豐富的 Material widget 。
  • main 方法制定了一個寬箭頭(=>)標誌,這是一行函數或者方法的簡寫。
  • App 繼承了 StatelessWidget,這使得 app 自己稱爲了一個 widget。在 Flutter 中,幾乎全部一切都是 widget,包括 alignment, padding, 和 layout。
  • Material 庫中的 Scaffold,提供了一個默認的 app bar,title,和一個 body 屬性,它持有了主頁面的 widget 樹。widget 的子樹可能至關複雜。
  • Widget 的主要的工做是提供一個 build() 方法,它描述瞭如何根據其餘較低級別的 widget 顯示 widget。
  • 這個例子中的 widget 樹的構成是一箇中心的 widget 包含了一個文本的子 child widget。中心 widget 將它的 widget 子樹對齊到屏幕的中心。

第2步:使用外部包

在這一步,我將使用一個名爲 english_words 的開源包,它包含了幾千個最經常使用的英文單詞和經常使用的工具方法。

pub.dartlang.org,你能夠找到 english_words,以及不少其它的開源包。

  1. pubspec 文件爲 Flutter app 管理 assets。在 pubspec.yaml,增長 english_words (3.1.0或者更高) 到依賴列表。新增行在下面已被高亮:

    dependencies:
    flutter:
    sdk: flutter
    
    cupertino_icons: ^0.1.0
    english_words: ^3.1.0
  2. 在 Android Studio’s editor 視圖查看 pubspec,點擊右上角的 Packages get。這會把包拉取到你的項目中。你會在控制檯上看到如下信息:

    flutter packages get
    Running "flutter packages get" in startup_namer...
    Process finished with exit code 0
  3. lib/main.dart,增長一個 english_words 的導入,就如高亮展現的那樣:

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

    因爲你的輸入,Android Studio 針對庫會給你一些導入的建議。而後將導入字符串呈現爲灰色,讓你知道倒入的庫你沒有使用它(目前爲止)。

  4. 使用 English words 包生成文本,用來替換掉以前的 "Hello World" 字符串。

    提示:"Pascal case" (也稱爲 「大駝峯式命名法」),表示字符串中的每一個單詞,包括第一個單詞,首字母大寫。因此,「uppercamelcase」 就變成 「UpperCamelCase」。

    作如下改變,以下面高亮處:

    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) {
        final wordPair = new WordPair.random();
        return new MaterialApp(
          title: 'Welcome to Flutter',
          home: new Scaffold(
            appBar: new AppBar(
              title: new Text('Welcome to Flutter'),
            ),
            body: new Center(
              //child: new Text('Hello World'), // Replace the highlighted text...
              child: new Text(wordPair.asPascalCase),  // With this highlighted text.
            ),
          ),
        );
      }
    }
  5. 若是 app 正在運行中,使用熱重載按鈕()來更新運行中的 app。每一次你點擊了熱重載,或者保存了項目,你將會看見不一樣的詞對,它在運行的 app 中是隨機的。這是由於詞對在 build 方法中被生成。在每次 MaterialApp 須要渲染或者在 Flutter Inspector 中切換平臺的時候。

問題?

若是你的 app 沒有正確運行,排查錯誤。若是須要,請使用如下連接的代碼來追蹤。


第3步:增長一個 Stateful widget

Stateless widget 是不可改變的,意味着它們的屬性不能被修改 —— 全部值都是 final 的。

Stateful widgets 維護了狀態,它可能會在 widget 的生命週期內被修改。實現一個 statful widget 須要兩個類:1)一個 StatefulWidget 類,用來建立一個實例 2)一個 State 類。StatefulWidget 類自己是不可變的,但 State 類在整個 widget 的生命週期中保持不變。

在這一步中,你將會增長一個 stateful widget,RandomWords,增長它的 State class,RandomWordsState。State 類中將最終維護這個 widget 中推薦喜歡的詞對。

  1. 增長 stateful RandomWords widget 到你的 main.dart 中。它能夠被放在任何地方,甚至 MyApp 以外,可是這裏的解決方案放在了文件的底部。RandomWords widget 除了建立它的 State 類沒有什麼特別的。

    class RandomWords extends StatefulWidget {
      @override
      createState() => new RandomWordsState();
    }
  2. 增長 RandomWordsState 類。app 的大部分代碼將會寫在這個類中,它維護拉這個 widget 中的 state。這個類會保存生成的詞對,會被用戶無限滾動,用戶經過列表切換中的心圖標來添加或刪除它們。

    你將逐步編寫這個類。做爲開始,經過如下高亮的文原本建立一個最小的 class:

    class RandomWordsState extends State<RandomWords> {
    }
  3. 在增長了 state class 以後,IDE 警告這個類缺乏一個 build 方法。而後,你將增長一個基本的 build 方法經過從 MyApp 轉移生成詞對的代碼到 RandomWordsState 來生成詞對:

    class RandomWordsState extends State<RandomWords> {
      @override
      Widget build(BuildContext context) {
        final wordPair = new WordPair.random();
        return new Text(wordPair.asPascalCase);
      }
    }
  4. 經過如下高亮改變,從 MyApp 中移除生成詞對的代碼:

    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        final wordPair = new WordPair.random();  // Delete this line
    
        return new MaterialApp(
          title: 'Welcome to Flutter',
          home: new Scaffold(
            appBar: new AppBar(
              title: new Text('Welcome to Flutter'),
           ),
            body: new Center(
              //child: new Text(wordPair.asPascalCase), // Change the highlighted text to...
              child: new RandomWords(), // ... this highlighted text
            ),
          ),
        );
      }
    }

重啓 app,若是你嘗試去熱重載,你可能會看到一個警告:

Reloading...
Not all changed program elements ran during view reassembly; consider
restarting.

這多是誤報,但考慮從新啓動以確保你的更改反映在 app UI 中。

app 應該會跟之前同樣,每次你熱重載或者保存的時候展現一個詞對。

問題?

若是你的 app 沒有正確運行,排查錯誤。若是須要,請使用如下連接的代碼來追蹤。


第4步:建立一個無限滾動的 ListView

在這一步,你將擴展 RandomWordsState 來生成和展現一個列表的詞對。當用戶滾動時,展現在 ListView widget 的列表會無限滾動。ListView 的 builder factory 構造方法容許你根據須要實現懶加載。

  1. 在 RandomWordsState 類中增長一個 _suggestions list 來保存推薦的詞對。注意變量如下劃線(_)開頭。在 Dart 語言中,如下劃線做爲前綴標誌表明私有。

    也增長一個 biggerFont 變量來使字體變大。

    class RandomWordsState extends State<RandomWords> {
      final _suggestions = <WordPair>[];
    
      final _biggerFont = const TextStyle(fontSize: 18.0);
      ...
    }
  2. 在 RandomWordsState 類中增長一個 _buildSuggestions() 方法。這個方法構建展現詞對的 ListView。

    ListView 類提供了一個 builder 屬性,itemBuilder,以匿名方法的方式指定一個工廠構造器和回調方法。兩個參數會被傳入到方法中 —— BuildContext,和行迭代器,i。迭代器從0開始,每一次方法被調用時遞增,每一個推薦詞對配對一次。這個模型容許在用戶滾動時推薦列表無限滾動。

    增長如下高亮行:

    class RandomWordsState extends State<RandomWords> {
      ...
      Widget _buildSuggestions() {
        return new ListView.builder(
          padding: const EdgeInsets.all(16.0),
          // The itemBuilder callback is called, once per suggested word pairing,
          // and places each suggestion into a ListTile row.
          // For even rows, the function adds a ListTile row for the word pairing.
          // For odd rows, the function adds a Divider widget to visually
          // separate the entries. Note that the divider may be difficult
          // to see on smaller devices.
          itemBuilder: (context, i) {
            // Add a one-pixel-high divider widget before each row in theListView.
            if (i.isOdd) return new Divider();
    
            // The syntax "i ~/ 2" divides i by 2 and returns an integer result.
            // For example: 1, 2, 3, 4, 5 becomes 0, 1, 1, 2, 2.
            // This calculates the actual number of word pairings in the ListView,
            // minus the divider widgets.
            final index = i ~/ 2;
            // If you've reached the end of the available word pairings...
            if (index >= _suggestions.length) {
              // ...then generate 10 more and add them to the suggestions list.
              _suggestions.addAll(generateWordPairs().take(10));
            }
            return _buildRow(_suggestions[index]);
          }
        );
      }
    }
  3. _buildSuggestions 方法在每一個詞配對時調用。這個方法在一個 ListTile 中展現一個新的配對,在下一步中它容許你在行中增長交互。

    RandomWordsState 中增長一個 _buildRow 方法:

    class RandomWordsState extends State<RandomWords> {
      ...
    
      Widget _buildRow(WordPair pair) {
        return new ListTile(
          title: new Text(
            pair.asPascalCase,
            style: _biggerFont,
          ),
        );
      }
    }
  4. 使用 _buildSuggestions() 來更新 RandomWordsState 的 build 方法,而不是直接調用生成詞對的庫。修改如下高亮改變:

    class RandomWordsState extends State<RandomWords> {
      ...
      @override
      Widget build(BuildContext context) {
        final wordPair = new WordPair.random(); // Delete these two lines.
        Return new Text(wordPair.asPascalCase);
        return new Scaffold (
          appBar: new AppBar(
            title: new Text('Startup Name Generator'),
          ),
        body: _buildSuggestions(),
        );
      }
      ...
    }
  5. 更新 MyApp 的 build 方法。在 MyApp 中移除 Scaffold 和 AppBar 實例。這些應該由 RandomWordsState 去管理,這讓在下一步中導航到另外一個頁面時修改 app bar 的名字更簡單。

    用下面高亮的 build 方法替換原生的方法:

    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return new MaterialApp(
          title: 'Startup Name Generator',
          home: new RandomWords(),
        );
      }
    }

重啓 app,你將看到一個詞對列表。按你想要的去滾動列表,你會看到新的詞對。

問題?

若是你的 app 沒有正確運行,排查錯誤。若是須要,請使用如下連接的代碼來追蹤。


第5步:增長交互

在這一步,你將在沒行增長一個可點擊的心型圖標。當用戶點擊 list 中的每行時,切換它的 「喜歡」 狀態,這會觸發詞對在保存的集合中增長或者刪除。

  1. 在 RandomWordsState 中增長一個 _saved 集合。這個集合存儲了用戶喜歡了的詞對。集合首選 List,由於正確的實現是 Set 不容許重複的條目。

    class RandomWordsState extends State<RandomWords> {
      final _suggestions = <WordPair>[];
    
      final _saved = new Set<WordPair>();
    
      final _biggerFont = const TextStyle(fontSize: 18.0);
      ...
    }
  2. _buildRow 方法中,增長一個 alreadySaved 檢查來確保詞對是否已經添加到喜歡集合中了。

    Widget _buildRow(WordPair pair) {
      final alreadySaved = _saved.contains(pair);
      ...
    }
  3. _buildRow(),增長一個心型的圖標到 ListTile 來啓用喜歡狀態。稍後,你會在這個心型圖標上增長一個交互。

    增長如下高亮:

    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,
        ),
      );
    }
  4. 重啓 app,如今你會看到每行都有心型圖標,可是它們還不能交互。

  5. _buildRow 方法中讓心形圖標可點擊。若是一個詞對已經被添加到喜歡集合,再次點擊會從喜歡集合中刪除。小心形圖標被點擊,調用setState()方法來通知系統狀態被改變。

    增長高亮行:

    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);
            }
          });
        },
      );
    }

提示:在 Flutter 響應式風格框架中,調用 setState() 觸發 State 對象的 build() 方法的調用,結果更新在 UI 中。

熱重載 app,你應該會看到點擊任意行來喜歡,取消喜歡條目。注意,點擊一行會生成從心型圖標發出的隱式墨跡飛濺動畫。

問題?

若是你的 app 沒有正確運行,排查錯誤。若是須要,請使用如下連接的代碼來追蹤。


第6步:導航到新的頁面

在這一步,你將增長一個新的頁面(在 Flutter 被稱爲 router)用來展現喜歡的集合。你將會學習到怎麼從首頁導航到一個新的頁面。

在 Fluter,Navigator 管理包含了 app 路由頁面的棧。壓入一個頁面到 Navigator 的棧,更新展現那個頁面。從 Navigator 彈出一個頁面,返回展現上一個頁面。

  1. 在 RandomWordsState 的 build 方法中增長一個列表圖標到 AppBar 上。當用戶點擊這個列表圖標,一個包含了喜歡的條目的新頁面被壓入到 Navigator,展現圖標。

    提示:一些widget屬性接收單個 widget(child),其它的屬性,如 action,接收一個數組 widgets(children),經過中括號([])標明。

    在 build 方法中增長圖標和它對應的 action:

    class RandomWordsState extends State<RandomWords> {
      ...
      @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(),
        );
      }
      ...
    }
  2. 在 RandomWordsState 類中增長一個 _pushSaved() 方法。

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

    熱重載 app,列表圖標出如今 app bar 上。點擊它還不會發生任何事情,由於 _pushSaved 方法是空的。

  3. 當用戶點擊 app bar 上的列表圖標,構建一個頁面並壓入 Navigator 的棧中。這個 action 會改變屏幕去展現新的頁面。

    新頁面的內容在 MaterialPageRoute 的 builder 屬性中經過匿名方法構建。

    增長調用 Navigator.push,以下高亮代碼展現,把頁面壓入到 Navigator 的棧裏。

    void _pushSaved() {
      Navigator.of(context).push(
      );
    }
  4. 增長 MaterialPageRoute 和它的 builder。如今,增長生成 ListTile 行的代碼。ListTile 的 divideTiles() 方法在每一個 ListTile 之間添加水平間距。分割變量保存最後一行,由 convienice 函數 toList() 轉換爲列表。

    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();
          },
        ),
      );
    }
  5. builder 屬性返回一個 Scaffold,包含了新頁面的 app bar,名爲 「Save Suggestions」。新頁面 body 的構成是一個 ListView 包含了 ListTiles 行;每行由分隔符分割。

    增長如下高亮代碼:

    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),
            );
          },
        ),
      );
    }
  6. 熱重載 app,喜歡其中的一些條目並點擊 app bar 上的列表圖標。新頁面展現出來,且包含了喜歡的條目。注意 Navigator 在 app bar 上增長了一個 「Back」 按鈕。你不須要明確地實現 Navigator.pop。點擊返回按鈕來返回首頁。




問題?

若是你的 app 沒有正確運行,排查錯誤。若是須要,請使用如下連接的代碼來追蹤。


第7步:使用 Theme 來改變 UI

在這一步,你將玩轉 app 的 theme。Theme會控制你的 app 的視覺和感受。你可使用默認的 theme,這依賴於物理設備或者模擬器,或者你能夠自定義 theme 來反映出你的品牌。

  1. 你能夠很簡單地經過配置 ThemeData 類來改變 app 的主題。你的 app當前使用的是默認的主題,可是你將修改主要顏色爲白色。

    經過增長高亮的代碼到 MyApop 來改變 app 的主題爲白色:

    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return new MaterialApp(
          title: 'Startup Name Generator',
          theme: new ThemeData(
            primaryColor: Colors.white,
          ),
          home: new RandomWords(),
        );
      }
    }
  2. 熱重載 app,注意,整個背景都是白色的,甚至是 app bar。

  3. 做爲讀者的聯繫,使用 ThemeData 來改變 UI 的其它方面。Material 庫中的 Colors 類提供了不少顏色常量可使用,而後熱重載使得 UI 實驗變的又快又簡單。



問題?

若是你的 app 沒有正確運行,排查錯誤。若是須要,請使用如下連接的代碼來追蹤。


幹得不錯

你已寫了一個運行在 iOS 和 Android 的具備交互性的 Flutter app。在這個 codelab,你已經:

  • 從頭建立了一個 Flutter app。
  • 編寫 Dart 代碼。
  • 使用外部第三方庫。
  • 使用熱重載來進行快速的開發週期。
  • 實現了 stateful widget,給你的 app 增長了互動性。
  • 使用 ListView 和 ListTiles 建立了一個懶加載,無限滾動的列表。
  • 建立了一個頁面,且增長了在主頁和新的頁面以前移動的邏輯。
  • 學習改變 app 主題外觀和主題
相關文章
相關標籤/搜索