前言
學(xué)習(xí)Flutter你一定會看到官網(wǎng)的第一個例子:中文版 或 英文版羡疗。但是作為新手糜颠,或許你看的會很費勁否彩,這篇文章的目的是幫助你更好的理解這個例子瑞凑。
最終的效果圖:
我們先分析一下如何實現(xiàn)上圖中的效果:
Android開發(fā)者
<table><tr><td bgcolor=#F3F3F3 >
- 準(zhǔn)備數(shù)據(jù):列表數(shù)據(jù)和選中的數(shù)據(jù)可以分別使用兩個List或者數(shù)組存儲。
- 界面列表:使用ListView或RecyclerView
- 界面跳轉(zhuǎn):可以使用Intent攜帶數(shù)據(jù)到新的列表頁
</td></tr></table>
iOS開發(fā)者
<table><tr><td bgcolor=#F3F3F3 >
- 準(zhǔn)備數(shù)據(jù):列表數(shù)據(jù)和選中的數(shù)據(jù)可以分別使用兩個數(shù)組存儲陡叠。
- 界面列表:使用TableView或CollectionView
- 界面跳轉(zhuǎn):使用NavigationController,可以把值直接賦值給新的頁面對象
</td></tr></table>
結(jié)論
我們發(fā)現(xiàn)玩郊,無論是原生的Android還是iOS開發(fā),都需要做的步驟是:
- 存儲要展示的數(shù)據(jù)枉阵,存儲選中的數(shù)據(jù)
- 展示列表译红,并把數(shù)據(jù)展示出來
- 設(shè)置跳轉(zhuǎn)到新頁面
所以在Flutter開發(fā)中,也遵照這幾個步驟會更好的理解
Flutter開發(fā)
<table><tr><td bgcolor=#DFEFD9 >
- 準(zhǔn)備數(shù)據(jù):列表數(shù)據(jù)使用數(shù)組存儲兴溜,選中的數(shù)據(jù)可以使用Set存儲(因為set可以自動去重)罪郊。
- 界面列表:使用ListView
- 界面跳轉(zhuǎn):可以使用Navigator
</td></tr></table>
拆解分析官方代碼校仑,帶你快速理解
官網(wǎng)上使用大概110行代碼實現(xiàn)上面的例子袁串,我們把這些代碼拆解成主要的三部分來幫助我們學(xué)習(xí):
前提:你首先應(yīng)該會用Android studio或者其他開發(fā)工具創(chuàng)建一個Flutter的工程鸠真,如果你需要學(xué)習(xí)關(guān)于這個步驟,可以在 這里快速學(xué)習(xí)
當(dāng)你創(chuàng)建一個全新的Flutter工程并運行膘怕,界面上會出現(xiàn)熟悉的“Hello world”想诅。
為了更容易的理解Flutter的代碼,我們先分析一下創(chuàng)建初始的代碼淳蔼,至少要知道我們需要從哪里開始動手:
我們要編輯的就是這里的 main.dart 文件侧蘸,跟其他語言一樣,F(xiàn)lutter的入口函數(shù)是main函數(shù):
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'),
),
),
);
}
}
分析
- 這里的 => 是Dart中單行函數(shù)的簡寫鹉梨,等價于:
void main() {
runApp(new MyApp());
}
StatelessWidget 代表只有一種狀態(tài)的組件讳癌,與之對應(yīng)的是StatefulWidget(表示可能有多種狀態(tài))。這里先不用深究其原理存皂,只需知道這個跟flutter的刷新等相關(guān)晌坤。
在Widget組件中都是通過build方法來描述自己的內(nèi)部結(jié)構(gòu)。這里的build表示構(gòu)建MyApp中使用的是MaterialApp的系統(tǒng)組件旦袋。
home標(biāo)簽的值:Scaffold是Material library 中提供的一個組件,我們可以在里面設(shè)置導(dǎo)航欄骤菠、標(biāo)題和包含主屏幕widget樹的body屬性“淘校可以看到這里是在頁面上添加了AppBar和一個Text商乎。
Center是一個可以把子組件放在中心的組件
開始改造
我們的目標(biāo)是把頁面中顯示hello_world的TextView換成一個ListView。由上面的分析可知祭阀,將上面第4點的home標(biāo)簽的值鹉戚,換成一個ListView就能改變頁面顯示的內(nèi)容鲜戒。不過在此之前,需要先準(zhǔn)備一下要顯示的數(shù)據(jù)抹凳,這里是使用一個叫 english_words 的三方包遏餐,可以幫助我們生成顯示的單詞數(shù)據(jù)。先學(xué)習(xí)一下如何添加依賴包:
- 打開pubspec.yaml文件添加三方庫:
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^0.1.0
english_words: ^3.1.0
- 點擊 Packages get 獲取剛添加的包赢底。
添加english_words庫之后失都,可以這樣使用這個庫創(chuàng)造數(shù)據(jù):
//創(chuàng)造5個隨機(jī)詞組,并返回詞組的迭代器
generateWordPairs().take(5)
學(xué)習(xí)使用可變狀態(tài)的組件 StatefulWidget
查看ListView的源碼幸冻,發(fā)現(xiàn)其最終是繼承自 StatelessWidget粹庞,所以它的狀態(tài)是唯一的。但是要實現(xiàn)的ListView中的數(shù)據(jù)是動態(tài)變化的嘁扼,所以需要使用StatefulWidget來動態(tài)改變ListView中的數(shù)據(jù)信粮。
<table><tr><td bgcolor=#D1EEEE>
使用StatefulWidget組件需要自己控制在不同情況下的顯示狀態(tài)黔攒,所以需要實現(xiàn)State類來告訴StatefulWidget類不同情況下如何展示趁啸。
</td></tr></table>
創(chuàng)建一個動態(tài)變化的組件類,用于表示要顯示的ListView:
class RandomWords extends StatefulWidget {
@override
State<StatefulWidget> createState() { //分析1
return new RandomWordsState();
}
}
分析:
- 創(chuàng)建State類的方法督惰,這里繼承系統(tǒng)的State創(chuàng)造一個名叫RandomWordsState的類不傅,來控制ListView各個狀態(tài)下的展示。
class RandomWordsState extends State<RandomWords> {
}
要完成展示數(shù)據(jù)和保存點擊后的數(shù)據(jù)赏胚,這里分別用數(shù)組和set來存儲(用set存儲點擊后的數(shù)據(jù)是因為set可以去重访娶,你也可以選擇其他的存儲方式)
class RandomWordsState extends State<RandomWords> {
final _suggestions = <WordPair>[]; //分析 1
final _saved = new Set<WordPair>();
final _biggerFont = const TextStyle(fontSize: 18.0); //分析 2
}
分析:
- Dart中加上 _表示私有化
- 表示字體大小的常量
創(chuàng)造數(shù)據(jù)集和構(gòu)建ListView
添加如下兩個方法到RandomWordsState類中,表示創(chuàng)造數(shù)據(jù)集和構(gòu)建ListView:
Widget _buildSuggestions() {
return new ListView.builder(
padding: const EdgeInsets.all(16.0),
// 對于每個建議的單詞對都會調(diào)用一次itemBuilder觉阅,然后將單詞對添加到ListTile行中
// 在偶數(shù)行崖疤,該函數(shù)會為單詞對添加一個ListTile row.
// 在奇數(shù)行,該函數(shù)會添加一個分割線widget典勇,來分隔相鄰的詞對劫哼。
// 注意,在小屏幕上割笙,分割線看起來可能比較吃力权烧。
itemBuilder: (context, i) {
// 在每一列之前,添加一個1像素高的分隔線widget
if (i.isOdd) return new Divider();
// 語法 "i ~/ 2" 表示i除以2伤溉,但返回值是整形(向下取整)般码,比如i為:1, 2, 3, 4, 5
// 時,結(jié)果為0, 1, 1, 2, 2乱顾, 這可以計算出ListView中減去分隔線后的實際單詞對數(shù)量
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);
}
});
},
);
}
代碼中有詳細(xì)的注釋,但是為了方便理解走净,這里還是給出一點解釋:
- _buildSuggestions方法就是返回一個ListView
- _buildRow方法就是返回ListView中的一行(ListTile)如何展示
_buildSuggestions方法中:
- 代碼中的itemBuilder就是如何顯示一行(ListTile)的配置券时,其返回值是_buildRow方法
-
_suggestions.addAll(generateWordPairs().take(10));
就是每次添加10個數(shù)據(jù)到顯示數(shù)組中
_buildRow方法中:
- 設(shè)置了一行(ListTile代表一行內(nèi)容囊嘉,在iOS和android中叫cell)如何展示
- ListTile設(shè)置了title、trailing(右邊的圖標(biāo))和點擊事件onTap()
- 會根據(jù)有沒有被保存過革为,決定右邊顯示什么圖標(biāo)
- 當(dāng)點擊時扭粱,會把沒有點擊過的內(nèi)容保存到_saved容器中。
添加跳轉(zhuǎn)邏輯
添加_pushSaved方法表示如何跳轉(zhuǎn)到新的頁面并展示選中的數(shù)據(jù):
main.dart
void _pushSaved() {
Navigator.of(context).push( // 分析 1
new MaterialPageRoute( // 分析 2
builder: (context) {
final tiles = _saved.map( //數(shù)據(jù)
(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的方式來處理跳轉(zhuǎn)震檩,需要的參數(shù)是一個Route
2.創(chuàng)建頁面Route
3.返回一個新的里面琢蛤,里面的body內(nèi)容是一個ListView,展示的是_saved中讀取出來的數(shù)據(jù)
最終抛虏,所有代碼整合是如下的樣子:
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);
}
});
},
);
}
}
運行吧博其,就能看到最上方的效果。
謝謝觀看這篇文章迂猴,如果讓您發(fā)現(xiàn)了錯誤或者有好的建議慕淡,歡迎在下方評論給我留言。