MASA MAUI Plugin (九)Android相册多选照片(使用Android Jetpack套件库)
背景
MAUI的出现,赋予了广大Net开发者开发多平台应用的能力,MAUI 是Xamarin.Forms演变而来,但是相比Xamarin性能更好,可扩展性更强,结构更简单。但是MAUI对于平台相关的实现并不完整。所以MASA团队开展了一个实验性项目,意在对微软MAUI的补充和扩展,项目地址https://github.com/BlazorComponent/MASA.Blazor/tree/feature/Maui/src/Masa.Blazor.Maui.Plugin,每个功能都有单独的demo演示项目,考虑到app安装文件体积(虽然MAUI已经集成裁剪功能,但是该功能对于代码本身有影响),届时每一个功能都会以单独的nuget包的形式提供,方便测试,现在项目才刚刚开始,但是相信很快就会有可以交付的内容啦。
前言
本系列文章面向移动开发小白,从零开始进行平台相关功能开发,演示如何参考平台的官方文档使用MAUI技术来开发相应功能。
介绍
Jetpack 包含一系列 Android 库,它们都采用最佳做法并在 Android 应用中提供向后兼容性。
上一篇我们是通过Intent实现的,今天我们用Jetpack 实现相册的多选功能。
一、实现方式
可以使用以下 activity 结果协定来启动照片选择器:
PickVisualMedia,用于选择单张图片或单个视频。
PickMultipleVisualMedia,用于选择多张图片或多个视频。
我们的需求是可以多选照片,我们主要介绍PickMultipleVisualMedia的使用方法。
我们先看一下JAVA的示例代码
JAVA代码
// Registering Photo Picker activity launcher with multiple selects (5 max in this example)
ActivityResultLauncher<PickVisualMediaRequest> pickMultipleMedia =
registerForActivityResult(new PickMultipleVisualMedia(5), uris -> {
// Callback is invoked after the user selects media items or closes the
// photo picker.
if (!uris.isEmpty()) {
Log.d("PhotoPicker", "Number of items selected: " + uris.size());
} else {
Log.d("PhotoPicker", "No media selected");
}
});
// For this example, launch the photo picker and allow the user to choose images
// and videos. If you want the user to select a specific type of media file,
// use the overloaded versions of launch(), as shown in the section about how
// to select a single media item.
pickMultipleMedia.launch(new PickVisualMediaRequest.Builder()
.setMediaType(PickVisualMedia.ImageAndVideo.INSTANCE)
.build());
这里先介绍一下registerForActivityResult
在Android中启动另一个 activity(无论是您应用中的 activity 还是其他应用中的 activity)不一定是单向操作。我们需要获取activity的返回结果。这里我们就是启动了相册,并获取用户选取照片的返回结果。其他例如打开相机获取拍照结果,打开通讯录获取联系人结果都是具体的应用场景。
虽然所有 API 级别的 Activity 类均提供底层 startActivityForResult() 和 onActivityResult() API,但Android官方强烈建议使用 AndroidX Activity 和 Fragment 中引入的 Activity Result API。
Activity Result API 提供了用于注册结果、启动结果以及在系统分派结果后对其进行处理的组件。
在启动 activity 以获取结果时,可能会出现您的进程和 activity 因内存不足而被销毁的情况;如果是使用相机等内存密集型操作,几乎可以确定会出现这种情况。
因此,Activity Result API 会将结果回调从您之前启动另一个 activity 的代码位置分离开来。由于在重新创建进程和 activity 时需要使用结果回调,因此每次创建 activity 时都必须无条件注册回调,即使启动另一个 activity 的逻辑仅基于用户输入内容或其他业务逻辑也是如此。
位于 ComponentActivity 或 Fragment 中时,Activity Result API 会提供 registerForActivityResult() API,用于注册结果回调。registerForActivityResult() 接受 ActivityResultContract 和 ActivityResultCallback 作为参数,并返回 ActivityResultLauncher,用来启动另一个 activity。
ActivityResultContract 定义生成结果所需的输入类型以及结果的输出类型。这些 API 可为拍照和请求权限等基本 intent 操作提供默认协定。当然也可以创建自己的自定义协定。
ActivityResultCallback 是单一方法接口,带有 onActivityResult() 方法,可接受 ActivityResultContract 中定义的输出类型的对象:
JAVA代码
// GetContent creates an ActivityResultLauncher<String> to allow you to pass
// in the mime type you'd like to allow the user to select
ActivityResultLauncher<String> mGetContent = registerForActivityResult(new GetContent(),
new ActivityResultCallback<Uri>() {
@Override
public void onActivityResult(Uri uri) {
// Handle the returned Uri
}
});
这里的代码看起来很简单,我们只需要在registerForActivityResult的第二个参数中new一个ActivityResultCallback并重写onActivityResult方法即可实现获取用户操作返回的需求。但是目前在MAUI中实现并非如此简单,因为MAUI中没有定义好的ActivityResultCallback类。下面我们来编写代码。
二、代码编写
1、实现代码
在上文代码的基础上,我们继续在MainActivity.cs添加代码
public class MainActivity : MauiAppCompatActivity
{
internal static MainActivity Instance { get; private set; }
internal static ActivityResultLauncher PickMultipleMedia { get; private set; }
public TaskCompletionSource<Dictionary<string, string>> PickImageTaskCompletionSource { set; get; }
protected override void OnCreate(Bundle savedInstanceState)
{
Instance = this;
PickMultipleMedia = Instance.RegisterForActivityResult(new ActivityResultContracts.PickMultipleVisualMedia(100), new ActivityResultCallback());
base.OnCreate(savedInstanceState);
}
private class ActivityResultCallback : Java.Lang.Object, IActivityResultCallback
{
public void OnActivityResult(Java.Lang.Object p0)
{
if (!p0.Equals(new Android.Runtime.JavaList()))
{
var list = (Android.Runtime.JavaList)p0;
if (!list.IsEmpty)
{
var uris = list.Cast<Uri>().ToList();
var fileList = Instance.GetImageDicFromUris(uris);
Instance.PickImageTaskCompletionSource.SetResult(fileList);
}
else
{
Instance.PickImageTaskCompletionSource.SetResult(new Dictionary<string, string>());
}
}
}
}
}
我们创建了一个静态的ActivityResultLauncher 类型的PickMultipleMedia,并在OnCreate方法中通过RegisterForActivityResult注册,方法第一个参数类型为ActivityResultContract,我们设置了100个图片的限制,第二个参数是一个IActivityResultCallback类型的Callback。由于默认没有提供,我们需要自己定义。
注意:我们的callback方法在继承IActivityResultCallback接口的同时,还必须显示的继承Java.Lang.Object,否则会报错。
我们仅需实现OnActivityResult方法即可,这里注意,方法的参数为Java.Lang.Object类型,有些文章会让我们将Java.Lang.Object强制转换为ActivityResult类型,然后再获取其中的文件Uri,但是经过测试目前在MAUI中不可用,转换之后永远为null。经过多次尝试后,确定多选照片返回的类型为Android.Runtime.JavaList。
我这里通过 !p0.Equals(new Android.Runtime.JavaList()) 判断用户没有选择任何照片的场景。最后通过遍历,使用之前写好的GetImageDicFromUris方法获取所有文件的内容。
2、测试代码
我们在上文的IPhotoPickerService.cs接口中扩展一个GetImageAsync4方便我们对几种实现方式进行对比。
public class AndroidPhotoPickerService : IPhotoPickerService
{
...
public Task<Dictionary<string, string>> GetImageAsync4()
{
MainActivity.PickMultipleMedia.Launch(new PickVisualMediaRequest.Builder()
.SetMediaType(ActivityResultContracts.PickVisualMedia.ImageAndVideo.Instance).Build());
MainActivity.Instance.PickImageTaskCompletionSource = new TaskCompletionSource<Dictionary<string, string>>();
return MainActivity.Instance.PickImageTaskCompletionSource.Task;
}
}
这里使用的方法非常简单,参考上面JAVA的写法即可
JAVA代码
pickMultipleMedia.launch(new PickVisualMediaRequest.Builder()
.setMediaType(PickVisualMedia.ImageAndVideo.INSTANCE)
.build());
在Index.razor中添加一个MListItem
<MList>
...
<MListItem OnClick="GetImageAsync4">
<MListItemContent>
<MListItemTitle>Jetpack-PickMultipleVisualMedia</MListItemTitle>
</MListItemContent>
</MListItem>
</MList>
三、演示效果
注意界面的变化,这里是以半屏弹出的方式展示的。
- Android 套件 Jetpack 照片 Pluginandroid套件jetpack照片 160 livedata android jetpack android jetpack compose 160 viewmodel android jetpack livedata android jetpack数据 android jetpack compose动画 databinding android jetpack数据 android jetpack compose button 瀑布flowcolumn android jetpack jetpack_compose android jetpack compose