上一篇介紹Banner的開發(fā)。在大多數(shù)應(yīng)用場景中。banner和ListView通常是一起顯示的。 并且能夠共同滑動。例如如下界面:
創(chuàng)新互聯(lián)公司主營拉孜網(wǎng)站建設(shè)的網(wǎng)絡(luò)公司,主營網(wǎng)站建設(shè)方案,成都App制作,拉孜h5小程序開發(fā)搭建,拉孜網(wǎng)站營銷推廣歡迎拉孜等地區(qū)企業(yè)咨詢
要實現(xiàn)上圖的界面,直接想到是ListView添加Header。但在Flutter中,ListView 組件相當(dāng)于RecyclerView,所以添加Header也用RecyclerView的原理:
封裝ListPage組件,list_page.dart
使用及測試:異步加載網(wǎng)絡(luò)數(shù)據(jù)使用
按照給定尺寸進(jìn)行圖片的解碼,而不是解碼整個圖片的尺寸,用來減少內(nèi)存的占用。
官方文檔:
官方說明:
Instructs Flutter to decode the image at the specified dimensions instead of at its native size.
This allows finer control of the size of the image in ImageCache and is generally used to reduce the memory footprint of ImageCache .
The decoded image may still be displayed at sizes other than the cached size provided here.
使用:
三方庫: cached_network_image 限2.5.0之后版本才可用
設(shè)定最大的緩存寬度和高度 this.maxWidthDiskCache 、 this.maxHeightDiskCache
使用:
從相冊選取圖片,展示時使用指定尺寸寬高進(jìn)行處理。
使用三方庫:
使用自定義 provider 來指定所需圖片的寬高:
AssetEntityImageProvider 傳入寬高和圖片原圖 AssetEntity 數(shù)據(jù)。
provider 中 key.entity.thumbDataWithSize 方法:
進(jìn)入 entity 中 thumbDataWithSize 方法:
進(jìn)入 _getThumbDataWithId 方法中,
進(jìn)入getThumb:
調(diào)用iOS原生的獲取圖片方法,
進(jìn)入 getThumbWithId 方法,
原生實現(xiàn)獲取置頂寬高縮略圖方法實現(xiàn):
使用 iOS 原生類 PHImageManager 的
來獲取縮略圖。
此控件的package我已經(jīng)托管到了 pub倉庫
如果你被墻住了,也可以看 國內(nèi)鏡像
使用方式就是在你的flutter pubspec.yaml中添加依賴:
然后flutter packages get更新依賴即可
最近寫demo時發(fā)現(xiàn)Flutter自帶的ListView widget很簡陋,沒有分隔線,沒有section/row之分,也沒有sectionHeader,如果要實現(xiàn)一個有分割線,有section區(qū)分,有section header的ListView,耦合會非常嚴(yán)重:
在 上沒有找到封裝好的這種TableView,于是乎決定自己寫一個,命名為SectionTableView
本人是iOS開發(fā),所以習(xí)慣了iOS上的UITableView的調(diào)用風(fēng)格,所以在實現(xiàn)flutter的SectionTableView時,決定實現(xiàn)如下功能
為了實現(xiàn)這些功能,并且方便后期增加滾動功能,上下拉刷新功能,使用了StatefulWidget作為父類:
接著在對應(yīng)的_SectionTableViewState中的build方法中,返回ListView:
熟悉flutter ListView的同學(xué)知道,ListView的builder類方法,有一個itemBuilder回調(diào)函數(shù),參數(shù)是當(dāng)前的上下文,和將要渲染的行索引index,index對應(yīng)想要獲取的某一行控件(cell或者叫ListItem),返回非空的組件就證明這個index有值,返回null就表示列表到盡頭了。
我們需要做的就是對index進(jìn)行映射,判斷當(dāng)前index對應(yīng)的控件,應(yīng)該是列表里的section header,還是分隔線devider,還是某一行的真正內(nèi)容cell。
出于性能的考慮,不可能每次調(diào)用 _buildCell的時候,都計算一遍index對應(yīng)的section和row的位置,所以定義了一個類成員變量indexPathSearch,是數(shù)組,數(shù)組長度就是ListView所有的行,當(dāng) _buildCell 的參數(shù)index大于等于indexPathSearch的長度的時候,就返回null,表示列表內(nèi)容到此為止了。
indexPathSearch里每一個元素,就是index對應(yīng)的section和row(稱為indexPath),index指向?qū)嶋H行(cell)的時候,section和row都是大于等于0的,當(dāng)section大于等于0,row==-1的時候,表示這里是一個section header,當(dāng)兩者都等于-1的時候,表示這里是一個分割線:
計算好了index到indexPath的映射,剩下的就好說了,在_buildCell中,提取indexPath并判斷indexPath的內(nèi)容,返回對應(yīng)的控件:
這是我的第一個flutter package,目前還很簡陋,flutter目前尚且如此,所以大家一起改善它,
下一步將優(yōu)化如下內(nèi)容:
如果大家喜歡,請多多star我的 項目GitHub
Flutter 里的 BuildContext 相信大家都不會陌生,雖然它叫 Context,但是它實際是 Element 的抽象對象,而在 Flutter 里,它主要來自于 ComponentElement 。
關(guān)于 ComponentElement 可以簡單介紹一下,在 Flutter 里根據(jù) Element 可以簡單地被歸納為兩類:
所以一般情況下,我們在 build 方法或者 State 里獲取到的 BuildContext 其實就是 ComponentElement 。
那使用 BuildContext 有什么需要注意的問題 ?
首先如下代碼所示,在該例子里當(dāng)用戶點(diǎn)擊 FloatingActionButton 的時候,代碼里做了一個 2秒的延遲,然后才調(diào)用 pop 退出當(dāng)前頁面。
正常情況下是不會有什么問題,但是當(dāng)用戶在點(diǎn)擊了 FloatingActionButton 之后,又馬上點(diǎn)擊了 AppBar 返回退出應(yīng)用,這時候就會出現(xiàn)以下的錯誤提示。
可以看到此時 log 說,Widget 對應(yīng)的 Element 已經(jīng)不在了,因為在 Navigator.of(context) 被調(diào)用時, context 對應(yīng)的 Element 已經(jīng)隨著我們的退出銷毀。
一般情況下處理這個問題也很簡單, 那就是增加 mounted 判斷,通過 mounted 判斷就可以避免上述的錯誤 。
上面代碼里的 mounted 標(biāo)識位來自于 State , 因為 State 是依附于 Element 創(chuàng)建,所以它可以感知 Element 的生命周期 ,例如 mounted 就是判斷 _element != null; 。
那么到這里我們收獲了一個小技巧: 使用 BuildContext 時,在必須時我們需要通過 mounted 來保證它的有效性 。
那么單純使用 mounted 就可以滿足 context 優(yōu)化的要求了嗎 ?
如下代碼所示,在這個例子里:
由于在 5 秒之內(nèi),Item 被劃出了屏幕,所以對應(yīng)的 Elment 其實是被釋放了,從而由于 mounted 判斷, SnackBar 不會被彈出。
那如果假設(shè)需要在開發(fā)時展示點(diǎn)擊數(shù)據(jù)上報的結(jié)果,也就是 Item 被釋放了還需要彈出,這時候需要如何處理 ?
我們知道不管是 ScaffoldMessenger.of(context) 還是 Navigator.of(context) ,它本質(zhì)還是通過 context 去往上查找對應(yīng)的 InheritedWidget 泛型,所以其實我們可以提前獲取。
所以,如下代碼所示,在 Future.delayed 之前我們就通過 ScaffoldMessenger.of(context); 獲取到 sm 對象,之后就算你直接退出當(dāng)前的列表頁面,5秒過后 SnackBar 也能正常彈出。
為什么頁面銷毀了,但是 SnackBar 還能正常彈出 ?
因為此時通過 of(context); 獲取到的 ScaffoldMessenger 是存在 MaterialApp 里,所以就算頁面銷毀了也不影響 SnackBar 的執(zhí)行。
但是如果我們修改例子,如下代碼所示,在 Scaffold 上面多嵌套一個 ScaffoldMessenger ,這時候在 Item 里通過 ScaffoldMessenger.of(context) 獲取到的就會是當(dāng)前頁面下的 ScaffoldMessenger 。
這種情況下我們只能保證Item 不可見的時候 SnackBar 還能正常彈出, 而如果這時候我們直接退出頁面,還是會出現(xiàn)以下的錯誤提示,因為 ScaffoldMessenger 也被銷毀了 。
所以到這里我們收獲第二個小技巧: 在異步操作里使用 of(context) ,可以提前獲取,之后再做異步操作,這樣可以盡量保證流程可以完整執(zhí)行 。
既然我們說到通過 of(context) 去獲取上層共享往下共享的 InheritedWidget ,那在哪里獲取就比較好 ?
還記得前面的 log 嗎?在第一個例子出錯時,log 里就提示了一個方法,也就是 State 的 didChangeDependencies 方法。
為什么是官方會建議在這個方法里去調(diào)用 of(context) ?
首先前面我們一直說,通過 of(context) 獲取到的是 InheritedWidget ,而 當(dāng) InheritedWidget 發(fā)生改變時,就是通過觸發(fā)綁定過的 Element 里 State 的 didChangeDependencies 來觸發(fā)更新, 所以在 didChangeDependencies 里調(diào)用 of(context) 有較好的因果關(guān)系 。
那我能在 initState 里提前調(diào)用嗎 ?
當(dāng)然不行,首先如果在 initState 直接調(diào)用如 ScaffoldMessenger.of(context).showSnackBar 方法,就會看到以下的錯誤提示。
這是因為 Element 里會判斷此時的 _StateLifecycle 狀態(tài),如果此時是 _StateLifecycle.created 或者 _StateLifecycle.defunct ,也就是在 initState 和 dispose ,是不允許執(zhí)行 of(context) 操作。
當(dāng)然,如果你硬是想在 initState 下調(diào)用也行,增加一個 Future 執(zhí)行就可以成功執(zhí)行
那我在 build 里直接調(diào)用不行嗎 ?
直接在 build 里調(diào)用肯定可以,雖然 build 會被比較頻繁執(zhí)行,但是 of(context) 操作其實就是在一個 map 里通過 key - value 獲取泛型對象,所以對性能不會有太大的影響。
真正對性能有影響的是 of(context) 的綁定數(shù)量和獲取到對象之后的自定義邏輯 ,例如你通過 MediaQuery.of(context).size 獲取到屏幕大小之后,通過一系列復(fù)雜計算來定位你的控件。
例如上面這段代碼,可能會導(dǎo)致鍵盤在彈出的時候,雖然當(dāng)前頁面并沒有完全展示,但是也會導(dǎo)致你的控件不斷重新計算從而出現(xiàn)卡頓。
所以到這里我們又收獲了一個小技巧: 對于 of(context) 的相關(guān)操作邏輯,可以盡量放到 didChangeDependencies 里去處理 。
感謝 知乎日報-API-分析 提供的api幫助完成這個demo
該項目完全開源,單純?yōu)榱藢W(xué)習(xí)與交流,希望大家喜歡,多多提意見。
后續(xù)會將未來學(xué)到的新知識點(diǎn)用到該項目,持續(xù)更新
1.今日熱點(diǎn)
2.主題分類
3.文章詳情
4.抽屜列表增加緩存, 防止多次拉去數(shù)據(jù)
5.評論列表 (界面,動畫優(yōu)化)
6.主題列表 (界面,動畫優(yōu)化)
7.主頁banner自動輪播,手指滑動是禁止輪播,放開則繼續(xù)
8.刷新數(shù)據(jù)失敗,增加重試按鈕
9.分享UI
9.登錄UI,聯(lián)動交互(在評論界面可以點(diǎn)擊寫點(diǎn)評進(jìn)入)
1.Flutter加載Html
1.注冊
2.登錄
3.發(fā)表評論
4.收藏
5.等等
ListView的基礎(chǔ)創(chuàng)建使用有三種方式:
通過默認(rèn)構(gòu)造函數(shù)來創(chuàng)建列表,應(yīng)用場景 = 短列表
這種方式創(chuàng)建的列表存在一個問題:對于那些長列表或者需要較昂貴渲染開銷的子組件,即使還沒有出現(xiàn)在屏幕中但仍然會被ListView所創(chuàng)建,這將是一項較大的開銷,使用不當(dāng)可能引起性能問題甚至卡頓。
長列表
列表子項之間需要分割線
ListView的進(jìn)階使用主要包括:下拉刷新 上拉加載
在Flutter中,ListView結(jié)合RefreshIndicator組件實現(xiàn)下拉刷新
通過包裹一層RefreshIndicator,自定義onRefresh回調(diào)方法實現(xiàn)
方式有兩種:
通過ListView.controller屬性可以判斷ListView是否滑動到了底部,再進(jìn)行上拉加載
NotificationListener是一個Widget,可監(jiān)聽子Widget發(fā)出的Notification
ListView在滑動時中會發(fā)出ScrollNotification類型的通知,可通過監(jiān)聽該通知得到ListView的滑動狀態(tài),判斷是否滑動到了底部,從而進(jìn)行上拉加載
NotificationListener有一個onNotification屬性,定義了監(jiān)聽的回調(diào)方法,通過它來處理加載更多邏輯
不定期分享關(guān)于 安卓開發(fā) 的干貨,追求 短、平、快 ,但 卻不缺深度 。