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

語言: CN / TW / HK

背景

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() 接受 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>

三、演示效果

在這裏插入圖片描述

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