MASA MAUI Plugin (九)Android相簿多選照片(使用Android Jetpack套件庫)

語言: CN / TW / HK

背景

MAUI的出現,賦予了廣大.Net開發者開發多平臺應用的能力,MAUI 是Xamarin.Forms演變而來,但是相比Xamarin效能更好,可擴充套件性更強,結構更簡單。但是MAUI對於平臺相關的實現並不完整。所以MASA團隊開展了一個實驗性專案,意在對微軟MAUI的補充和擴充套件

專案地址http://github.com/BlazorComponent/MASA.Blazor/tree/feature/Maui/src/Masa.Blazor.Maui.Plugin

每個功能都有單獨的demo演示專案,考慮到app安裝檔案體積(雖然MAUI已經整合裁剪功能,但是該功能對於程式碼本身有影響),屆時每一個功能都會以單獨的nuget包的形式提供,方便測試,現在專案才剛剛開始,但是相信很快就會有可以交付的內容啦。

前言

本系列文章面向移動開發小白,從零開始進行平臺相關功能開發,演示如何參考平臺的官方文件使用MAUI技術來開發相應功能。

介紹

Jetpack 包含一系列 Android 庫,它們都採用最佳做法並在 Android 應用中提供向後相容性。

http://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() 接受 ActivityResultContractActivityResultCallback 作為引數,並返回 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>

三、演示效果

在這裡插入圖片描述

注意介面的變化,這裡是以半屏彈出的方式展示的。