在這一步中,你將開始使用一個名爲 english_words 的開源軟件包,其中包含數千個最經常使用的英文單詞以及一些實用功能。html
你能夠 在 pub.dartlang.org 上找到 english_words 軟件包以及其餘許多開源軟件包。shell
dependencies: flutter: sdk: flutter cupertino_icons: ^0.1.0 english_words: ^3.1.0 # 新增了這一行
Packages get
,這會將依賴包安裝到您的項目。您能夠在控制檯中看到如下內容:flutter packages get Running "flutter packages get" in startup_namer... Process finished with exit code 0
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
Stateless widgets 是不可變的,這意味着它們的屬性不能改變——全部的值都是 final。編輯器
Stateful widgets 持有的狀態可能在 widget 生命週期中發生變化,實現一個 stateful widget 至少須要兩個類:1)一個 StatefulWidget 類;2)一個 State 類,StatefulWidget 類自己是不變的,可是 State 類在 widget 生命週期中始終存在。ide
在這一步,你將添加一個 stateful widget(有狀態的控件)—— RandomWords,它會建立本身的狀態類 —— RandomWordsState,而後你須要將 RandomWords 內嵌到已有的無狀態的 MyApp widget。函數
class RandomWordsState extends State<RandomWords> { // TODO Add build method }
注意一下 State<RandomWords> 的聲明。這代表咱們在使用專門用於 RandomWords 的 State 泛型類。應用的大部分邏輯和狀態都在這裏 —— 它會維護 RandomWords 控件的狀態。這個類會保存代碼生成的單詞對,這個單詞對列表會隨着用戶滑動而無限增加,另外還會保存用戶喜好的單詞對(第二部分),也即當用戶點擊愛心圖標的時候會從喜好的列表中添加或者移除當前單詞對。佈局
RandomWordsState 繼承自 RandomWords,咱們接下來會建立這個類。
class RandomWords extends StatefulWidget { @override RandomWordsState createState() => new RandomWordsState(); }
在添加狀態類後,IDE 會提示該類缺乏 build 方法。接下來,您將添加一個基本的 build 方法,該方法經過將生成單詞對的代碼從 MyApp 移動到 RandomWordsState 來生成單詞對。
class RandomWordsState extends State<RandomWords> { @override // 新增代碼片斷 - 開始 ... Widget build(BuildContext context) { final WordPair wordPair = new WordPair.random(); return new Text(wordPair.asPascalCase); } // ... 新增的代碼片斷 - 結束 }
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(), // 修改爲本行代碼 ), ), ); } }
提示: 若是您嘗試熱重載,則可能會看到一條警告,考慮從新啓動當前應用:
Reloading... Not all changed program elements ran during view reassembly; consider restarting.由於重啓應用以後就能夠生效,故這多是誤報。
在這一步中,您將擴展(繼承)RandomWordsState 類,以生成並顯示單詞對列表。 當用戶滾動時,ListView 中顯示的列表將無限增加。 ListView 的 builder 工廠構造函數容許您按需創建一個懶加載的列表視圖。
提示:在 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,對於每一個建議的單詞對都會執行一次。該模型容許建議的單詞對列表在用戶滾動時無限增加。
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 的第二部分。
Widget _buildRow(WordPair pair) { return new ListTile( title: new Text( pair.asPascalCase, style: _biggerFont, ), ); }
@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(), ); // ... 添加到這裏 }
@override Widget build(BuildContext context) { return new MaterialApp( title: 'Startup Name Generator', home: new RandomWords(), ); }
在這部分,咱們將爲每一行添加一個心形的(收藏)圖標,下一步你將可以爲這個圖標加入點擊收藏的功能。
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); ... }
Widget _buildRow(WordPair pair) { final bool alreadySaved = _saved.contains(pair); // 新增本行 ... }
同時在 _buildRow() 中, 添加一個心形 ❤️ 圖標到 ListTiles以啓用收藏功能。接下來,你就能夠給心形 ❤️ 圖標添加交互能力了。
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, ), // ... 新增代碼結束 ); }
在這部分,咱們將爲剛剛的心形 ❤️圖標增長交互,當用戶點擊列表中的條目,切換其"收藏"狀態,並將該詞對添加到或移除出"收藏夾"。
爲了作到這個,咱們在 _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: () { // 增長以下 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 添加一個列表圖標。當用戶點擊列表圖標時,包含收藏夾的新路由頁面入棧顯示。
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),用方括號 [] 表示。
class RandomWordsState extends State<RandomWords> { ... // 新增代碼開始 void _pushSaved() { } // 新增代碼結束 }
接下來,(當用戶點擊導航欄中的列表圖標時)咱們會創建一個路由並將其推入到導航管理器棧中。此操做會切換頁面以顯示新路由,新頁面的內容會在 MaterialPageRoute 的 builder 屬性中構建,builder 是一個匿名函數。
void _pushSaved() { Navigator.of(context).push( ); }
接下來,添加 MaterialPageRoute 及其 builder。 如今,添加生成 ListTile 行的代碼,ListTile 的 divideTiles() 方法在每一個 ListTile 之間添加 1 像素的分割線。 該 divided 變量持有最終的列表項,並經過 toList()方法很是方便的轉換成列表顯示。
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 組成;每行之間經過一個分隔線分隔。
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), ); // ... 新增代碼段結束. }, ), ); }
這一部分,咱們將會一塊兒修改應用的主題。Flutter 裏咱們使用 theme 來控制你應用的外觀和風格,你可使用默認主題,該主題取決於物理設備或模擬器,也能夠自定義主題以適應您的品牌。
你能夠經過配置 ThemeData 類輕鬆更改應用程序的主題,目前咱們的應用程序使用默認主題,下面將更改 primaryColor 顏色爲白色。
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 的文檔,添加其餘屬性來更多改變 UI 樣式。Material library 中的 Colors 類提供了許多可使用的顏色常量, 你可使用熱重載來快速簡單地嘗試、實驗。