Flutter曾經有很是好用的多選組件,例如Sh1d0w的multi_image_picker
,但他們都有或多或少的問題,例如不支持GIF選擇,不支持視頻或音頻選擇,定製程度不夠高、依賴原生組件、不是純Dart組件等。git
隨着Flutter的不斷髮展,愈來愈多的packages涌現,項目迭代使得multi_image_picker
已逐漸不能知足需求,且其依賴的iOS原生依賴做者在開源方向上的態度也使得這個庫再也不穩定,我我的便萌生了本身定製插件的想法。因而利用清明前一週開始至今的閒時,結合本身的項目OpenJMU定製了一個純Dart的仿微信的資源選擇組件,本次主要使用了三個重要依賴:photo_manager
出自財經龍大佬之手,提供完整的API獲取資源信息,爲定製資源選擇組件提供了啓動基礎🤣;extended_image
出自法佬之手,做爲強力的圖片展現組件,體驗+++++;以及你們熟知的provider
用於維護選擇器及各類部件的狀態。github
wechat_assets_picker
是一個對標微信的多選資源選擇器,99%接近於原生微信的操做,純Dart編寫,支持選擇的同時也支持預覽資源。本篇闡述的內容,在源碼中均有對應的註釋進行簡易說明。若是你是源碼選手,請移步repo。截止發文已於pub發佈1.3.0版本,支持以下功能:微信
效果圖: markdown
下面是具體的實現過程,將基於1.3.0版本進行說明。對於涉及到各依賴的使用方法,請移步對應倉庫進行查看。less
調用一個組件的方法是一切的開始,既然這個組件是一個純Dart組件,那麼也應該基於Flutter的上下文(context
)進行調用,用於路由跳轉。因此咱們很快的寫出一個組件的靜態調用方法:async
class AssetPicker extends StatlessWidget {
/// 跳轉至選擇器的靜態方法
static Future<void> pickAssets(BuildContext context) async {}
}
複製代碼
做爲一個多選組件,固然須要知道我能選幾個資源,因此加入int maxAssets
指定最大的資源可選數量,默認爲9
;ide
用戶能夠自定義網格數量,因此加入int gridCount
指定網格每行格子數,默認爲4
;佈局
用戶能夠指定縮略圖的清晰度,因此加入int pageThumbSize
指定選擇器中縮略圖加載的像素,默認爲200
;性能
再加億點......ui
最後咱們的靜態調用方法以下:
static Future<List<AssetEntity>> pickAssets( // 經過路由來傳遞已選中的資源
BuildContext context, {
int maxAssets = 9,
int pathThumbSize = 200,
int gridCount = 4,
RequestType requestType = RequestType.image, // 請求加載的類型
List<AssetEntity> selectedAssets, // 已選的資源,用來處理重複選中的問題
Color themeColor = C.themeColor, // 主題色,默認採用了微信的#00bc56
TextDelegate textDelegate, // 文字代理構建,用於構建每一個文字點
}) async {}
複製代碼
一個完整的靜態方法,經過AssetPicker.pickAssets
就能夠調用了。
做爲一個一把梭選手,在這裏選用了ChangeNotifier
做爲選擇器的model,進行對應狀態的控制,由於涉及到大量資源的展現,若是不進行局部控制,將致使性能的大幅度降低。接下來開始設計AssetPickerProvider
。
選擇器須要保持什麼狀態?簡單分析後,大概須要幾個狀態:
bool isAssetsEmpty
)。在加載完成後,若是設備沒有資源,則展現空佈局。bool hasAssetsToDisplay
)。切換路徑加載後,若是路徑下沒有資源,則展現空佈局。bool isSwitchingPath
)。正在進行切換路徑操做時,顯示路徑切換組件,並變換對應Widget
。Map<AssetPathEntity, Uint8List> pathEntityList
)。保存全部的資源路徑,並加載他們的第一個資源的縮略圖,提供給路徑切換組件。AssetPathEntity currentPathEntity
)。List<AssetEntity> currentAssets
)。List<AssetEntity> selectedAssets
)。看上去很複雜,但上述狀態均爲必要的內容,從而保證咱們的選擇器可以正常工做。
這裏分享一個知識點:在model中咱們經常須要存儲一些集合數據(Map
/Set
/List
),在Selector
進行比較時,因爲比對的仍然是同一個對象,比較的時候prev == next
,沒法得出正確結果。這時咱們須要使用集合的from
方法,例如Map.from
、List.from
生成新的集合對象,就可讓Selector
正常的比較先後變化啦~舉個🌰
set selectedAssets(List<AssetEntity> value) {
assert(value != null);
if (value == _selectedAssets) {
return;
}
_selectedAssets = List<AssetEntity>.from(value);
notifyListeners();
}
複製代碼
狀態管理好了,咱們還須要把選擇器使用到的方法,一同放進model,結合數據一同使用。這裏再也不贅述源碼,包含的方法有:獲取全部的資源路徑、獲取指定路徑下的資源、獲取指定路徑下的第一個資源的縮略數據、選中&取消選中資源、切換路徑。
至此繼續調整咱們的靜態方法,在方法中構造model並傳入組件:
static Future<List<AssetEntity>> pickAssets(
BuildContext context, {
int maxAssets = 9,
int pathThumbSize = 200,
int gridCount = 4,
RequestType requestType = RequestType.image,
List<AssetEntity> selectedAssets,
Color themeColor = C.themeColor,
TextDelegate textDelegate,
}) async {
final bool isPermissionGranted = await PhotoManager.requestPermission(); // 調用前檢查權限,經過才拉起
if (isPermissionGranted) {
final AssetPickerProvider provider = AssetPickerProvider( // 構建model
maxAssets: maxAssets,
pathThumbSize: pathThumbSize,
selectedAssets: selectedAssets,
requestType: requestType,
);
final WidgetBuilder picker = (BuildContext _) => AssetPicker( // 構建組件
provider: provider,
gridCount: gridCount,
textDelegate: textDelegate,
);
final List<AssetEntity> result = await Navigator.of(context).push<List<AssetEntity>>( // 構建路由
Platform.isAndroid
? MaterialPageRoute<List<AssetEntity>>(builder: picker)
: CupertinoPageRoute<List<AssetEntity>>(builder: picker),
);
return result;
} else {
return null;
}
}
複製代碼
在選擇器中咱們一定有各處文字提示,或是在按鈕裏,或是在佈局填充裏。爲了增長可定製程度,在此我定義了文字代理抽象類TextDelegate
,用於構建各處文字。聞其名而知其意,上代碼:
abstract class TextDelegate {
/// 確認按鈕的字段
String confirm;
/// 返回按鈕的字段
String cancel;
/// 編輯按鈕的字段
String edit;
/// 選擇器沒有可顯示的內容時的佔位字段
String emptyPlaceHolder;
/// GIF指示的字段
String gifIndicator;
/// HEIC類型資源加載失敗的字段
String heicNotSupported;
/// 資源加載失敗時的字段
String loadFailed;
/// 選擇是否原圖的字段
String original;
/// 預覽按鈕的字段
String preview;
/// 選擇按鈕的字段
String select;
/// 未支持的資源類型的字段
String unSupportedAssetType;
/// 該字段用在選擇器視頻部件上,用於顯示視頻資源的時長。
String videoIndicatorBuilder(Duration duration);
}
複製代碼
默認還提供了DefaultTextDelegate
,做爲默認的文字實現。
開發Flutter已經走過了一年的時間,慢慢地開始學會本身動手豐衣足食。下一篇咱們將繼續分析插件的界面開發內容~(我也不知道何時有下一篇😉)
最後歡迎加入Flutter Candies,一塊兒生產可愛的Flutter小糖果 (QQ羣:181398081)