Related articles

  • Dry a Flutter component: Grind out a resource selection plugin (1) – Basics
  • Dry a Flutter component: Grind a resource with multiple options (2) – UI Development (in preparation)

background

Flutter used to have very useful multichoice components, such as Sh1d0w’s multi_image_picker, but they all had more or less problems, such as not supporting GIF selection, not supporting video or audio selection, not being customizable enough, relying on native components, not being pure Dart components, etc.

With the development of Flutter, more and more packages emerged, and the project iteration made multi_image_picker gradually unable to meet the requirements, and the library was not stable due to the iOS native dependency of the author in the open source direction, so I came up with the idea of my own custom plug-in. Therefore, during the free time from one week before Qingming Festival to now, I customized a pure Dart resource selection component imitating wechat in combination with my own project OpenJMU. This time, I mainly used three important dependencies: Photo_manager from the hands of financial dragon, provides a complete API to obtain resource information, for customizable resource selection components to provide a startup basis 🤣; Extended_image is a powerful image presentation component from the French, experience +++++; And the well-known provider for maintaining the state of the selector and various components.

Introduction to the

Wechat_assets_picker wechat_assets_picker is a multi-selection resource picker for standard wechat. It is 99% similar to the operation of native wechat. It is written in pure Dart and supports selection as well as preview resources. The content of this article, in the source code have corresponding annotations for simple explanation. If you are a source player, please go to repo. Pub has released version 1.3.0, which supports the following functions:

  • Image Resource Support
  • Video Resource Support
  • Internationalization support
  • Custom text support

Effect:

The implementation process

The following is the detailed implementation process, which will be explained based on version 1.3.0. For the dependency usage method, go to the corresponding warehouse to check.

A method is called

The method of calling a component is where it all begins. Since this component is a pure Dart component, it should also be called based on the context of the Flutter to route the jump. So let’s quickly write a static call to a component:

class AssetPicker extends StatlessWidget {
  /// Jump to the static method of the selector
  static Future<void> pickAssets(BuildContext context) async{}}Copy the code

As a multi-select component, of course I need to know how many resources I can select, so I add int maxAssets to specify the maximum number of resources available. Default is 9;

Int gridCount specifies the number of grids per row. Default is 4.

The user can specify the sharpness of the thumbnail, so int pageThumbSize specifies the number of pixels that the thumbnail will load in the selector. Default is 200;

Add another billion points……

Finally, our static call method looks like this:

static Future<List<AssetEntity>> pickAssets( // Pass the selected resource by route
  BuildContext context, {
  int maxAssets = 9.int pathThumbSize = 200.int gridCount = 4,
  RequestType requestType = RequestType.image, // The type of request to load
  List<AssetEntity> selectedAssets, // The selected resource is used to handle the double-selected problem
  Color themeColor = C.themeColor, // The theme color is #00bc56
  TextDelegate textDelegate, // Text proxy build, used to build each text point
}) async {}
Copy the code

A complete static method that can be called by AssetPicker. PickAssets.

Component state maintenance

As a shuttle player, I chose ChangeNotifier as the model selector to control the corresponding state. Because it involves the display of a large number of resources, if local control is not carried out, the performance will be greatly reduced. Let’s start designing the AssetPickerProvider.

What state does the selector need to stay in? After simple analysis, there are probably several states:

  • Whether a resource file exists on the device (bool isAssetsEmpty). After loading, the empty layout is displayed if the device has no resources.
  • Whether resources are available to display in the selected path (bool hasAssetsToDisplay). If there are no resources in the loaded path, the empty layout is displayed.
  • Is path selection in progress (bool isSwitchingPath). The path switching component is displayed when the path switching operation is being performed, and the corresponding path switching component is changedWidget.
  • All resource paths and their first resource’s thumbnail data (Map

    pathEntityList) Save all resource paths and load a thumbnail of their first resource to provide to the path-switching component.
    ,>
  • Resource path being viewed (AssetPathEntity currentPathEntity).
  • All resources of the resource path being viewed (List

    currentAssets).
  • Selected resources (List

    selectedAssets).

It may seem complicated, but all of these states are necessary for our selector to work.

In the model, we often need to store some Set data (Map/Set/List). When comparing the selectors, the same object is still compared, and the correct result cannot be obtained when the Selector is prev == next. In this case, we need to use the from method of the collection, such as map. from, List. From to generate a new collection object, and we can make the Selector change before and after the normal comparison

set selectedAssets(List<AssetEntity> value) {
  assert(value ! =null);
  if (value == _selectedAssets) {
    return;
  }
  _selectedAssets = List<AssetEntity>.from(value);
  notifyListeners();
}
Copy the code

With state management done, we also need to put the methods used by the selector into the model and use them with the data. Here is no longer the source code, including methods: get all resource paths, get the specified path of resources, get the specified path of the first resource under the thumbnail data, select & unselect resources, switch paths.

To continue tweaking our static method, construct the model in the method and pass in the component:

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(); // Check permissions before calling
  if (isPermissionGranted) {
    final AssetPickerProvider provider = AssetPickerProvider( / / build model
      maxAssets: maxAssets,
      pathThumbSize: pathThumbSize,
      selectedAssets: selectedAssets,
      requestType: requestType,
    );
    final WidgetBuilder picker = (BuildContext _) => AssetPicker( // Build the component
      provider: provider,
      gridCount: gridCount,
      textDelegate: textDelegate,
    );
    final List<AssetEntity> result = await Navigator.of(context).push<List<AssetEntity>>( // Build the route
        Platform.isAndroid
            ? MaterialPageRoute<List<AssetEntity>>(builder: picker)
            : CupertinoPageRoute<List<AssetEntity>>(builder: picker),
    );
    return result;
  } else {
    return null; }}Copy the code

Text proxy construction

We must have text hints everywhere in the selector, either in the button or in the layout padding. To increase customizability, here I define the abstract TextDelegate class for building text everywhere. You know what it means by its name.

abstract class TextDelegate {
  /// Confirm the field of the button
  String confirm;

  /// Returns the field of the button
  String cancel;

  /// Edit the field of the button
  String edit;

  /// A placeholder field when the selector has no content to display
  String emptyPlaceHolder;

  /// GIF indicates the field
  String gifIndicator;

  /// Failed to load HEIC resources
  String heicNotSupported;

  /// Field when the resource failed to load
  String loadFailed;

  /// Field to select whether the original image
  String original;

  /// Preview the field of the button
  String preview;

  /// Select the field for the button
  String select;

  /// Field for unsupported resource types
  String unSupportedAssetType;

  /// This field is used in the selector video component to display the duration of the video resource.
  String videoIndicatorBuilder(Duration duration);
}
Copy the code

DefaultTextDelegate is also provided by default as the default literal implementation.

conclusion

Flutter has been in development for a year, slowly learning to feed itself. We will continue to look at the interface development of plugins in the next article (I do not know when the next article will be 😉).

And finally, welcome aboardFlutter CandiesTo produce the lovely Flutter candies.