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)