第1步:創建初始Flutter應用
創建一個簡單的 Flutter 應用。主要編輯 Dart 代碼所在的 lib / main.dart。
- 替換 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'),
),
),
);
}
}
-
運行應用程序。現在應該可以看到下面的頁面。
18288956071032318.jpg
第2步:使用外部 package
在這一步,將使用名為 english_words 的開源軟件包 ,其中包含數千個最常用的英文單詞以及一些實用功能。
可以在 pub.dartlang.org 上找到 english_words 軟件包以及其他許多開源軟件包。
- pubspec 文件管理著 Flutter 應用程序的靜態資源文件(assets)。 在 pubspec.yaml 文件中, 將 english_words(3.1.0或更高版本)添加到依賴列表。新的一行高亮如下:
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^0.1.0
english_words: ^3.1.0 // 新增的
- 在 Android Studio 的 editor 視圖中查看 pubspec 時, 點擊右上角的 Packages get ,將把 package 拉取到項目中。在控制臺中看到以下內容:
flutter packages get
Running "flutter packages get" in startup_namer...
Process finished with exit code 0
- 在 lib/main.dart 中,為
english_words
添加導入:
import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';// 新增的
- 改用英文單詞的 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.
),
),
);
}
}
-
運行:
248652834188319698.jpg
第3步:添加有狀態的widget
Stateless widgets 是不可改變的,這意味著它們的屬性不能改變——所有的值都是 final 的。
Statefulwidget 在其生命周期保持的狀態可能會變化,實現一個有狀態的 widget 至少需要兩個類:StatefulWidgets類和State類,其中StatefulWidgets類創建了一個State類的實例。StatefulWidget類本身是不可變的,但State類可存在于Widget的整個生命周期中。
在這一步,將添加一個有狀態的 RandomWords widget ,它可以創建其 State 類 RandomWordsState 。 State 類會為 widget 保存被推薦和被收藏的詞組。
- 將有狀態的 RandomWords widget 添加到 main.dart 。它可以在 MyApp 類之外的任何位置使用,但當前將把它放在文件底部。 RandomWords widget 除了創建 State 類之外幾乎沒有任何其他代碼:
class RandomWords extends StatefulWidget {
@override
createState() => new RandomWordsState();
}
- 添加 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);
}
}
- 從 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
),
),
);
}
}
-
重啟應用:
369675901483756576.jpg
第4步:創建一個無限滾動的 ListView
在這一步,可以擴展 RandomWordsState 類,生成并展示詞組列表。當用戶滑動列表,ListView widget 中顯示的列表將無限增長。
-
_suggestions
變量向 RandomWordsState 類中添加一個數組列表,用來保存推薦詞組。 該變量以下劃線(_
)開頭,在 Dart 語言中使用下劃線前綴表示強制私有。此外,添加一個
biggerFont
變量來增大字體。class RandomWordsState extends State<RandomWords> { final _suggestions = <WordPair>[]; final _biggerFont = const TextStyle(fontSize: 18.0); ... }
-
向 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]); } ); } }
-
對于每個詞組,
_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, ), ); } }
-
更新 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(), ); } ... }
-
更新 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(), ); } }
重啟應用程序,將可以看到一個詞組清單。盡量向下滑動,將繼續看到新的詞組。
第5步:添加可交互性
在這一步,將為每一行添加可點擊的心形圖標。當用戶點擊列表中的條目,切換其“收藏”狀態,詞組就會添加到收藏欄,或從已保存詞組的收藏欄中刪除。
-
添加一個 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); ... }
-
在
_buildRow
函數中,添加alreadySaved
標志檢查來確保一個詞組還沒有被添加到收藏。Widget _buildRow(WordPair pair) { final alreadySaved = _saved.contains(pair); ... }
-
在
_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, ), ); }
重啟應用。現在應該可以在每一行看到心形圖標,但還沒有交互功能。
-
在
_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的更新。
熱重載應用。可以點擊任意一行來收藏或取消收藏條目。 請注意,點擊一行可以產生從心形圖標展開的潑墨動畫效果。
第6步:導航到新頁面
在這一步,將添加一個顯示收藏夾的新頁面(在 Flutter 中稱為 route(路由))。你將學習如何在主路由和新路由之間導航。
在 Flutter 中, Navigator 管理著包含了應用程序所有路由的一個堆棧。將一個路由push到 Navigator 的堆棧,將顯示更新為新頁面路由。將一個路由 pull 出 Navigator 的堆棧,顯示將返回到前一個頁面路由。
-
在 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(), ); } ... }
-
向 RandomWordsState 類添加一個
_pushSaved()
函數。class RandomWordsState extends State<RandomWords> { ... void _pushSaved() { } }
重新加載應用程序。列表圖標將出現在應用欄中。點擊它不會有任何響應,因為
_pushSaved
這個函數還未實現功能。 -
當用戶點擊應用欄中的列表圖標時,將建立一個新路由并 push 到 Navigator 的路由堆棧中,這個操作將改變界面顯示,展示新的路由頁面。
新頁面的內容使用匿名函數在 MaterialPageRoute widget的
builder
屬性中創建。將函數調用添加到 Navigator.push 中作為參數,如高亮代碼所示,將路由 push 到 Navigator 的堆棧中。
void _pushSaved() { Navigator.of(context).push( ); }
-
添加 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(); }, ), ); }
-
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), ); }, ), ); }
熱重載應用程序。對一些條目點擊收藏,然后點擊應用欄右側的列表圖標。顯示出包含收藏夾列表的新頁面。注意,Navigator 會在應用欄左側添加一個“返回”按鈕。不必再顯式實現 Navigator.pop 。點擊返回按鈕會返回到主頁面。
第7步:使用主題更改UI
在最后一步中,將使用該應用的主題。 theme 控制的是應用程序的觀感。可以使用默認主題,該主題取決于使用的模擬器或真機,也可以自定義主題以反映你的品牌。
-
可以通過配置 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(), ); } }
熱重載應用程序。請注意,整個背景都是白色的,甚至包括應用欄。
作為讀者的練習,可使用 ThemeData 來改變用戶界面的其他方面。 Material 庫中的 Colors 類提供了多種可以使用的顏色常量,而熱重載使用戶界面的修改變得簡單快捷。