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.