12_第一個Flutter程序

使用 package

在這一步中,你將開始使用一個名爲 english_words 的開源軟件包,其中包含數千個最經常使用的英文單詞以及一些實用功能。html

你能夠 在 pub.dartlang.org 上找到 english_words 軟件包以及其餘許多開源軟件包。shell

  1. pubspec 文件管理 Flutter 應用程序的 assets(資源,如圖片、package等)。 在pubspec.yaml 中,將 english_words(3.1.0或更高版本)添加到依賴項列表,以下面高亮顯示的行:
dependencies:
  flutter:
    sdk: flutter

  cupertino_icons: ^0.1.0
  english_words: ^3.1.0   # 新增了這一行
  1. 在Android Studio 的編輯器視圖中查看 pubspec 時,單擊右上角的 Packages get,這會將依賴包安裝到您的項目。您能夠在控制檯中看到如下內容:
flutter packages get
Running "flutter packages get" in startup_namer...
Process finished with exit code 0
  1. 在 lib/main.dart 中引入,以下所示:
import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';  // 新增了這一行

在您輸入時,Android Studio會爲您提供有關庫導入的建議。而後它將呈現灰色的導入字符串,讓您知道導入的庫截至目前還沒有被使用。app

接下來,咱們使用 English words 包生成文原本替換字符串"Hello World":框架

咱們須要進行以下更改:less

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(    // 這裏把以前的 "const" 換成了 "new".
          //child: const Text('Hello World'),   // 咱們不用這樣的方式生成文字了
          child: new Text(wordPair.asPascalCase),  // 這是新的文字生成方式
        ),
      ),
    );
  }
}

提示:「大駝峯式命名法」也稱爲 upper camel case 或 Pascal case,表示字符串中的每一個單詞(包括第一個單詞)都以大寫字母開頭。因此,uppercamelcase 會變成 UpperCamelCase。dom

  1. 若是你沒有把 Center 前面的修飾詞從 const 改爲 new 的話,系統就會報錯,由於這個時候它的子對象已經不是常量,那就不能再用 const 了,因此這裏 Center 和 Text 都須要使用 new 建立新的實例。

添加一個 Stateful widget

Stateless widgets 是不可變的,這意味着它們的屬性不能改變——全部的值都是 final。編輯器

Stateful widgets 持有的狀態可能在 widget 生命週期中發生變化,實現一個 stateful widget 至少須要兩個類:1)一個 StatefulWidget 類;2)一個 State 類,StatefulWidget 類自己是不變的,可是 State 類在 widget 生命週期中始終存在。ide

在這一步,你將添加一個 stateful widget(有狀態的控件)—— RandomWords,它會建立本身的狀態類 —— RandomWordsState,而後你須要將 RandomWords 內嵌到已有的無狀態的 MyApp widget。函數

  1. 建立一個最簡的 state 類,這個類能夠在任意地方建立而不必定非要在 MyApp 裏,咱們的示例代碼是放在 MyApp 類的最下面了:
class RandomWordsState extends State<RandomWords> {
  // TODO Add build method
}

注意一下 State<RandomWords> 的聲明。這代表咱們在使用專門用於 RandomWords 的 State 泛型類。應用的大部分邏輯和狀態都在這裏 —— 它會維護 RandomWords 控件的狀態。這個類會保存代碼生成的單詞對,這個單詞對列表會隨着用戶滑動而無限增加,另外還會保存用戶喜好的單詞對(第二部分),也即當用戶點擊愛心圖標的時候會從喜好的列表中添加或者移除當前單詞對。佈局

RandomWordsState 繼承自 RandomWords,咱們接下來會建立這個類。

  1. 添加有狀態的 RandomWords widget 到 main.dart,RandomWords widget 除了建立 State 類以外幾乎沒有其餘任何東西:
class RandomWords extends StatefulWidget {
  @override
  RandomWordsState createState() => new RandomWordsState();
}

在添加狀態類後,IDE 會提示該類缺乏 build 方法。接下來,您將添加一個基本的 build 方法,該方法經過將生成單詞對的代碼從 MyApp 移動到 RandomWordsState 來生成單詞對。

  1. 將 build 方法添加到 RandomWordState 中,以下所示:
class RandomWordsState extends State<RandomWords> {
  @override                                  // 新增代碼片斷 - 開始 ... 
  Widget build(BuildContext context) {
    final WordPair wordPair = new WordPair.random();
    return new Text(wordPair.asPascalCase);
  }                                          // ... 新增的代碼片斷 - 結束
}
  1. 以下所示,刪除 MyApp 裏生成文字的代碼:
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final WordPair 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(wordPair.asPascalCase), // 修改本行內容 
          child: new RandomWords(),                 // 修改爲本行代碼
        ),
      ),
    );
  }
}
  1. 熱重載(Hot reload)當前的工程,應用應該像以前同樣運行,每次熱重載或保存應用程序時都會顯示一個單詞對。

提示: 若是您嘗試熱重載,則可能會看到一條警告,考慮從新啓動當前應用:

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

由於重啓應用以後就能夠生效,故這多是誤報。

建立一個無限滾動的 ListView

在這一步中,您將擴展(繼承)RandomWordsState 類,以生成並顯示單詞對列表。 當用戶滾動時,ListView 中顯示的列表將無限增加。 ListView 的 builder 工廠構造函數容許您按需創建一個懶加載的列表視圖。

  1. 向 RandomWordsState 類中添加一個 _suggestions 列表以保存建議的單詞對,同時,添加一個 biggerFont 變量來增大字體大小 Also, add a _biggerFont variable for making the font size larger.

提示:在 Dart 語言中使用下劃線前綴標識符,會強制其變成私有。

class RandomWordsState extends State<RandomWords> {
  // 添加以下兩行
  final List<WordPair> _suggestions = <WordPair>[];
  final TextStyle _biggerFont = const TextStyle(fontSize: 18.0); 
  ...
}

接下來,咱們將向 RandomWordsState 類添加一個 _buildSuggestions() 函數,此方法構建顯示建議單詞對的 ListView。

ListView 類提供了一個 builder 屬性,itemBuilder 值是一個匿名回調函數, 接受兩個參數- BuildContext 和行迭代器 i。迭代器從 0 開始, 每調用一次該函數,i 就會自增 1,對於每一個建議的單詞對都會執行一次。該模型容許建議的單詞對列表在用戶滾動時無限增加。

  1. 向 RandomWordsState 類添加 _buildSuggestions() 函數,內容以下:
Widget _buildSuggestions() {
    return new ListView.builder(
      padding: const EdgeInsets.all(16.0),

      // 對於每一個建議的單詞對都會調用一次 itemBuilder,
      // 而後將單詞對添加到 ListTile 行中
      // 在偶數行,該函數會爲單詞對添加一個 ListTile row.
      // 在奇數行,該函數會添加一個分割線的 widget,來分隔相鄰的詞對。
      // 注意,在小屏幕上,分割線看起來可能比較吃力。

      itemBuilder: (BuildContext _context, int 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 int index = i ~/ 2;
        // 若是是建議列表中最後一個單詞對
        if (index >= _suggestions.length) {
        // ...接着再生成10個單詞對,而後添加到建議列表
          _suggestions.addAll(generateWordPairs().take(10));
        }
        return _buildRow(_suggestions[index]);
      }
    );
  }

對於每個單詞對,_buildSuggestions 函數都會調用一次 _buildRow。 這個函數在 ListTile 中顯示每一個新詞對,這使您在下一步中能夠生成更漂亮的顯示行,詳見本 codelab 的第二部分。

  1. 在 RandomWordsState 中添加 _buildRow 函數 :
Widget _buildRow(WordPair pair) {
    return new ListTile(
      title: new Text(
        pair.asPascalCase,
        style: _biggerFont,
      ),
    );
  }
  1. 更新 RandomWordsState 的 build 方法以使用 _buildSuggestions(),而不是直接調用單詞生成庫,代碼更改後以下:(使用 Scaffold 類實現基礎的 Material Design 佈局)
@override
  Widget build(BuildContext context) {
    //final wordPair = new WordPair.random(); // 刪掉 ... 
    //return new Text(wordPair.asPascalCase); // ... 這兩行

    return new Scaffold (                   // 代碼從這裏... 
      appBar: new AppBar(
        title: new Text('Startup Name Generator'),
      ),
      body: _buildSuggestions(),
    );                                      // ... 添加到這裏
  }
  1. 更新 MyApp 的 build 方法, changing the title, and changing the home to be a RandomWords widget.
@override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Startup Name Generator',
      home: new RandomWords(),
    );
  }
  1. 從新啓動你的項目工程應用,你應該看到一個單詞對列表。儘量地向下滾動,你將繼續看到新的單詞對。

向列表中添加圖標

在這部分,咱們將爲每一行添加一個心形的(收藏)圖標,下一步你將可以爲這個圖標加入點擊收藏的功能。

  1. 添加一個 _saved Set(集合)到 RandomWordsState,這個集合存儲用戶喜歡(收藏)的單詞對。 在這裏,Set 比 List 更合適,由於 Set 中不容許重複的值。
class RandomWordsState extends State<RandomWords> {
  final List<WordPair> _suggestions = <WordPair>[];
  final Set<WordPair> _saved = new Set<WordPair>();   // 新增本行
  final TextStyle _biggerFont = const TextStyle(fontSize: 18.0);
  ...
}
  1. 在 _buildRow 方法中添加 alreadySaved 來檢查確保單詞對尚未添加到收藏夾中。
Widget _buildRow(WordPair pair) {
  final bool alreadySaved = _saved.contains(pair);  // 新增本行
  ...
}

同時在 _buildRow() 中, 添加一個心形 ❤️ 圖標到 ListTiles以啓用收藏功能。接下來,你就能夠給心形 ❤️ 圖標添加交互能力了。

  1. 向列表添加圖標,以下所示:
Widget _buildRow(WordPair pair) {
  final bool 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,
    ),                    // ... 新增代碼結束
  );
}
  1. 熱重載應用,你如今能夠在每一行看到心形 ❤️圖標️,但它們尚未交互。

添加交互

在這部分,咱們將爲剛剛的心形 ❤️圖標增長交互,當用戶點擊列表中的條目,切換其"收藏"狀態,並將該詞對添加到或移除出"收藏夾"。

爲了作到這個,咱們在 _buildRow 中讓心形 ❤️圖標變得能夠點擊。若是單詞條目已經添加到收藏夾中, 再次點擊它將其從收藏夾中刪除。小心形 ❤️圖標被點擊時,函數調用 setState() 通知框架狀態已經改變。

  1. 增長 onTap 方法,以下所示:
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: () {      // 增長以下 9 行代碼...
      setState(() {
        if (alreadySaved) {
          _saved.remove(pair);
        } else {
          _saved.add(pair);
        }
      });
    },               // ... 一直到這裏
  );
}

提示: 在 Flutter 的響應式風格的框架中,調用 setState() 會爲 State 對象觸發 build() 方法,從而致使對 UI 的更新

熱重載應用,你就能夠點擊任何一行測試收藏或取消收藏功能,你的點擊同時自帶 Material Design 裏的水波動畫特效。

導航到新頁面

在這一步中,您將添加一個顯示收藏夾內容的新頁面(在 Flutter 中稱爲路由[route])。您將學習如何在主路由和新路由之間導航(切換頁面)。

在 Flutter 中,導航器管理應用程序的路由棧。將路由推入(push)到導航器的棧中,將會顯示更新爲該路由頁面。 從導航器的棧中彈出(pop)路由,將顯示返回到前一個路由。

接下來,咱們在 RandomWordsState 的 build 方法中爲 AppBar 添加一個列表圖標。當用戶點擊列表圖標時,包含收藏夾的新路由頁面入棧顯示。

  1. 將該圖標及其相應的操做添加到 build 方法中:
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: const Icon(Icons.list), onPressed: _pushSaved),
        ],                      // ... 代碼新增結束
      ),
      body: _buildSuggestions(),
    );
  }
  ...
}

提示: 某些 widget 屬性須要單個 widget(child),而其它一些屬性,如 action,須要一組widgets(children),用方括號 [] 表示。

  1. 在 RandomWordsState 這個類裏添加 _pushSaved() 方法:
class RandomWordsState extends State<RandomWords> {
  ...
  // 新增代碼開始
  void _pushSaved() {
  }
  // 新增代碼結束 
}
  1. 熱重載應用,列表圖標()將會出如今導航欄中。如今點擊它不會有任何反應,由於 _pushSaved 函數仍是空的。

接下來,(當用戶點擊導航欄中的列表圖標時)咱們會創建一個路由並將其推入到導航管理器棧中。此操做會切換頁面以顯示新路由,新頁面的內容會在 MaterialPageRoute 的 builder 屬性中構建,builder 是一個匿名函數。

  1. 添加 Navigator.push 調用,這會使路由入棧(之後路由入棧均指推入到導航管理器的棧)
void _pushSaved() {
  Navigator.of(context).push(
  );
}

接下來,添加 MaterialPageRoute 及其 builder。 如今,添加生成 ListTile 行的代碼,ListTile 的 divideTiles() 方法在每一個 ListTile 之間添加 1 像素的分割線。 該 divided 變量持有最終的列表項,並經過 toList()方法很是方便的轉換成列表顯示。

  1. 添加以下所示的代碼:
void _pushSaved() {
  Navigator.of(context).push(
    new MaterialPageRoute<void>(   // 新增以下20行代碼 ...
      builder: (BuildContext context) {
        final Iterable<ListTile> tiles = _saved.map(
          (WordPair pair) {
            return new ListTile(
              title: new Text(
                pair.asPascalCase,
                style: _biggerFont,
              ),
            );
          },
        );
        final List<Widget> divided = ListTile
          .divideTiles(
            context: context,
            tiles: tiles,
          )
          .toList();
      },
    ),                           // ... 新增代碼結束
  );
}

builder 返回一個 Scaffold,其中包含名爲"Saved Suggestions"的新路由的應用欄。新路由的body 由包含 ListTiles 行的 ListView 組成;每行之間經過一個分隔線分隔。

  1. 添加水平分隔符,以下代碼所示:
void _pushSaved() {
  Navigator.of(context).push(
    new MaterialPageRoute<void>(
      builder: (BuildContext context) {
        final Iterable<ListTile> tiles = _saved.map(
          (WordPair pair) {
            return new ListTile(
              title: new Text(
                pair.asPascalCase,
                style: _biggerFont,
              ),
            );
          },
        );
        final List<Widget> divided = ListTile
          .divideTiles(
            context: context,
            tiles: tiles,
          )
              .toList();

        return new Scaffold(         // 新增 6 行代碼開始 ...
          appBar: new AppBar(
            title: const Text('Saved Suggestions'),
          ),
          body: new ListView(children: divided),
        );                           // ... 新增代碼段結束.
      },
    ),
  );
}
  1. 熱重載應用程序,點擊列表項收藏一些項,點擊列表圖標,在新的 route(路由)頁面中顯示收藏的內容。Navigator(導航器)會在應用欄中自動添加一個"返回"按鈕,無需調用Navigator.pop,點擊後退按鈕就會返回到主頁路由。

修改主題

這一部分,咱們將會一塊兒修改應用的主題。Flutter 裏咱們使用 theme 來控制你應用的外觀和風格,你可使用默認主題,該主題取決於物理設備或模擬器,也能夠自定義主題以適應您的品牌。

你能夠經過配置 ThemeData 類輕鬆更改應用程序的主題,目前咱們的應用程序使用默認主題,下面將更改 primaryColor 顏色爲白色。

  1. 在 MyApp 這個類裏修改顏色:
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(),
    );
  }
}
  1. 熱重載應用。 你會發現,整個背景將會變爲白色,包括 app bar(應用欄)。

一個小練習,你能夠看一下 ThemeData 的文檔,添加其餘屬性來更多改變 UI 樣式。Material library 中的 Colors 類提供了許多可使用的顏色常量, 你可使用熱重載來快速簡單地嘗試、實驗。

相關文章
相關標籤/搜索