編寫第一個Flutter App(翻譯)


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

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

以下代碼 Github 地址:https://github.com/wangjiegulu/flutter_test_01

編寫你的第一個Flutter App

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

這個你創建第一個Flutter app的指南。如果你熟悉面向對象的代碼,基本的編程概念,比如變量,循環,和條件,你就可以完成本教程。你不需要之前有Dart或者手機的編程經驗。

你將構建什么

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

Gif 動圖展示了 app 完成之后的運行效果。

你將學到什么

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

你將使用什么

  • 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 主題外觀和主題


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM