Skip to content

Commit 6b6a46a

Browse files
committed
✨ Add custom item build mode.
Related to #39 .
1 parent ada7c5d commit 6b6a46a

File tree

3 files changed

+163
-27
lines changed

3 files changed

+163
-27
lines changed

example/lib/pages/multi_assets_page.dart

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,27 @@ class _MultiAssetsPageState extends State<MultiAssetsPage> {
131131
);
132132
},
133133
),
134+
PickMethodModel(
135+
icon: '➕',
136+
name: 'Prepend custom item',
137+
description: 'An custom item will prepend to the assets grid.',
138+
method: (
139+
BuildContext context,
140+
List<AssetEntity> assets,
141+
) async {
142+
return await AssetPicker.pickAssets(
143+
context,
144+
maxAssets: maxAssetsCount,
145+
selectedAssets: assets,
146+
themeColor: themeColor,
147+
requestType: RequestType.common,
148+
customItemPosition: CustomItemPosition.append,
149+
customItemBuilder: (BuildContext context) {
150+
return const Center(child: Text('Custom Widget'));
151+
},
152+
);
153+
},
154+
),
134155
];
135156

136157
Future<void> selectAssets(PickMethodModel model) async {

lib/src/constants/enums.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,7 @@
77
/// un-common pick pattern.
88
/// 提供一些特殊的选择器类型以整合非常规的选择行为。
99
enum SpecialPickerType { wechatMoment }
10+
11+
/// Provide an item slot for custom widget insertion.
12+
/// 提供一个自定义位置供自定义item放入资源列表中。
13+
enum CustomItemPosition { none, prepend, append }

lib/src/widget/asset_picker.dart

Lines changed: 138 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ class AssetPicker extends StatelessWidget {
2929
Color themeColor,
3030
TextDelegate textDelegate,
3131
this.specialPickerType,
32+
this.customItemPosition = CustomItemPosition.none,
33+
this.customItemBuilder,
3234
}) : assert(
3335
provider != null,
3436
'AssetPickerProvider must be provided and not null.',
@@ -75,6 +77,14 @@ class AssetPicker extends StatelessWidget {
7577
/// * [SpecialPickerType.wechatMoment] 微信朋友圈模式。当用户选择了视频,将不能选择图片。
7678
final SpecialPickerType specialPickerType;
7779

80+
/// Allow users set custom item in the picker.
81+
/// 允许用户在选择器中添加一个自定义item,并指定位置。
82+
final CustomItemPosition customItemPosition;
83+
84+
/// The widget builder for the custom item.
85+
/// 自定义item的构造方法
86+
final WidgetBuilder customItemBuilder;
87+
7888
/// Static method to push with the navigator.
7989
/// 跳转至选择器的静态方法
8090
static Future<List<AssetEntity>> pickAssets(
@@ -91,6 +101,8 @@ class AssetPicker extends StatelessWidget {
91101
ThemeData pickerTheme,
92102
SortPathDelegate sortPathDelegate,
93103
TextDelegate textDelegate,
104+
WidgetBuilder customItemBuilder,
105+
CustomItemPosition customItemPosition = CustomItemPosition.none,
94106
Curve routeCurve = Curves.easeIn,
95107
Duration routeDuration = const Duration(milliseconds: 300),
96108
}) async {
@@ -120,6 +132,12 @@ class AssetPicker extends StatelessWidget {
120132
requestType ??= RequestType.image;
121133
}
122134
}
135+
if ((customItemBuilder == null &&
136+
customItemPosition != CustomItemPosition.none) ||
137+
(customItemBuilder != null &&
138+
customItemPosition == CustomItemPosition.none)) {
139+
throw ArgumentError('Custom item didn\'t set properly.');
140+
}
123141

124142
try {
125143
final bool isPermissionGranted = await PhotoManager.requestPermission();
@@ -141,6 +159,8 @@ class AssetPicker extends StatelessWidget {
141159
themeColor: themeColor,
142160
pickerTheme: pickerTheme,
143161
specialPickerType: specialPickerType,
162+
customItemPosition: customItemPosition,
163+
customItemBuilder: customItemBuilder,
144164
);
145165
final List<AssetEntity> result = await Navigator.of(
146166
context,
@@ -602,6 +622,10 @@ class AssetPicker extends StatelessWidget {
602622
fontSize: isAppleOS ? 14.0 : 12.0,
603623
fontWeight: isAppleOS ? FontWeight.w500 : FontWeight.normal,
604624
),
625+
strutStyle: const StrutStyle(
626+
forceStrutHeight: true,
627+
height: 1.0,
628+
),
605629
),
606630
),
607631
),
@@ -658,6 +682,7 @@ class AssetPicker extends StatelessWidget {
658682
),
659683
),
660684
child: Row(
685+
crossAxisAlignment: CrossAxisAlignment.center,
661686
children: <Widget>[
662687
const Icon(
663688
Icons.videocam,
@@ -674,6 +699,10 @@ class AssetPicker extends StatelessWidget {
674699
color: Colors.white,
675700
fontSize: 16.0,
676701
),
702+
strutStyle: const StrutStyle(
703+
forceStrutHeight: true,
704+
height: 1.4,
705+
),
677706
),
678707
),
679708
],
@@ -825,40 +854,122 @@ class AssetPicker extends StatelessWidget {
825854
mainAxisSpacing: itemSpacing,
826855
crossAxisSpacing: itemSpacing,
827856
),
828-
itemCount: currentAssets.length,
857+
itemCount: _assetsGridItemCount(_, currentAssets),
829858
itemBuilder: (BuildContext _, int index) {
830-
if (index == currentAssets.length - gridCount * 3 &&
831-
_.read<AssetPickerProvider>().hasMoreToLoad) {
832-
provider.loadMoreAssets();
833-
}
834-
final AssetEntity asset = currentAssets.elementAt(index);
835-
Widget builder;
836-
switch (asset.type) {
837-
case AssetType.audio:
838-
builder = audioItemBuilder(context, index, asset);
839-
break;
840-
case AssetType.image:
841-
case AssetType.video:
842-
builder = imageAndVideoItemBuilder(context, index, asset);
843-
break;
844-
case AssetType.other:
845-
builder = const SizedBox.shrink();
846-
break;
847-
}
848-
return Stack(
849-
children: <Widget>[
850-
builder,
851-
if (specialPickerType != SpecialPickerType.wechatMoment ||
852-
asset.type != AssetType.video)
853-
_selectIndicator(asset),
854-
],
855-
);
859+
return _assetGridItemBuilder(_, index, currentAssets);
856860
},
857861
);
858862
},
859863
),
860864
);
861865

866+
/// The function which return items count for the assets' grid.
867+
/// 为资源列表提供内容数量计算的方法
868+
int _assetsGridItemCount(
869+
BuildContext context,
870+
List<AssetEntity> currentAssets,
871+
) {
872+
final AssetPathEntity currentPath = Provider.of<AssetPickerProvider>(
873+
context,
874+
listen: false,
875+
).currentPathEntity;
876+
877+
/// Return actual length if current path is all.
878+
/// 如果当前目录是全部内容,则返回实际的内容数量。
879+
if (!currentPath.isAll) {
880+
return currentAssets.length;
881+
}
882+
int length;
883+
switch (customItemPosition) {
884+
case CustomItemPosition.none:
885+
length = currentAssets.length;
886+
break;
887+
case CustomItemPosition.prepend:
888+
case CustomItemPosition.append:
889+
length = currentAssets.length + 1;
890+
break;
891+
}
892+
return length;
893+
}
894+
895+
/// The item builder for the assets' grid.
896+
/// 资源列表项的构建
897+
///
898+
/// There're several conditions within this builder:
899+
/// * Return [customItemBuilder] while the current path is all and [customItemPosition]
900+
/// is not equal to [CustomItemPosition.none].
901+
/// * Return item builder according to the asset's type.
902+
/// * [AssetType.audio] -> [audioItemBuilder]
903+
/// * [AssetType.image], [AssetType.video] -> [imageAndVideoItemBuilder]
904+
/// * Load more assets when the index reached at third line counting backwards.
905+
/// 资源构建有几个条件:
906+
/// * 当前路径是全部资源且 [customItemPosition] 不等于 [CustomItemPosition.none] 时,将会通过
907+
/// [customItemBuilder] 构建内容。
908+
/// * 根据资源类型返回对应类型的构建:
909+
/// * [AssetType.audio] -> [audioItemBuilder] 音频类型
910+
/// * [AssetType.image], [AssetType.video] -> [imageAndVideoItemBuilder] 图片和视频类型
911+
/// * 在索引到达倒数第三列的时候加载更多资源。
912+
Widget _assetGridItemBuilder(
913+
BuildContext context,
914+
int index,
915+
List<AssetEntity> currentAssets,
916+
) {
917+
final AssetPathEntity currentPath = Provider.of<AssetPickerProvider>(
918+
context,
919+
listen: false,
920+
).currentPathEntity;
921+
922+
int currentIndex;
923+
switch (customItemPosition) {
924+
case CustomItemPosition.none:
925+
case CustomItemPosition.append:
926+
currentIndex = index;
927+
break;
928+
case CustomItemPosition.prepend:
929+
currentIndex = index - 1;
930+
break;
931+
}
932+
if (!currentPath.isAll) {
933+
currentIndex = index;
934+
}
935+
936+
if (index == currentAssets.length - gridCount * 3 &&
937+
context.read<AssetPickerProvider>().hasMoreToLoad) {
938+
provider.loadMoreAssets();
939+
}
940+
941+
if (currentPath.isAll) {
942+
if ((index == currentAssets.length &&
943+
customItemPosition == CustomItemPosition.append) ||
944+
(index == 0 && customItemPosition == CustomItemPosition.prepend)) {
945+
return customItemBuilder(context);
946+
}
947+
}
948+
949+
final AssetEntity asset = currentAssets.elementAt(currentIndex);
950+
Widget builder;
951+
switch (asset.type) {
952+
case AssetType.audio:
953+
builder = audioItemBuilder(context, currentIndex, asset);
954+
break;
955+
case AssetType.image:
956+
case AssetType.video:
957+
builder = imageAndVideoItemBuilder(context, currentIndex, asset);
958+
break;
959+
case AssetType.other:
960+
builder = const SizedBox.shrink();
961+
break;
962+
}
963+
return Stack(
964+
children: <Widget>[
965+
builder,
966+
if (specialPickerType != SpecialPickerType.wechatMoment ||
967+
asset.type != AssetType.video)
968+
_selectIndicator(asset),
969+
],
970+
);
971+
}
972+
862973
/// The item builder for audio type of asset.
863974
/// 音频资源的部件构建
864975
Widget audioItemBuilder(

0 commit comments

Comments
 (0)