相關文章
- 幹一個Flutter元件:動動小手磨出一個資源多選外掛(1)——基礎構建篇
- 幹一個Flutter元件:動動小手磨出一個資源多選外掛(2)——介面開發篇(準備中)
背景
Flutter曾經有非常好用的多選元件,例如Sh1d0w的multi_image_picker
,但他們都有或多或少的問題,例如不支援GIF選擇,不支援視訊或音訊選擇,定製程度不夠高、依賴原生元件、不是純Dart元件等。
隨著Flutter的不斷髮展,越來越多的packages湧現,專案迭代使得multi_image_picker
已逐漸不能滿足需求,且其依賴的iOS原生依賴作者在開源方向上的態度也使得這個庫不再穩定,我個人便萌生了自己定製外掛的想法。於是利用清明前一週開始至今的閒時,結合自己的專案OpenJMU定製了一個純Dart的仿微信的資源選擇元件,本次主要使用了三個重要依賴:photo_manager
出自財經龍大佬之手,提供完整的API獲取資源資訊,為定製資源選擇元件提供了啟動基礎?;extended_image
出自法佬之手,作為強力的圖片展示元件,體驗+++++;以及大家熟知的provider
用於維護選擇器及各種部件的狀態。
簡介
wechat_assets_picker
是一個對標微信的多選資源選擇器,99%接近於原生微信的操作,純Dart編寫,支援選擇的同時也支援預覽資源。本篇闡述的內容,在原始碼中均有對應的註釋進行簡易說明。如果你是原始碼選手,請移步repo。截止發文已於pub釋出1.3.0版本,支援如下功能:
- 圖片資源支援
- 視訊資源支援
- 國際化支援
- 自定義文字支援
效果圖:
實現過程
下面是具體的實現過程,將基於1.3.0版本進行說明。對於涉及到各依賴的使用方法,請移步對應倉庫進行檢視。
呼叫方法
呼叫一個元件的方法是一切的開始,既然這個元件是一個純Dart元件,那麼也應該基於Flutter的上下文(context
)進行呼叫,用於路由跳轉。所以我們很快的寫出一個元件的靜態呼叫方法:
class AssetPicker extends StatlessWidget {
/// 跳轉至選擇器的靜態方法
static Future<void> pickAssets(BuildContext context) async {}
}
複製程式碼
作為一個多選元件,當然需要知道我能選幾個資源,所以加入int maxAssets
指定最大的資源可選數量,預設為9
;
使用者可以自定義網格數量,所以加入int gridCount
指定網格每行格子數,預設為4
;
使用者可以指定縮圖的清晰度,所以加入int pageThumbSize
指定選擇器中縮圖載入的畫素,預設為200
;
再加億點......
最後我們的靜態呼叫方法如下:
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)