【Flutter】編寫第一個 Flutter 應用

第1步:創建初始Flutter應用

創建一個簡單的 Flutter 應用。主要編輯 Dart 代碼所在的 lib / main.dart

  1. 替換 lib / main.dart 。
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'),
        ),
      ),
    );
  }
}
  1. 運行應用程序。現在應該可以看到下面的頁面。


    18288956071032318.jpg

第2步:使用外部 package

在這一步,將使用名為 english_words 的開源軟件包 ,其中包含數千個最常用的英文單詞以及一些實用功能。
可以在 pub.dartlang.org 上找到 english_words 軟件包以及其他許多開源軟件包。

  1. pubspec 文件管理著 Flutter 應用程序的靜態資源文件(assets)。 在 pubspec.yaml 文件中, 將 english_words(3.1.0或更高版本)添加到依賴列表。新的一行高亮如下:
dependencies:
  flutter:
    sdk: flutter
 
  cupertino_icons: ^0.1.0
  english_words: ^3.1.0 // 新增的
  1. 在 Android Studio 的 editor 視圖中查看 pubspec 時, 點擊右上角的 Packages get ,將把 package 拉取到項目中。在控制臺中看到以下內容:
flutter packages get
Running "flutter packages get" in startup_namer...
Process finished with exit code 0
  1. lib/main.dart 中,為 english_words 添加導入:
import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';// 新增的
  1. 改用英文單詞的 package 來生成文本,而不是字符串 “Hello World” 。
    對代碼進行以下更改,如所示:
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.
        ),
      ),
    );
  }
}
  1. 運行:


    248652834188319698.jpg

第3步:添加有狀態的widget

Stateless widgets 是不可改變的,這意味著它們的屬性不能改變——所有的值都是 final 的。

Statefulwidget 在其生命周期保持的狀態可能會變化,實現一個有狀態的 widget 至少需要兩個類:StatefulWidgets類和State類,其中StatefulWidgets類創建了一個State類的實例。StatefulWidget類本身是不可變的,但State類可存在于Widget的整個生命周期中。

在這一步,將添加一個有狀態的 RandomWords widget ,它可以創建其 State 類 RandomWordsState 。 State 類會為 widget 保存被推薦和被收藏的詞組。

  1. 將有狀態的 RandomWords widget 添加到 main.dart 。它可以在 MyApp 類之外的任何位置使用,但當前將把它放在文件底部。 RandomWords widget 除了創建 State 類之外幾乎沒有任何其他代碼:
class RandomWords extends StatefulWidget {
  @override
  createState() => new RandomWordsState();
}
  1. 添加 RandomWordsState 類。這個類保存了 RandomWords widget 的狀態,該應用程序的大部分代碼都放在該類中。這個類將保存隨著用戶的滑動操作而生成的無限增長的詞組,以及保存用戶收藏的詞組,用戶通過觸發心形圖標來添加或刪除收藏的詞組列表。

添加 state 類之后,必須有 build 方法,并將生成單詞的代碼行從 MyApp 類移動到 RandomWordsState 類的 build 方法中,生成詞組。

class RandomWordsState extends State<RandomWords> {
  @override
  Widget build(BuildContext context) {
    final wordPair = new WordPair.random();
    return new Text(wordPair.asPascalCase);
  }
}
  1. 從 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(
          // [圖片上傳中...(369675901483756576.jpg-18adf7-1531203134196-0)]
child: new Text(wordPair.asPascalCase), // Change the highlighted text to...
          child: new RandomWords(), // ... this highlighted text
        ),
      ),
    );
  }
}
  1. 重啟應用:


    369675901483756576.jpg

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

在這一步,可以擴展 RandomWordsState 類,生成并展示詞組列表。當用戶滑動列表,ListView widget 中顯示的列表將無限增長。

  1. _suggestions 變量向 RandomWordsState 類中添加一個數組列表,用來保存推薦詞組。 該變量以下劃線(_)開頭,在 Dart 語言中使用下劃線前綴表示強制私有。

    此外,添加一個 biggerFont 變量來增大字體。

    class RandomWordsState extends State<RandomWords> {
      final _suggestions = <WordPair>[];
    
      final _biggerFont = const TextStyle(fontSize: 18.0);
      ...
    }
    
  2. 向 RandomWordsState 類添加一個 _buildSuggestions() 函數,用于構建一個顯示詞組的 ListView 。

    ListView 類提供了一個 itemBuilder 屬性,這是一個工廠 builder 并作為匿名函數進行回調。它有兩個傳入參數— BuildContext 上下文和行迭代器 i 。對于每個推薦詞組都會執行一次函數調用,迭代器從 0 開始,每調用一次函數就累加 1 。這個模塊允許推薦列表在用戶滑動時無限增長。

    添加如下高亮代碼行:

    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 函數都調用一次 _buildRow 函數。這個函數每次會在一個 ListTile widget 中展示一條新詞組,這將在下一步操作中,使一行數據更有表現力。

    添加 _buildRow 函數到 RandomWordsState 類中:

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

    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 類進行統一管理,這樣在下一步操作中,可以使用戶從一個頁面導航到另一頁面時,更方便的更改應用欄中的頁面名稱。

    用下面高亮的 build 方法替換原始代碼:

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

重啟應用程序,將可以看到一個詞組清單。盡量向下滑動,將繼續看到新的詞組。


74782939917607052.jpg

第5步:添加可交互性

在這一步,將為每一行添加可點擊的心形圖標。當用戶點擊列表中的條目,切換其“收藏”狀態,詞組就會添加到收藏欄,或從已保存詞組的收藏欄中刪除。

  1. 添加一個 Set 集合 _saved 到 RandomWordsState 類。保存用戶收藏的詞組。Set 集合比 List 更適用于此,因為它不允許重復元素。

    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() 的 ListTiles widget 中,添加一個心形圖標來使用收藏功能,隨后將添加與心形圖標進行交互的功能。

    添加以下高亮代碼行:

    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. 重啟應用。現在應該可以在每一行看到心形圖標,但還沒有交互功能。

  5. _buildRow 函數中使心形可點擊。如果詞條已經被加入收藏,再次點擊它將從收藏中刪除。當心形圖標被點擊,函數將調用 setState() 通知應用框架state已經改變。

    添加高亮代碼行:

    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的更新。

熱重載應用。可以點擊任意一行來收藏或取消收藏條目。 請注意,點擊一行可以產生從心形圖標展開的潑墨動畫效果。

733844901585464410.jpg

第6步:導航到新頁面

在這一步,將添加一個顯示收藏夾的新頁面(在 Flutter 中稱為 route(路由))。你將學習如何在主路由和新路由之間導航。

在 Flutter 中, Navigator 管理著包含了應用程序所有路由的一個堆棧。將一個路由push到 Navigator 的堆棧,將顯示更新為新頁面路由。將一個路由 pull 出 Navigator 的堆棧,顯示將返回到前一個頁面路由。

  1. 在 RandomWordsState 類的 build 方法中,向 AppBar 添加一個列表圖標。當用戶點擊列表圖標時,包含了已收藏條目的新路由將被 push 到 Navigator 堆棧并顯示新頁面。

    小貼士: 某些 widget 屬性使用獨立 widget(child) 和其他屬性例如 action 組成一個子 widget 數組(children),用方括號([])表示。

    將該圖標及其相應的 action 操作添加到 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: new Icon(Icons.list), onPressed: _pushSaved),
            ],
          ),
          body: _buildSuggestions(),
        );
      }
      ...
    }
    
  2. 向 RandomWordsState 類添加一個 _pushSaved() 函數。

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

    重新加載應用程序。列表圖標將出現在應用欄中。點擊它不會有任何響應,因為 _pushSaved 這個函數還未實現功能。

  3. 當用戶點擊應用欄中的列表圖標時,將建立一個新路由并 push 到 Navigator 的路由堆棧中,這個操作將改變界面顯示,展示新的路由頁面。

    新頁面的內容使用匿名函數在 MaterialPageRoute widget的builder屬性中創建。

    將函數調用添加到 Navigator.push 中作為參數,如高亮代碼所示,將路由 push 到 Navigator 的堆棧中。

    void _pushSaved() {
      Navigator.of(context).push(
      );
    }
    
  4. 添加 MaterialPageRoute widget 及其 builder 屬性。先添加生成 ListTile widget 的代碼。其中 ListTile 的 divideTiles() 方法為每個 ListTile widget 之間添加水平間距。divided變量保存最終生成的所有行,并用 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 widget ,其中包含了應用欄標題名為 “Saved Suggestions” 的新路由頁面。新頁面的body屬性由包含多個 ListTile widget 的 ListView 組成。

    添加如下高亮代碼:

    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. 熱重載應用程序。對一些條目點擊收藏,然后點擊應用欄右側的列表圖標。顯示出包含收藏夾列表的新頁面。注意,Navigator 會在應用欄左側添加一個“返回”按鈕。不必再顯式實現 Navigator.pop 。點擊返回按鈕會返回到主頁面。

698831790289287084.jpg
499341336619050828.jpg

第7步:使用主題更改UI

在最后一步中,將使用該應用的主題。 theme 控制的是應用程序的觀感。可以使用默認主題,該主題取決于使用的模擬器或真機,也可以自定義主題以反映你的品牌。

  1. 可以通過配置 ThemeData 類輕松更改應用程序的主題。應用程序目前使用默認主題,現在將更改主要顏色為白色。

    將高亮代碼添加到 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(),
        );
      }
    }
    
  2. 熱重載應用程序。請注意,整個背景都是白色的,甚至包括應用欄。

  3. 作為讀者的練習,可使用 ThemeData 來改變用戶界面的其他方面。 Material 庫中的 Colors 類提供了多種可以使用的顏色常量,而熱重載使用戶界面的修改變得簡單快捷。

390394729575969454.jpg
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,333評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,491評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,263評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,946評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,708評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,186評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,409評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,939評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,774評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,976評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,209評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,641評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,872評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,650評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,958評論 2 373

推薦閱讀更多精彩內容