Maui Blazor 安卓Android 多选照片以及快速显示照片

发布时间 2023-11-25 14:26:21作者: MTony

1. 本文感谢两位大佬提供相关教程,相关文章和具体实现原理请参考如下链接:

Sunday866: MASA MAUI Plugin (八)Android相册多选照片(Intent 方式): https://www.cnblogs.com/sunday866/p/17331295.html

YU-CORE:  MAUI Blazor 显示本地图片新思路:https://www.cnblogs.com/Yu-Core/p/17571292.html

 

2. 在MauiBlazor项目根目录新建Service文件夹,新建一个IPhotoPickerService.cs接口Interface进行调用:

using System;

namespace AndroidPhotoPicker.Service
{
    public interface IPhotoPickerService
    {
        /// <summary>
        /// Intent
        /// </summary>
        Task<Dictionary<string, string>> GetImageAsyncByIntent();
    }
}

3. 在Platforms-Android文件夹内新建AndroidPhotoPIckerService.cs:

using System;
using Android.Content;
using Android.Provider;
using AndroidPhotoPicker.Service;
using AndroidX.Activity.Result;
using AndroidX.Activity.Result.Contract;

namespace AndroidPhotoPicker.Platforms.Android
{
    public class AndroidPhotoPickerService : IPhotoPickerService
    {

        /// <summary>
        /// Intent
        /// </summary>
        public Task<Dictionary<string, string>> GetImageAsyncByIntent()
        {
            Intent intent = new Intent(Intent.ActionPick);
            intent.SetDataAndType(MediaStore.Images.Media.ExternalContentUri, "image/*");
            intent.PutExtra(Intent.ExtraAllowMultiple, true);
            MainActivity.Instance.StartActivityForResult(Intent.CreateChooser(intent, "Select Picture"),
                MainActivity.PickImageId);
            MainActivity.Instance.PickImageTaskCompletionSource = new TaskCompletionSource<Dictionary<string, string>>();
            return MainActivity.Instance.PickImageTaskCompletionSource.Task;
        }

    }
}

4. 在Platforms-Android文件夹内MainActivity.cs里添加如下代码:

using Android.App;
using Android.Content.PM;
using Android.OS;
using AndroidX.Activity.Result;
using AndroidX.Activity.Result.Contract;
using AndroidX.DocumentFile.Provider;

namespace AndroidPhotoPicker;

[Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)]
public class MainActivity : MauiAppCompatActivity
{
    internal static MainActivity Instance { get; private set; }
    public static readonly int PickImageId = 1000;
    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);
    }

    protected Dictionary<string, string> GetImageDicFromUris(List<Android.Net.Uri> list)
    {
        Dictionary<string, string> fileList = new Dictionary<string, string>();
        for (int i = 0; i < list.Count; i++)
        {
            var imageUri = list[i];
            var documentFile = DocumentFile.FromSingleUri(Instance, imageUri);
            if (documentFile != null)
            {

                using (var stream = Instance.ContentResolver.OpenInputStream(imageUri))
                {
                    string tempFolderPath = Path.GetTempPath();
                    string filePath = Path.Combine(tempFolderPath, $"{Guid.NewGuid()}.{Path.GetExtension(documentFile.Name)}");
                    using (FileStream fs = new FileStream(filePath, FileMode.Create))
                    {
                        stream.CopyTo(fs);
                        fileList.Add($"{Guid.NewGuid()}.{Path.GetExtension(documentFile.Name)}", filePath);
                        Console.WriteLine(filePath);
                    }
                }
            }
        }
        return fileList;
    }
    protected override void OnActivityResult(int requestCode, Result resultCode, Android.Content.Intent intent)
    {
        base.OnActivityResult(requestCode, resultCode, intent);

        if (requestCode == PickImageId)
        {
            if ((resultCode == Result.Ok) && (intent != null))
            {
                var imageNames = intent.ClipData;

                if (imageNames != null)
                {
                    var uris = new List<Android.Net.Uri>();

                    for (int i = 0; i < imageNames.ItemCount; i++)
                    {
                        var imageUri = imageNames.GetItemAt(i).Uri;
                        uris.Add(imageUri);
                    }

                    var fileList = Instance.GetImageDicFromUris(uris);
                    PickImageTaskCompletionSource.SetResult(fileList);
                }
            }
            else
            {
                PickImageTaskCompletionSource.SetResult(new Dictionary<string, string>());
            }
        }
    }
    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<Android.Net.Uri>().ToList();

                    var fileList = Instance.GetImageDicFromUris(uris);
                    Instance.PickImageTaskCompletionSource.SetResult(fileList);
                }
                else
                {
                    Instance.PickImageTaskCompletionSource.SetResult(new Dictionary<string, string>());
                }
            }
        }
    }

}
展开代码

5. 在MauiProgram.cs里注册安卓端接口:

#if ANDROID
        builder.Services.AddSingleton<IPhotoPickerService, AndroidPhotoPickerService>();
#endif

6. 在Index.razor里添加测试代码进行多选照片测试:

@page "/"
@using AndroidPhotoPicker.Service

<h1>Hello, world!</h1>

Welcome to your new app.

<button @onclick="GetImageAsync3">添加图片INTENT</button>


@if (_phoneDictionary.Any())
{
    @foreach (var phone in _phoneDictionary)
    {
        <div style="height: 100%; width: 100%;">
            <img src="@phone.Value" style="height:90px;width:90px;object-fit:cover;" />
        </div>
        <div>图片名称: @phone.Key</div>
    }
}

@code{

    [Inject]
    private IPhotoPickerService _photoPickerService { get; set; } //注入服务接口

    private Dictionary<string, string> _phoneDictionary { get; set; } = new Dictionary<string, string>();//图片路径字典
    private async Task GetImageAsync3()
    {
        var photoDic = await _photoPickerService.GetImageAsync3();
        foreach (var photo in photoDic)
        {
            _phoneDictionary.Add(photo.Key, photo.Value);
        }
        await InvokeAsync(StateHasChanged);
    }

}

7. Android文件夹内的AndroidManifest.xml里别忘记添加读取储存的权限:

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

8. 在MainPage.xaml里添加BlazorWebViewInitializing和BlazorWebViewInitialized事件,参考如下:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:AndroidPhotoPicker"
             x:Class="AndroidPhotoPicker.MainPage"
             BackgroundColor="{DynamicResource PageBackgroundColor}">

    <BlazorWebView x:Name="blazorWebView" HostPage="wwwroot/index.html" BlazorWebViewInitializing="blazorWebView_BlazorWebViewInitializing" BlazorWebViewInitialized="blazorWebView_BlazorWebViewInitialized">
        <BlazorWebView.RootComponents>
            <RootComponent Selector="#app" ComponentType="{x:Type local:Main}" />
        </BlazorWebView.RootComponents>
    </BlazorWebView>

</ContentPage>

9. 在MainPage.xaml.cs里BlazorWebViewInitializing(暂时留空,IOS快速显示本地图片后续添加相关代码)和BlazorWebViewInitialized事件里添加如下代码:

    void blazorWebView_BlazorWebViewInitialized(System.Object sender, Microsoft.AspNetCore.Components.WebView.BlazorWebViewInitializedEventArgs e)
    {
#if ANDROID
        e.WebView.SetWebViewClient(new MyWebViewClient(e.WebView.WebViewClient));
#endif
    }
#if ANDROID
    private class MyWebViewClient : WebViewClient
    {
        private WebViewClient WebViewClient { get; }

        public MyWebViewClient(WebViewClient webViewClient)
        {
            WebViewClient = webViewClient;
        }

        public override bool ShouldOverrideUrlLoading(Android.Webkit.WebView view, IWebResourceRequest request)
        {
            return WebViewClient.ShouldOverrideUrlLoading(view, request);
        }

        public override WebResourceResponse ShouldInterceptRequest(Android.Webkit.WebView view, IWebResourceRequest request)
        {
            var resourceResponse = WebViewClient.ShouldInterceptRequest(view, request);
            if (resourceResponse == null)
                return null;
            if (resourceResponse.StatusCode == 404)
            {
                var path = request.Url.Path;
                if (File.Exists(path))
                {
                    string mime = MimeTypeMap.Singleton.GetMimeTypeFromExtension(Path.GetExtension(path));
                    string encoding = "UTF-8";
                    Stream stream = File.OpenRead(path);
                    return new(mime, encoding, stream);
                }
            }
            //Debug.WriteLine("路径:" + request.Url.ToString());
            return resourceResponse;
        }

        public override void OnPageFinished(Android.Webkit.WebView view, string url)
        => WebViewClient.OnPageFinished(view, url);

        protected override void Dispose(bool disposing)
        {
            if (!disposing)
                return;

            WebViewClient.Dispose();
        }
    }
#endif