學習Flutter你必定會看到官網的第一個例子:中文版 或 英文版。可是做爲新手,或許你看的會很費勁,這篇文章的目的是幫助你更好的理解這個例子。android
最終的效果圖:數組
咱們先分析一下如何實現上圖中的效果:app
Android開發者less
1. 準備數據:列表數據和選中的數據能夠分別使用兩個List或者數組存儲。 2. 界面列表:使用ListView或RecyclerView 3. 界面跳轉:可使用Intent攜帶數據到新的列表頁 |
iOS開發者dom
1. 準備數據:列表數據和選中的數據能夠分別使用兩個數組存儲。 2. 界面列表:使用TableView或CollectionView 3. 界面跳轉:使用NavigationController,能夠把值直接賦值給新的頁面對象 |
咱們發現,不管是原生的Android仍是iOS開發,都須要作的步驟是:ide
因此在Flutter開發中,也遵守這幾個步驟會更好的理解函數
Flutter開發工具
* 準備數據:列表數據使用數組存儲,選中的數據可使用Set存儲(由於set能夠自動去重)。 * 界面列表:使用ListView * 界面跳轉:可使用Navigator |
官網上使用大概110行代碼實現上面的例子,咱們把這些代碼拆解成主要的三部分來幫助咱們學習:學習
前提:你首先應該會用Android studio或者其餘開發工具建立一個Flutter的工程,若是你須要學習關於這個步驟,能夠在 這裏快速學習開發工具
當你建立一個全新的Flutter工程並運行,界面上會出現熟悉的「Hello world」。 爲了更容易的理解Flutter的代碼,咱們先分析一下建立初始的代碼,至少要知道咱們須要從哪裏開始動手:
咱們要編輯的就是這裏的 main.dart 文件,跟其餘語言同樣,Flutter的入口函數是main函數:
import 'package:flutter/material.dart';
void main() => runApp(new MyApp()); //分析 1
class MyApp extends StatelessWidget { //分析 2 (StatelessWidget)
@override
Widget build(BuildContext context) { //分析 3
return new MaterialApp(
title: 'Welcome to Flutter',
home: new Scaffold( //分析 4
appBar: new AppBar(
title: new Text('Welcome to Flutter'),
),
body: new Center( //分析 5
child: new Text('Hello World'),
),
),
);
}
}
複製代碼
void main() {
runApp(new MyApp());
}
複製代碼
StatelessWidget 表明只有一種狀態的組件,與之對應的是StatefulWidget(表示可能有多種狀態)。這裏先不用深究其原理,只需知道這個跟flutter的刷新等相關。
在Widget組件中都是經過build方法來描述本身的內部結構。這裏的build表示構建MyApp中使用的是MaterialApp的系統組件。
home標籤的值:Scaffold是Material library 中提供的一個組件,咱們能夠在裏面設置導航欄、標題和包含主屏幕widget樹的body屬性。能夠看到這裏是在頁面上添加了AppBar和一個Text。
Center是一個能夠把子組件放在中心的組件
咱們的目標是把頁面中顯示hello_world的TextView換成一個ListView。由上面的分析可知,將上面第4點的home標籤的值,換成一個ListView就能改變頁面顯示的內容。不過在此以前,須要先準備一下要顯示的數據,這裏是使用一個叫 english_words 的三方包,能夠幫助咱們生成顯示的單詞數據。先學習一下如何添加依賴包:
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^0.1.0
english_words: ^3.1.0
複製代碼
添加english_words庫以後,能夠這樣使用這個庫創造數據:
//創造5個隨機詞組,並返回詞組的迭代器
generateWordPairs().take(5)
複製代碼
查看ListView的源碼,發現其最終是繼承自 StatelessWidget,因此它的狀態是惟一的。可是要實現的ListView中的數據是動態變化的,因此須要使用StatefulWidget來動態改變ListView中的數據。
使用StatefulWidget組件須要本身控制在不一樣狀況下的顯示狀態,因此須要實現State類來告訴StatefulWidget類不一樣狀況下如何展現。 |
建立一個動態變化的組件類,用於表示要顯示的ListView:
class RandomWords extends StatefulWidget {
@override
State<StatefulWidget> createState() { //分析1
return new RandomWordsState();
}
}
複製代碼
分析:
class RandomWordsState extends State<RandomWords> {
}
複製代碼
要完成展現數據和保存點擊後的數據,這裏分別用數組和set來存儲(用set存儲點擊後的數據是由於set能夠去重,你也能夠選擇其餘的存儲方式)
class RandomWordsState extends State<RandomWords> {
final _suggestions = <WordPair>[]; //分析 1
final _saved = new Set<WordPair>();
final _biggerFont = const TextStyle(fontSize: 18.0); //分析 2
}
複製代碼
分析:
添加以下兩個方法到RandomWordsState類中,表示創造數據集和構建ListView:
Widget _buildSuggestions() {
return new ListView.builder(
padding: const EdgeInsets.all(16.0),
// 對於每一個建議的單詞對都會調用一次itemBuilder,而後將單詞對添加到ListTile行中
// 在偶數行,該函數會爲單詞對添加一個ListTile row.
// 在奇數行,該函數會添加一個分割線widget,來分隔相鄰的詞對。
// 注意,在小屏幕上,分割線看起來可能比較吃力。
itemBuilder: (context, 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 index = i ~/ 2;
// 若是是建議列表中最後一個單詞對
if (index >= _suggestions.length) {
// ...接着再生成10個單詞對,而後添加到建議列表
_suggestions.addAll(generateWordPairs().take(10));
}
return _buildRow(_suggestions[index]);
}
);
}
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);
}
});
},
);
}
複製代碼
代碼中有詳細的註釋,可是爲了方便理解,這裏仍是給出一點解釋:
_buildSuggestions方法中:
_suggestions.addAll(generateWordPairs().take(10));
就是每次添加10個數據到顯示數組中_buildRow方法中:
添加_pushSaved方法表示如何跳轉到新的頁面並展現選中的數據:
main.dart
void _pushSaved() {
Navigator.of(context).push( // 分析 1
new MaterialPageRoute( // 分析 2
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( // 分析 3
appBar: new AppBar(
title: new Text('Saved Suggestions'),
),
body: new ListView(children: divided),
);
},
),
);
}
複製代碼
分析: 1.使用Navigator.of(context).push的方式來處理跳轉,須要的參數是一個Route 2.建立頁面Route 3.返回一個新的裏面,裏面的body內容是一個ListView,展現的是_saved中讀取出來的數據
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) {
return new MaterialApp(
title: 'Welcome to Flutter',
theme: new ThemeData(
primaryColor: Colors.red,
),
home: RandomWords(),
);
}
}
class RandomWords extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return new RandomWordsState();
}
}
class RandomWordsState extends State<RandomWords> {
final _suggestions = <WordPair>[];
final _saved = new Set<WordPair>();
final _biggerFont = const TextStyle(fontSize: 18.0);
@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(),
);
}
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),
);
},
),
);
}
Widget _buildSuggestions() {
return new ListView.builder(
padding: const EdgeInsets.all(16.0),
itemBuilder: (context, i) {
if (i.isOdd) return new Divider();
final index = i ~/ 2;
if (index >= _suggestions.length) {
_suggestions.addAll(generateWordPairs().take(10));
}
return _buildRow(_suggestions[index]);
});
}
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);
}
});
},
);
}
}
複製代碼
運行吧,就能看到最上方的效果。
謝謝觀看這篇文章,若是讓您發現了錯誤或者有好的建議,歡迎在下方評論給我留言。