當我們搭建好了整個APP的頁面框架,現在我往Tab頁里加點東西:各種分類的新聞列表。也可以參考我的Git,上面有要點註釋。
由於需要請求外部資料,因此引入一個較為方便的http庫。官方示例的httpClient也是可以的,但是坑略多,待會兒講。
第一步
調整程式碼結構,定義一個Tab
頁內通用的列表物件,這種場景下使用ListView.builder()
建立不定長度的列表:
//因為列表的長度不定,因此需要用有狀態類來承載列表
class NewsList extends StatefulWidget{
final String newsType; //新聞型別
@override
NewsList({Key key, this.newsType} ):super(key:key);
_NewsListState createState() => new _NewsListState();
}
class _NewsListState extends State<NewsList>{
...
@override
Widget build(BuildContext context){
return new ListView.builder( //ListView.builder非常適合用於建立不確定長度的的列表
padding: const EdgeInsets.all(16.0),
itemCount: data == null ? 0 : data.length,
itemBuilder: (context, i) {
return _newsRow(data[i]);//把資料項塞入ListView中
}
);
}
...
}
複製程式碼
將Tab
頁的資料表達進行結構化處理,在最外層定義新聞Tab
頁的類,方便後面使用:
//定義TAB頁物件,這樣做的好處就是,可以靈活定義每個tab頁用到的物件,可結合Iterable物件使用,以後講
class NewsTab {
String text;
NewsList newsList;
NewsTab(this.text,this.newsList);
}
複製程式碼
到**_MyTabbedPageState**物件中例項化這些Tab
:
//將每個Tab頁都結構化處理下,由於http的請求需要傳入新聞型別的引數newsType,因此將新聞型別引數值作為物件屬性傳入Tab中
final List<NewsTab> myTabs = <NewsTab>[
new NewsTab('頭條',new NewsList(newsType: 'toutiao')), //拼音就是引數值
new NewsTab('社會',new NewsList(newsType: 'shehui')),
new NewsTab('國內',new NewsList(newsType: 'guonei')),
new NewsTab('國際',new NewsList(newsType: 'guoji')),
new NewsTab('娛樂',new NewsList(newsType: 'yule')),
new NewsTab('體育',new NewsList(newsType: 'tiyu')),
new NewsTab('軍事',new NewsList(newsType: 'junshi')),
new NewsTab('科技',new NewsList(newsType: 'keji')),
new NewsTab('財經',new NewsList(newsType: 'caijing')),
new NewsTab('時尚',new NewsList(newsType: 'shishang')),
];
複製程式碼
第二步
由於重新了Tabs,原來的TabBar和TabBarView獲取對應值的方式也發生了改變,用map
+toList
方法處理下:
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
backgroundColor: Colors.orangeAccent,
title: new TabBar(
controller: _tabController,
tabs: myTabs.map((NewsTab item){ //NewsTab可以不用宣告
return new Tab(text: item.text==null?'錯誤':item.text);
}).toList(), //記住要用toList()轉換一下map的結果,否則會由於型別不匹配而報錯
indicatorColor: Colors.white,
isScrollable: true, //水平滾動的開關,開啟後Tab標籤可自適應寬度並可橫向拉動,並自動從左到右排列,預設關閉
),
),
body: new TabBarView(
controller: _tabController,
children: myTabs.map((item) {
return item.newsList; //使用引數值
}).toList(),
),
);
}
複製程式碼
以上對新聞頁面的結構進行了重構,重頭戲就是完善NewsList物件。於是乎,在初始化NewsList物件時發起HTTP請求應該是個不錯的辦法:
具體是怎麼初始化資料的,第三步會講到,踩了不少坑。這裡的重點是,Flutter提倡資料驅動元件的建立,元件自己無法觸發動態建立物件,只有通過資料繫結的方式,實現物件的重繪和動態載入,原理和react類似,比如:
第三步
到了這一步,完全進入踩坑模式。
- 踩坑1 http和httpclient都是IO非同步操作,其內建的請求函式的返回值是Future型別物件,需要提前宣告定義型別如:
Future<String>
,返回值也需要await
非同步處理後才可以轉換成需要的資料型別:
上圖中列舉了兩種方法,建議使用下面那種,因為如果能從返回值中提取請求獲取的資料,即可將所有的http請求封裝到API檔案中去,不必寫在頁面程式碼中,原因大家都懂的。
注意在setState()
之前有一句if(!mounted) return
,因為非同步請求資料和控制元件的渲染是同時進行的,如果程式碼已經執行到了setState
,但是資料還沒有獲取到,此時setState
觸發的控制元件渲染就會報錯,為了避免這種空值錯誤,在setState
之前先判斷空間是否已經渲染完成,mounted
即Flutter內建的當前控制元件的狀態標識,記住就好。
- 踩坑2 在ListView直屬的Column裡面不能用Expanded控制元件
圖中的提示說使用Flexible控制元件更佳,然而實際上Flexible也會報錯。報錯的英文大概意思是ListView控制元件生成未知長度的列表時,總是會自動壓縮每一個子元素的高度,而Expanded和Flexible都是可以自由伸縮的控制元件,造成ListView的子元素無法確定繪製的高度,為了使超出螢幕寬度的新聞標題自動換行,這個時候用ListTile頂替一下吧。
- 踩坑3 處理不同新聞圖片數量不一致的問題
如上圖,肯定是不行的,控制元件的子元素是不允許為空的,於是使用條件判斷的方式封裝一下:
- 踩坑3 深刻體會
map
陣列和Object
陣列的使用,newsinfo.["title"]
和newsinfo.title
兩者的newsinfo
型別是不一樣的,詳細還是到原始碼中去體會吧,注意對比newsinfo
和myTab
這兩個的用法。
這次頁面寫的非常辛苦,而且還沒實現滾動重新整理或頂部下拉重新整理的效果,下一篇再更吧,還有很多要點我在原始碼中有標識,可以去我的Git中慢慢品味,今天就更到這裡,滾去睡覺,真的來不起了。
感謝大家的支援,請關注我的Flutter圈子,多多投稿,也可以加入**flutter 中文社群(官方QQ群:338252156)**共同成長,謝謝大家~