.NET MAUI:iOS16与Android33直接读取相册图片至CollectionView方案
.NET MAUI:iOS 16/Android 33 无手动选择读取相册图片+监听更新方案
针对需求中直接读取系统相册并监听更新的要求,由于Android 30+的Scoped Storage限制和iOS的沙盒机制,无法直接通过文件路径访问资源,以下是分平台的具体实现:
Android 33+ 实现
Android 30+引入Scoped Storage后,GetExternalStoragePublicDirectory这类直接访问公共目录的API已被废弃,必须通过MediaStore查询图片资源。
1. 权限配置
在AndroidManifest.xml中添加必要权限:
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" /> <!-- 兼容Android 12及以下版本 --> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
2. 查询相册图片
通过ContentResolver查询MediaStore获取所有图片:
using Android.Content; using Android.Provider; using Android.Database; using Android.Net; public async Task<List<MediaAsset>> FetchAndroidPhotos() { var photoList = new List<MediaAsset>(); var context = Android.App.Application.Context; // 指定要查询的字段 string[] projection = { MediaStore.Images.Media.InterfaceConsts.Id, MediaStore.Images.Media.InterfaceConsts.Data, MediaStore.Images.Media.InterfaceConsts.DisplayName }; // 查询外部存储的图片,按添加时间倒序排列 using (ICursor cursor = context.ContentResolver.Query( MediaStore.Images.Media.ExternalContentUri, projection, null, null, $"{MediaStore.Images.Media.InterfaceConsts.DateAdded} DESC")) { if (cursor?.MoveToFirst() == true) { do { try { string assetId = cursor.GetString(cursor.GetColumnIndexOrThrow(MediaStore.Images.Media.InterfaceConsts.Id)); string filePath = cursor.GetString(cursor.GetColumnIndexOrThrow(MediaStore.Images.Media.InterfaceConsts.Data)); string fileName = cursor.GetString(cursor.GetColumnIndexOrThrow(MediaStore.Images.Media.InterfaceConsts.DisplayName)); // 转换为MAUI可识别的Uri var contentUri = ContentUris.WithAppendedId(MediaStore.Images.Media.ExternalContentUri, long.Parse(assetId)); var imageSource = ImageSource.FromUri(contentUri); photoList.Add(new MediaAsset { Path = filePath, Name = fileName, ImageSource = imageSource }); } catch { continue; } } while (cursor.MoveToNext()); } } return photoList; }
3. 监听相册更新
通过ContentObserver监听MediaStore的内容变化:
public class PhotoUpdateObserver : ContentObserver { private readonly Action _onUpdate; public PhotoUpdateObserver(Action onUpdate) : base(Android.OS.Handler.MainLooper) { _onUpdate = onUpdate; } public override void OnChange(bool selfChange) { base.OnChange(selfChange); _onUpdate?.Invoke(); } } // 注册监听(放在页面初始化或ViewModel构造中) var observer = new PhotoUpdateObserver(() => { MainThread.BeginInvokeOnMainThread(async () => { MediaAssets.Clear(); var freshPhotos = await FetchAndroidPhotos(); MediaAssets.AddRange(freshPhotos); }); }); Android.App.Application.Context.ContentResolver.RegisterContentObserver( MediaStore.Images.Media.ExternalContentUri, true, observer);
iOS 16+ 实现
iOS沙盒机制禁止直接访问相册路径,必须使用Photos框架获取图片资源。
1. 权限配置
在Info.plist中添加权限说明:
<key>NSPhotoLibraryUsageDescription</key> <string>需要访问相册以展示图片</string> <key>NSPhotoLibraryAddUsageDescription</key> <string>需要访问相册以监听图片更新</string>
2. 查询相册图片
通过PHAsset和PHImageManager获取图片资源:
using Photos; using CoreGraphics; public async Task<List<MediaAsset>> FetchiOSPhotos() { var photoList = new List<MediaAsset>(); // 请求相册权限 var authStatus = await PHPhotoLibrary.RequestAuthorizationAsync(); if (authStatus != PHAuthorizationStatus.Authorized) { return photoList; } // 查询所有图片,按创建时间倒序 var fetchOpts = new PHFetchOptions { SortDescriptors = new[] { new NSSortDescriptor("creationDate", true) } }; var assets = PHAsset.FetchAssets(PHAssetMediaType.Image, fetchOpts); foreach (PHAsset asset in assets) { try { var tcs = new TaskCompletionSource<ImageSource>(); // 请求缩略图(尺寸可按需调整) PHImageManager.DefaultManager.RequestImageForAsset( asset, new CGSize(300, 300), PHImageContentMode.AspectFill, null, (image, info) => { if (image != null) { tcs.SetResult(ImageSource.FromStream(() => { using var pngStream = image.AsPNG().AsStream(); var memoryStream = new MemoryStream(); pngStream.CopyTo(memoryStream); memoryStream.Position = 0; return memoryStream; })); } else { tcs.SetResult(null); } }); var source = await tcs.Task; if (source != null) { photoList.Add(new MediaAsset { LocalIdentifier = asset.LocalIdentifier, ImageSource = source }); } } catch { continue; } } return photoList; }
3. 监听相册更新
通过PHPhotoLibraryChangeObserver监听相册变化:
public class iOSPhotoObserver : PHPhotoLibraryChangeObserver { private readonly Action _onUpdate; public iOSPhotoObserver(Action onUpdate) { _onUpdate = onUpdate; } public override void PhotoLibraryDidChange(PHChange changeInstance) { // 必须回到主线程更新UI MainThread.BeginInvokeOnMainThread(() => { _onUpdate?.Invoke(); }); } } // 注册监听 var observer = new iOSPhotoObserver(() => { MainThread.BeginInvokeOnMainThread(async () => { MediaAssets.Clear(); var freshPhotos = await FetchiOSPhotos(); MediaAssets.AddRange(freshPhotos); }); }); PHPhotoLibrary.SharedPhotoLibrary.RegisterChangeObserver(observer);
通用绑定与模型
XAML中的CollectionView
<CollectionView ItemsSource="{Binding MediaAssets}"> <CollectionView.ItemTemplate> <DataTemplate> <Image Source="{Binding ImageSource}" WidthRequest="100" HeightRequest="100" Aspect="AspectFill" Margin="4"/> </DataTemplate> </CollectionView.ItemTemplate> <CollectionView.ItemsLayout> <GridItemsLayout Span="3" Orientation="Vertical"/> </CollectionView.ItemsLayout> </CollectionView>
MediaAsset模型类
public class MediaAsset { public string Path { get; set; } public string Name { get; set; } public string LocalIdentifier { get; set; } public ImageSource ImageSource { get; set; } }
内容的提问来源于stack exchange,提问作者Alexander Petrov
相关产品推荐
相关产品推荐

