你們好,通過幾個月的潛水,Flutter出乎意料的火熱,抱歉一直沒有更新,因爲加入了創業團隊,經歷了幾波大起大落,如今終於騰出時間搞搞技術,如今和成都的幾位技術極客合做推出門路網,正在用flutter實踐開發APP,也算是對flutter商業化的小試牛刀,本篇將門路網APP用到的flutter技術進行簡單分享。html
以前的新聞APP的實踐項目中,用到了Tab+TabBarView+Tabcontroller
的用法,實現了基於scaffold
下頂部標籤頁的頁面切換,可是你們都會遇到來回切換頁面致使TabBarView
自動重繪的問題,頁面沒法停留到切換前的狀態,這個問題也是困擾了我好久,用PageStorageKey搭配Stack
+Offstage
解決這個問題。java
首先,咱們本身寫一個TabBar玩玩,爲何呢?由於這樣能夠實現控件的高度自定義,順便學一學新的組件用法:git
class NewsTab {
String text;
String tab;
NewsTab(this.text,this.tab);
}
//定義tab頁基本數據結構
final List<NewsTab> NewsTabs = <NewsTab>[
new NewsTab('金融','financial'),
new NewsTab('科技','technology'),
new NewsTab('醫療','medical'),
];
class TabNavigation extends StatelessWidget {
TabNavigation({this.currentTab, this.onSelectTab});
final NewsTab currentTab;
final ValueChanged<NewsTab> onSelectTab; //這個參數比較關鍵,仔細理解下,省了setState()調用的環節
@override
Widget build(BuildContext context) {
return Row(
children: NewsTabs.map((item){
return GestureDetector( //手勢監聽控件,用於監聽各類手勢
child: Container(
padding: EdgeInsets.fromLTRB(24.0, 0.0, 24.0, 0.0),
child: Text(item.text,style: TextStyle(color: _colorTabMatching(item: item)),),
),
onTap: ()=>onSelectTab(item,)
//onSelectTab函數的使用很是巧妙,
//至關於定義了一個接口,可操控當前控件之外的數據
);
}).toList()
);
}
//定義tab被選中和沒被選中的顏色樣式
Color _colorTabMatching({NewsTab item}) {
return currentTab == item ? Colors.black : Colors.grey;
}
}
複製代碼
爲何要這麼作呢?由於咱們能夠經過onSelectTab
函數對外部數據進行控制,主頁面調用TabNavigation
:github
class _MainListState extends State<MainList> {
NewsTab _currenttab = NewsTabs[0]; //定義默認打開的Tab頁
void _selectTab(NewsTab tab){ //修改狀態值
setState(() {
_currenttab = tab;
});
}
TabNavigation(
currentTab: _currenttab,
onSelectTab: _selectTab,
),
....
}
複製代碼
當使用TabNavigation
時,向其傳入定義好的_selectTab
函數,便可完成狀態值修改的任務,這也是子控件向父控件傳遞參數的一種方式,特別適用於子控件修改父控件狀態值時的場景。json
以上是Tab
標籤和主頁面的定義,接下來看Tab
頁的定義:swift
class NewsList extends StatefulWidget{
@override
NewsList({this.newsType,this.pageKey});
final PageStorageKey<NewsTab> pageKey; //當前控件惟一標識Key
final String newsType;
NewsListState createState() => new NewsListState();
}
class NewsListState extends State<NewsList>{
....
}
複製代碼
注意看控件惟一標識Key的定義,有關PageStorageKey
的說明請參考官方閱讀理解,看不懂能夠用谷歌翻譯過一遍,這裏不作贅述了,關鍵在PageStorageKey<NewsTab>
中的<NewsTab>
。PageStorageKey
是局部Key,在父控件中定義時不要重複便可,因此我用了NewsTab
類型,固然小夥伴也能夠定義其餘不會重複的值做爲標識,不過可能會比我這個麻煩一點,想知道爲啥,由於在主頁面下是這樣定義和使用Key
的:數組
class MainList extends StatefulWidget {
const MainList({ Key key }) : super(key: key);
@override
_MainListState createState() => new _MainListState();
}
class _MainListState extends State<MainList> {
//定義Key值,類型名便是構造函數,須要傳入匹配類型的參數
Map<NewsTab, PageStorageKey<NewsTab>> pageKeys = {
NewsTabs[0]: PageStorageKey<NewsTab>(NewsTabs[0]),
NewsTabs[1]: PageStorageKey<NewsTab>(NewsTabs[1]),
NewsTabs[2]: PageStorageKey<NewsTab>(NewsTabs[2]),
};
...
Widget build(BuildContext context){
return Scaffold(
...
body: Stack( //Stack在初始化時,會將子控件所有渲染,而TabBarView則僅渲染默認子控件
children: NewsTabs.map((item) {
return Offstage( //使用Offstage,把不須要顯示的子控件隱藏起來
offstage: _currenttab != item,
child: NewsList(
pageKey: pageKeys[item], //傳入Key值
newsType: item.tab),
);
}).toList(),
)
}
}
}
複製代碼
這裏用到了Stack
+Offstage
的組合,特性在註釋中可瞭解,因爲這兩個控件能夠保留子控件的特性,再加上PageStorageKey<NewsTab>
標識,便可以保證NewsList
在控件樹中的位置保持不變,從而避免了NewsList
被切換後重復渲染的問題。網絡
爲了方便理解,我把pageKeys
的定義和使用分開進行,也就是在列表控件NewsList
初始化的時候,即爲其分配了一個PageStorageKey<NewsTab>
類型的key值,保證它須要重複使用的時候不被flutter認爲是新控件,也就不會觸發重繪了。固然你也能夠這麼寫:數據結構
NewsList(
pageKey: PageStorageKey<NewsTab>(item),
newsType: item.tab),
)
複製代碼
以上兩種方式,無論怎麼寫,都會經過遍歷NewsTabs
獲取NewsTab
,這樣建立PageStorageKey
方便很多。less
爲何沒有用GlobalKey
?由於用不上,一方面GlobalKey
比較耗費資源,存在於APP的整個生命週期,如同全局變量,另外一方全局不容許重複定義,萬一在別的地方須要重建相同控件,還得費腦子想辦法避開相同的GlobalKey
,省得捅出其餘簍子。另外補充一點,只有有狀態控件才能使用GlobalKey
,一看GlobalKey
的定義你就明白了:GlobalKey<State<StatefulWidget>>
,是給StatefulWidget
下的State
類使用的。
動圖對比一下Tab+TabBarView+Tabcontroller
和PageStorageKey+Stack+Offstage
:
能夠看到,在頁面初始渲染和切換時,二者的區別,前者初始化時僅渲染了一個Tab頁,頁面切換時每一個Tab頁都會自動dispose
掉,而且新頁面要從新initState
,然後者則在初始化時即渲染了全部子Tab頁,頁面切換時沒有dispose
,而是僅調用了有狀態控件的didChangeDependencies
事件。
源碼地址請點擊此處,本次分享僅作解決方案上的思考,也許還有更好的方案,歡迎你們分享。
1.當項目打包APK後,再次修改代碼運行,有必定概率遇到新代碼不生效 解決辦法: 1). 打開flutter下的這個目錄:[你的地址]\flutter\bin 2). 刪除cache文件夾 3). 命令行中輸入:flutter doctor 4). 等待處理結束後,再次flutter run
我覺得直接用命令:flutter clean
刪除項目目錄下的build文件夾,從新運行一下就能夠解決問題,沒想到運行後報錯:
flutter clean
會直接刪除整個build文件夾,都不帶放回收站的,而後就悲劇了,整個項目無法運行,這時候你須要一句命令滿血復活:
flutter create -i objc。
2.使用Navigator
作頁面跳轉時,記得在其使用它的父控件構造函數或函數中添加BuildContext
屬性
BuildContext
屬性在flutter中的意義是控件在控件樹中的錨點,也能夠理解爲索引,當須要跳轉頁面時,須要告訴Navigator
當前控件的錨點,以便於在新頁面中點擊返回鍵時,能夠回退到原來的頁面,英文好的同窗能夠查看原閱讀理解。實際上Navigator
也是基於此錨點建立頁面錨點堆棧,因此當你須要對一個寫的很深的子控件觸發頁面跳轉時,須要把context
參數從頂層父控件一層一層往下傳。 控件函數中加入BuildContext context
參數的意義是讓控件明白:我是誰,我從哪裏來,要到哪裏去,好比:
//這裏加入了BuildContext context,是爲了把獲取到的context傳遞到子控件,以用於Navigator作頁面跳轉
_list(BuildContext context, List dataList){
....
return ListView.builder(
// padding: const EdgeInsets.all(16.0),
itemCount: dataList.length,
itemBuilder: (context, i) {
//context參數至關於當前控件在控件樹中的錨點,
//缺乏這個參數會致使列表中的項目沒法經過MaterialPageRoute進入下一個頁面
return _newsRow(dataList[i],context);
}
);
}
//這裏又須要定義context,是從上面的_list傳下來的
_newsRow(Map newsInfo,BuildContext context){
return ListTile(
...
onTap: (){
Navigator.of(context).push( //直到被Navigator.of(context)用到
MaterialPageRoute(
builder: (BuildContext context) => NewsDetail(
id:newsInfo["id"].toString()
)
)
);
},
);
}
複製代碼
那麼控件樹是啥呢?相信你們在寫頁面佈局的時候應該感覺到了什麼叫父子控件,整個flutter項目就是N個父子控件串起來的控件樹。
3. 從網絡獲取的json
數據內包含數組,沒法直接被List.add()
或List.addAll()
這個問題須要處理兩個問題:
List
對象,必需要進行初始化,不然直接調用list.add()
會報null
錯誤:List
獲取到的json數據鍵值對有數據的狀況下,沒法直接賦值到定義好的List<Map> list
,須要從新組裝數據,因爲獲取到的json鍵值對中有這樣格式的數據:
//獲取到的json數據
data:{items:[{'k1':'v1'},{'k2':'v2'},{'k3':'v3'},{'k4':'v4'},...]}
複製代碼
我便直接賦值給了上面定義的list
變量:
List<Map> list = new List();
list = request['data']['items'];
複製代碼
結果就悲劇了,一直報這個錯:
因此,從網絡請求獲取到的json
數據默認是Iterable<Map<dynamic,dynamic>>
格式,沒法直接賦值給List
對象,所以須要作一下處理:
List<Map> a = new List(); //這句new很重要,數組對象實例化,不然沒法運行a.add()
if (request['success']==true){
for(int i=0;i<request['data']['items'].length;i++){
a.add(request['data']['items'][i]); //
}
return a; //此處的意義即是把網絡請求獲取到的數據標準化,不然沒法直接賦值給dataList
}else return null;
複製代碼
這樣就好多了,固然,若是你作了json序列化,請無視這個問題。
篇幅較長見諒,在此感謝你們的支持,想繼續瞭解更多Flutter技巧,請關注Flutter圈子,歡迎大牛向這裏投稿佈道,也能夠加入flutter 中文社區(官方QQ羣:338252156)共同成長,謝謝你們~