⑴ Flutter -Listview 4個優化能讓你的列表絲般順滑
優化點1:使用 builder構建列表
當你的列裂絕表元素是動態增長的時候(比如上拉載入更多),請不要直接用children 的方式,一直往children 的數組增加組件,那樣會很糟糕。對於 ListView.builder 是按需構建列表元素,也就是只有那些可見得元素才會調用itemBuilder 構建元素,這樣對於大列表而言性能開銷自然會小很多。
優化點2:禁用 addAutomaticKeepAlives 和 addRepaintBoundaries 特性
這兩個屬性都是為了優化滾動過程中的用戶體驗的。
addAutomaticKeepAlives 特性默認是 true,意思是在列表元素不可見後可以保持元素的狀態,從而在再次出現在屏幕的時候能夠快速構建。這其實是一個拿空間換時間的方法,會造成一定程度得內存開銷。可以設置為 false 關閉這一特性。缺點是滑動過快的時候可能會出現短暫的白屏(實際會很少發生)。
addRepaintBoundaries 是將列表元素使用一個重繪邊界(Repaint Boundary)包裹,從而使得滾動的時候可以避免重繪。而如果列表很容易繪制(列表元素布局比較簡單的情況下)的時候,可以關閉這個特性來提高滾動的流暢度。
優化點3:盡可能將列表元素中不變的組件使用 const 修飾
使用 const 相當於將元素緩存起來實現共用,若列表元素某些部分一直保持不變,那麼可以使用 const 修飾。
優化點4:使用 itemExtent 確定列表元素滾動方向的尺寸
對於很多列表,我們在滾動方向上的尺寸是提前可以根據 UI設計稿知道的,如果能夠知道的話肆兄姿,那麼使用 itemExtent 屬性制定列表元素在滾動方向的尺寸,可以提升性能。這是因為,如果不指定的話,在滾動過程中,會需要推算每個元素在滾動方向的尺寸從而消耗計算資源。
Flutter ListView 的4個優化要點,非常實用哦!實際上,這些要點都可以從官網的文塵配檔里找出對應得說明。因此,如果遇到了性能問題,除了搜索引擎外,也建議多看看官方的文檔。
⑵ Flutter圖片載入與緩存
其中,參數 image 類型為抽象類 ImageProvider ,定義了圖片數據獲取和載入的相關介面。
根據不同的數據來源,派生出不同的 ImageProvider :
抽象類 ImageProvider 提供了一個用於載入數據源的抽象方法 @protected ImageStreamCompleter load(T key, DecoderCallback decode); 介面,不同的數據源定義各自的實現。
子類 NetworkImage 實現如下:
load 方法返回類型為抽象類 ImageStreamCompleter ,其中定義了一些管理圖片載入過程的介面,比如 addListener 、 removeListener 、 等, 為其子類。
第一個參數 codec 類型為 Future<ui.Codec> ,用來對突破進行解碼,當 codec 准備好的時候桐指雀,就會立即對圖片第一幀進行解碼操作。
codec 為 _loadAsync 方法返回值,
_loadAsync 方法實現:
decode 方法的類型:
其中解碼傳入的回調方法 image_provider.DecoderCallback decode ,
傳入 Uint8List ,返回 Future<ui.Codec> 。
而對 decode 回調方法的具體定義,在 ImageProvider 的 resolveStreamForKey 方法中做了定義, resolveStreamForKey 方法在 ImageProvider 的 resolve 方法中有調用, resolve 方法則為 ImageProvider 類層級結構的公共入口點。
resolveStreamForKey 和 resolve 實現如下:
decode 方法,即 PaintingBinding.instance!.instantiateImageCodec ,即為具體圖片解碼的方法實現。
ui.instantiateImageCodec 實現:
descriptor.instantiateCodec 方法實現:
_instantiateCodec 方法的實現,最終到了 native 的實現:
其中返回值類型 Codec 里定義了一些屬性:
obtainKey 方法:
ImageProvider 定義了一個抽象方法 Future<T> obtainKey(ImageConfiguration configuration); ,供子類來實現,其中 NetworkImage 的實現為:
obtainKey 作用:
配合實現圖片緩存, ImageProvider 從逗搜數據源載入完數據後,會在 ImageCache 中緩存圖片數據,圖片數據緩存時一個 Map ,其中 Map 中的 key 便是 obtainKey 。
resolve 作為 ImageProvider 提供給 Image 的主入口方法,參數為 ImageConfiguration ,
resolve 其中調用了 _createErrorHandlerAndKey 方法,設置了成功回調和失敗回調:
其中 _createErrorHandlerAndKey 方法的實現,便調用了 obtainKey 來設置 key 。
在成功回調里,調用局早了方法 resolveStreamForKey ,裡面有具體的緩存實現 PaintingBinding.instance!.imageCache!.putIfAbsent :
PaintingBinding.instance!.imageCache 是ImageCache的一個實例,是 PaintingBinding 的一個屬性,是一個單例,圖片緩存是全局的。
如上述判斷:
ImageCache 定義:
ImageCache 緩存池:
在 NetworkImage 中,對 ImageProvider 的抽象方法 obtainKey 進行了實現,將自己創建了一個同步 Future 進行返回:
同時,自身又重寫了 ImageProvider 定義的 == 比較操作符,通過圖片 url 和圖片的縮放比例 scale 進行比較:
通過ImageCache提供的方法來設置:
⑶ Flutter 仿企業微信多選-listview可見item位置
有一個需求,是仿企業照微信的多選(效果大家自己去看)。我想到了兩種方案:
思路:我們直接通過listview.builder是沒辦法自定義SliverChildBuilderDelegate,我們可以通過listview.custom來自定義SliverChildBuilderDelegate,通過自定義我們可以重寫didFinishLayout方法,拿到裡面緩存的第一個item和最後一個item。可見item的跟緩存item是差5個的,可以間接算出來,後面發現其實不太行,上下滑動之後會顯示之前滑動時候的可見位置。宏此高 正解是:這個裡面還有個estimateMaxScrollOffset方法,正常來說通過它可以獲取到可見的扒槐第一個和最後一個item位置。但是我一開始使用這個方法,不會被回調,後面不知道修改了什麼,就會回調,然後這個位置是准確的。
看下listview.builder的源碼
我們蔽尺可以看到childrenDelegate是直接定義好了的。
在看看listview.custom 的源碼
childrenDelegate這個是一個必傳參數。
⑷ Flutter 本地緩存
Flutter本地存廳森儲可以用 shared_preferences ,其會根據不同操作系統進行相對應的存儲。
在pubspec.yaml添加
`shared_preferences: ^2.0.13`
```d
import 'package:shared_preferences/shared_preferences.dart';
class SpUtils {
SharedPreferences?prefs;
SpUtils._() {
init();
}
static SpUtils?_instance;
扮鄭畝 static preInit() {
_instance ??=SpUtils._();
}
static SpUtilsgetInstance() {
_instance ??=SpUtils._();
return _instance!;
}
void init()async {
prefs ??=await SharedPreferences.getInstance();
}
setString(String key, String value) {
prefs!.setString(key, value);
}
setDouble(String key, double value) {
prefs!.setDouble(key, value);
}
setInt(String key, int value) {
prefs!.setInt(key, value);
}
setBool(String key, bool value) {
prefs!.setBool(key, value);
}
setStringList(String key, List value) {
prefs!.setStringList(key, value);
}
clear(String key){
prefs!.remove(key);
叢睜 }
clearAll(){
prefs!.clear();
}
Tget(String key) {
return prefs!.get(key)as T;
}
}
```
在項目初始頁調用
`SpUtils.preInit();`
存
`SpUtils.getInstance().setString('userId', '12345678');`
`SpUtils.getInstance().setDouble('price', 12.88);`
`SpUtils.getInstance().setInt('count', 200);`
`SpUtils.getInstance().setBool('flag', true);`
取
`SpUtils.getInstance().get('userId');`
刪
`SpUtils.getInstance().clearAll();`
`SpUtils.getInstance().clear('userId');`