接著上一篇撩满,我們做一個這樣的APP:
開始之前葵第,我發(fā)現(xiàn)了一個好玩的東西圣拄,每次我們在終端中輸入命令:
flutter run
終端里會有這個東西:
按照上圖所示嘴秸,我們的進入這個網(wǎng)頁看看是個啥:
好高大上的感覺,具體是干嘛的庇谆,我也不知道岳掐,有興趣的同學(xué)可以點進去把玩把玩,以后搞明白了再更吧饭耳。
由于Flutter官方的更新串述,使用外部包需要到文件管理的配置文件中添加依賴的項目,所以需要到項目根目錄下的pubspec.yaml
文件中添加如下代碼:
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^0.1.0
english_words: ^3.1.0 //將本行代碼添加到示例中的位置
第一步
先創(chuàng)建一個列表寞肖。
回到main.dart
中纲酗,把原來的代碼全部清空衰腌,復(fù)制以下代碼:
import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
@override
//構(gòu)建一個容器
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Startup Name Generator',
home: new RandomWords(),//定義子組件為有狀態(tài)控件RandomWords類的實例
);
}
}
//定義有狀態(tài)控件RandomWords類
class RandomWords extends StatefulWidget {
@override
createState() => new RandomWordsState();//創(chuàng)建有狀態(tài)控件RandomWords的狀態(tài)類實例:RandomWordsState
}
//定義狀態(tài)類RandomWordsState
class RandomWordsState extends State<RandomWords> {
@override
final _suggestions = <WordPair>[]; //用于保存隨機字符串詞組,注意這是一個數(shù)組變量
final _biggerFont = const TextStyle(fontSize: 18.0); //用于標(biāo)識字符串的樣式
//構(gòu)建一個腳手架觅赊,里面塞入前面定義好的_buildSuggestions類
Widget build(BuildContext context) {
return new Scaffold (
appBar: new AppBar(
title: new Text('Startup Name Generator'),
),
body: _buildSuggestions(),
);
}
//定義一個子控件右蕊,這個控件就是放置隨機字符串詞組的列表
Widget _buildSuggestions() {
return new ListView.builder( //ListView(列表視圖)是material.dart中的基礎(chǔ)控件
padding: const EdgeInsets.all(16.0), //padding(內(nèi)邊距)是ListView的屬性,配置其屬性值
//通過ListView自帶的函數(shù)itemBuilder茉兰,向ListView中塞入行尤泽,變量 i 是從0開始計數(shù)的行號
//此函數(shù)會自動循環(huán)并計數(shù),咋結(jié)束的我也不知道规脸,走著瞧咯
itemBuilder: (context, i) {
if (i.isOdd) return new Divider();//奇數(shù)行塞入分割線對象
final index = i ~/ 2; //當(dāng)前行號除以2取整坯约,得到的值就是_suggestions數(shù)組項索引號
// 如果計算得到的數(shù)組項索引號超出了_suggestions數(shù)組的長度,那_suggestions就再生10個隨機組合的字符串詞組
if (index >= _suggestions.length) {
_suggestions.addAll(generateWordPairs().take(10));
}
return _buildRow(_suggestions[index]);//把這個數(shù)據(jù)項塞入ListView中
}
);
}
//定義的_suggestions數(shù)組項屬性
Widget _buildRow(WordPair pair) {
//ListTile和Text都是material.dart中的基礎(chǔ)控件
return new ListTile(
title: new Text(
pair.asPascalCase, //使用駝峰樣式
style: _biggerFont,
),
);
}
}
看到這里莫鸭,是不是有點暈闹丐,各種聲明、各種引用被因,還有回調(diào)卿拴,把上面的代碼,用下面的圖解析下結(jié)構(gòu)梨与,看看到底怎么個情況:
可以發(fā)現(xiàn)在StatelessWidget和State類中都有一個Widget類型的函數(shù)build()
堕花,感覺有點像類的初始化方法construct()
,而RandomWords對象為什么只使用了createState()
卻沒有build()
粥鞋,我也不知道缘挽,走著瞧吧。當(dāng)對象實例化的時候呻粹,首先執(zhí)行Widget build(BuildContext context){}函數(shù)壕曼,函數(shù)中BuildContext類型的參數(shù)context,到目前為止還不知道干嘛用的等浊,暫且忽略其意義吧腮郊。
material類型的子控件都通過回調(diào)函數(shù)的方式創(chuàng)建,我讀起來有些不習(xí)慣筹燕,但通過回調(diào)轧飞,免去了先聲明再使用的麻煩,并且可以直接對對象的屬性進行配置撒踪,通過build()
一層層回調(diào)踪少,代碼簡潔不少,而代碼中使用到的material內(nèi)置控件糠涛,我就不一一介紹了援奢,有興趣的同學(xué)請參考material官方API,注意material控件索引在頁面右邊的列表忍捡,別找到左邊去了集漾。
注意切黔,遇到這種聲明類屬性的格式:_[變量名]
。按官方的意思是具篇,如果變量名的前綴有_
下劃線纬霞,表示強制轉(zhuǎn)換為私有變量,相當(dāng)于聲明變量為private驱显,但使用這個變量的時候诗芜,還是要將下劃線進行完整的書寫。
保存代碼后運行一下埃疫,可以看到APP變成了這個樣子:
向下滾動試試伏恐,發(fā)現(xiàn)可以一直滾下去~
第二步
向列表里加個收藏標(biāo)簽按鈕,使每行可以標(biāo)記收藏或取消收藏栓霜。這個收藏標(biāo)簽就是狀態(tài)翠桦,既然要修改狀態(tài),肯定要到state中進行啦胳蛮。
到對象RandomWordsState中定義一個對象销凑,用于存儲標(biāo)記。為什么要單獨存儲標(biāo)記呢仅炊?因為這樣就不需要往行對象(ListTile)中添加標(biāo)記斗幼,降低了對象的復(fù)雜度。如下:
class RandomWordsState extends State<RandomWords> {
final _suggestions = <WordPair>[];
final _saved = new Set<WordPair>(); //新加這一句
final _biggerFont = const TextStyle(fontSize: 18.0);
...
}
為什么存儲標(biāo)記的是對象而不是一個數(shù)組呢抚垄?大概是想順便教我們使用一下Set對象吧孟岛,據(jù)說Set對象不允許有重復(fù)的項目,方便后面模擬堆棧的效果督勺,非常適合這種場景。然后我們到每個行添加一個標(biāo)記收藏的控件:
Widget _buildRow(WordPair pair) {
//定義一個布爾變量斤贰,用于判斷行控件ListTile是否被標(biāo)記為收藏
final alreadySaved = _saved.contains(pair);
return new ListTile(
title: new Text(
pair.asPascalCase,
style: _biggerFont,
),
//安放圖標(biāo)控件
trailing: new Icon(
alreadySaved ? Icons.favorite : Icons.favorite_border,
color: alreadySaved ? Colors.red : null,
),
//定義點擊事件智哀,控制圖標(biāo)的樣式的切換
onTap: () {
setState(() {
if (alreadySaved) {
_saved.remove(pair);
} else {
_saved.add(pair);
}
});
},
);
}
注意,在onTap
事件中荧恍,使用到了setState()
方法(用過vue或react的玩家是不是很熟悉呀)瓷叫,在這個方法里修改變量值,即可觸發(fā)state對象執(zhí)行build()
方法重繪對象送巡。這里每次變更對象_saved后摹菠,都會重繪ListTile對象,而靜態(tài)變量alreadySaved
也被重新定義骗爆,因此不用擔(dān)心alreadySaved
值不被更新的問題次氨,如果去除final
關(guān)鍵字,Dart語法會報錯摘投,還請路過大神點撥一下原因煮寡。
保存代碼后虹蓄,可以看到APP刷新了,每一行都添加了一個心型圖標(biāo)幸撕,反復(fù)戳這個行薇组,還自動配有動畫效果:
第三步
加入一個導(dǎo)航欄樣式的堆棧。
先往主頁面控件(AppBar)中添加一個可以進入收藏列表的入口:
class RandomWordsState extends State<RandomWords> {
...
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Startup Name Generator'),
//為AppBar對象的actions屬性添加一個IconButton對象坐儿,actions屬性值可以是Widget類型的數(shù)組
actions: <Widget>[
new IconButton(icon: new Icon(Icons.list), onPressed: _pushSaved),
//可以試試添加這兩行后APP上有什么效果
new IconButton(icon: new Icon(Icons.add), onPressed: _pushSaved),
new IconButton(icon: new Icon(Icons.create), onPressed: _pushSaved),
],
),
body: _buildSuggestions(),
);
}
...
}
由于沒有定義_pushSaved
函數(shù)律胀,直接復(fù)制上面注釋說明的代碼會報錯,因此貌矿,定義一個void類型的函數(shù)_pushSaved
:
class RandomWordsState extends State<RandomWords> {
...
void _pushSaved() {
}
}
這時候可以看到右上角多了3個圖標(biāo)炭菌,如圖:
然后我們往_pushSaved()
函數(shù)中添加代碼,讓收藏夾玩起來:
void _pushSaved() {
//創(chuàng)建導(dǎo)航欄控件Navigator站叼,然后往里面塞入MaterialPageRoute控件
Navigator.of(context).push(
new MaterialPageRoute(
builder: (context) {
//通過遍歷_saved對象娃兽,獲取已收藏的行對象
final tiles = _saved.map(
(pair) {
return new ListTile(
title: new Text(
pair.asPascalCase,
style: _biggerFont,
),
);
},
);
//函數(shù)的的鏈?zhǔn)秸{(diào)用,獲取到添加好分割線的ListTile控件
final divided = ListTile
.divideTiles( //divideTiles()函數(shù)尽楔,向每個tile間隔插入一個1像素寬的邊框
context: context,
tiles: tiles,
)
.toList(); //不要漏掉這個函數(shù)投储,否則進入收藏夾直接崩潰
return new Scaffold(
appBar: new AppBar(
title: new Text('收藏的列表項目'),
),
body: new ListView(children: divided), //直接將準(zhǔn)備好的ListTile塞入其中,完成內(nèi)容填充
);
},
),
);
}
保存代碼后阔馋,刷新APP玛荞,如圖:
如上圖所示,紅色的箭頭表示點擊按鈕后頁面的切換呕寝,綠色箭頭表示收藏夾的值的對照勋眯,有沒有發(fā)現(xiàn)我們并沒有寫返回主頁按鈕的代碼,這個返回按鈕從哪來的呢下梢?是由Navigator對象自動生成的客蹋,并且自動指向到主頁面的路由,不過遺憾的是孽江,沒有加入返回手勢的支持讶坯,如有需要,還得自己寫岗屏,具體怎么寫辆琅,跟著我的筆記走吧,我現(xiàn)在也不知道这刷。
大家注意看主頁列表和收藏夾列表的區(qū)別婉烟,兩者都是列表,都是使用的ListView和ListTile對象暇屋,但實現(xiàn)的方式完全不同似袁,插入行對象和分割線的差異很有意思,有興趣的同學(xué)可以自行修改下代碼,看看能不能將兩種列表的構(gòu)建方法對調(diào)一下叔营,參考官方資料ListView和ListTile
第四步
變更UI主題風(fēng)格屋彪。
這一步超級簡單,往MaterialApp對象里添加theme
屬性值即可:
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Startup Name Generator',
//添加theme屬性值绒尊,塞入ThemeData對象
theme: new ThemeData(
primaryColor: Colors.white,
),
home: new RandomWords(),
);
}
}
再保存畜挥,刷新APP試試,主題變成了白色風(fēng)格:
ThemeData()
方法本身提供了很多配色方案婴谱,玩家可以參考ThemeData官方說明蟹但,掌握其強大功能。沒想到變更主題如此輕松谭羔,在當(dāng)前APP界越來越追求視覺體驗升級的趨勢下华糖,使用flutter開發(fā)APP的玩家應(yīng)該欣慰不少吧~
好勒,這次官方萌新課程的搬運就到這里瘟裸,我是真的超喜歡這個萌新課程客叉,連課程總結(jié)都幫我寫好了:
- 從頭開始創(chuàng)建一個Flutter應(yīng)用程序。
- 書寫了Dart語言的代碼话告。
- 學(xué)會了調(diào)用外部的第三方庫兼搏。
- 體驗熱更新帶來的開發(fā)周期加速。
- 學(xué)會使用有狀態(tài)控件沙郭,增強了應(yīng)用的交互佛呻。
- 使用ListView和ListTiles創(chuàng)建了一個支持懶加載的無限滾動列表。
- 創(chuàng)建了一組路由并實現(xiàn)了主路由和新路由之間的跳轉(zhuǎn)邏輯病线。
- 了解如何使用主題更改應(yīng)用UI的外觀吓著。
自我總結(jié)一下,flutter是一個控件高度集成化的開發(fā)平臺送挑,控件的完整度極高灰羽,控件之間的交互實現(xiàn)也傾向于傻瓜化慰丛,比如自動生成返回按鈕捕虽、主題風(fēng)格全局可控黑滴。只要把握好狀態(tài)值和控件之間的嵌套關(guān)系,開發(fā)者幾乎不需要單獨敲代碼實現(xiàn)跳轉(zhuǎn)邏輯赡突,代碼簡直不要太簡潔,不知不覺間就寫好了一個APP区赵。而VScode中的Dart Code插件也實在太好用惭缰,代碼提示、函數(shù)用法和參數(shù)都有詳盡的說明笼才,看得出谷歌拿出了十足的誠意要在跨平臺開發(fā)上面大搞特搞一番漱受。
當(dāng)然了,滿屏幕的回調(diào)函數(shù)讓我這種編程思維還停留在C語言時代的菜鳥來說,扶墻~ 頭有點暈昂羡,還需要點時間慢慢適應(yīng)一下下絮记,也由于我沒有那么深厚的技術(shù)功底,對這個教程的理解還比較有限虐先,可能有寫的不對或不好的地方怨愤,也歡迎大家指正,尤其我花了一天一夜寫了這篇稿也是蠻不容易了蛹批,有路過的高手不說兩句也是哪啥了是吧撰洗。當(dāng)然,我有空的時候抓緊讀一讀Dart 語法基礎(chǔ)和官方原版腐芍,有了新的發(fā)現(xiàn)也會寫稿分享出來差导。
好啦就寫到這里,廣告時間猪勇,對flutter感興趣的小伙伴可以關(guān)注我设褐,歡迎大家到Flutter圈子中投稿,也可以聯(lián)系管理員加入我們的flutter微信群嗨聊泣刹,謝謝捧場~助析!
flutter 中文社區(qū)(官方QQ群:338252156)