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