背景
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 應用中提供向後相容性。
https://developer.android.google.cn/jetpack?hl=zh-cn
上一篇我們是通過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中不可用,轉換之後永遠為。經過多次嘗試後,确定多選照片傳回的類型為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>
三、示範效果
注意界面的變化,這裡是以半屏彈出的方式展示的。
如果你對我們的開源項目感興趣,無論是代碼貢獻、使用、提 Issue,歡迎聯系我們