干一个Flutter组件:动动小手磨出一个资源多选插件(1)——基础构建篇

相关文章

  • 干一个Flutter组件:动动小手磨出一个资源多选插件(1)——基础构建篇
  • 干一个Flutter组件:动动小手磨出一个资源多选插件(2)——界面开发篇(准备中)

背景

  Flutter曾经有很是好用的多选组件,例如Sh1d0wmulti_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指定最大的资源可选数量,默认为9ide

  用户能够自定义网格数量,因此加入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.fromList.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) FlutterCandies

相关文章
相关标签/搜索