从图片库中选取照片
本文介绍如何创建一种应用程序,使用户可通过该应用程序从手机的图片库中选取照片。 由于 Xamarin.Forms 不包含此功能,因此有必要使用 DependencyService
来访问每个平台上的本机 API。
创建界面
首先,在表示所需功能的共享代码中创建接口。 如果是照片选取应用程序,只需要一种方法。 该方法在示例代码的 .NET Standard 库中的 IPhotoPickerService
接口进行定义:
namespace DependencyServiceDemos
{
public interface IPhotoPickerService
{
Task<Stream> GetImageStreamAsync();
}
}
GetImageStreamAsync
方法定义为异步,因为该方法必须快速返回,但是不能为所选照片返回 Stream
对象,除非用户浏览了图片库并选择了一张照片。
该接口使用特定于平台的代码在所有平台上实现。
iOS 实现
IPhotoPickerService
接口的 iOS 实现使用 UIImagePickerController
,正如“从库中选择照片”方案和示例代码中描述那样。
iOS 实现包含在示例代码的 iOS 项目中的 PhotoPickerService
类中。 为了使该类对 DependencyService
管理器可见,此类必须被标识为 Dependency
类型的 [assembly
] 属性,且该类必须是公共的,并显式地实现 IPhotoPickerService
接口:
[assembly: Dependency (typeof (PhotoPickerService))]
namespace DependencyServiceDemos.iOS
{
public class PhotoPickerService : IPhotoPickerService
{
TaskCompletionSource<Stream> taskCompletionSource;
UIImagePickerController imagePicker;
public Task<Stream> GetImageStreamAsync()
{
// Create and define UIImagePickerController
imagePicker = new UIImagePickerController
{
SourceType = UIImagePickerControllerSourceType.PhotoLibrary,
MediaTypes = UIImagePickerController.AvailableMediaTypes(UIImagePickerControllerSourceType.PhotoLibrary)
};
// Set event handlers
imagePicker.FinishedPickingMedia += OnImagePickerFinishedPickingMedia;
imagePicker.Canceled += OnImagePickerCancelled;
// Present UIImagePickerController;
UIWindow window = UIApplication.SharedApplication.KeyWindow;
var viewController = window.RootViewController;
viewController.PresentViewController(imagePicker, true, null);
// Return Task object
taskCompletionSource = new TaskCompletionSource<Stream>();
return taskCompletionSource.Task;
}
...
}
}
GetImageStreamAsync
方法创建 UIImagePickerController
并初始化它,以从照片库中选择图像。 需要两个事件处理程序:一个用于用户选择照片,另一个用于用户取消照片库的显示。 然后 PresentViewController
方法向用户显示照片图库。
此时,GetImageStreamAsync
方法必须将 Task<Stream>
对象返回给调用它的代码。 只有用户已完成与照片库的交互并调用其中一个事件处理程序时,该任务才会完成。 对于这种情况,TaskCompletionSource
类是必不可少的。 该类提供要从 GetImageStreamAsync
方法返回的适当泛型类型的 Task
对象,稍后完成任务时该类将收到通知。
用户已选择图片时,将调用 FinishedPickingMedia
事件处理程序。 但是,处理程序提供 UIImage
对象,而 Task
必须返回 .NET Stream
对象。 这需要执行两个步骤来完成:首先将 UIImage
对象转换为存储在 NSData
对象中的内存中 PNG 或 JPEG 文件,然后将 NSData
对象转换为 .NET Stream
对象。 通过调用 TaskCompletionSource
对象的 SetResult
方法,以提供 Stream
对象的方式完成任务:
namespace DependencyServiceDemos.iOS
{
public class PhotoPickerService : IPhotoPickerService
{
TaskCompletionSource<Stream> taskCompletionSource;
UIImagePickerController imagePicker;
...
void OnImagePickerFinishedPickingMedia(object sender, UIImagePickerMediaPickedEventArgs args)
{
UIImage image = args.EditedImage ?? args.OriginalImage;
if (image != null)
{
// Convert UIImage to .NET Stream object
NSData data;
if (args.ReferenceUrl.PathExtension.Equals("PNG") || args.ReferenceUrl.PathExtension.Equals("png"))
{
data = image.AsPNG();
}
else
{
data = image.AsJPEG(1);
}
Stream stream = data.AsStream();
UnregisterEventHandlers();
// Set the Stream as the completion of the Task
taskCompletionSource.SetResult(stream);
}
else
{
UnregisterEventHandlers();
taskCompletionSource.SetResult(null);
}
imagePicker.DismissModalViewController(true);
}
void OnImagePickerCancelled(object sender, EventArgs args)
{
UnregisterEventHandlers();
taskCompletionSource.SetResult(null);
imagePicker.DismissModalViewController(true);
}
void UnregisterEventHandlers()
{
imagePicker.FinishedPickingMedia -= OnImagePickerFinishedPickingMedia;
imagePicker.Canceled -= OnImagePickerCancelled;
}
}
}
iOS 应用程序需要用户的许可才能访问手机的照片库。 将以下内容添加到 Info.plist 文件的 dict
部分:
<key>NSPhotoLibraryUsageDescription</key>
<string>Picture Picker uses photo library</string>
Android 实现
Android 实现使用选择图像方案和示例代码中描述的技术。 但是,如果用户已选择图片库中的图像,则调用的方法是派生自 Activity
的类中的 OnActivityResult
替代。 因此,Android 项目中的常规 MainActivity
类补充了一个字段、一个属性和 OnActivityResult
方法的一个替代:
public class MainActivity : FormsAppCompatActivity
{
internal static MainActivity Instance { get; private set; }
protected override void OnCreate(Bundle savedInstanceState)
{
// ...
Instance = this;
}
// ...
// Field, property, and method for Picture Picker
public static readonly int PickImageId = 1000;
public TaskCompletionSource<Stream> PickImageTaskCompletionSource { set; get; }
protected override void OnActivityResult(int requestCode, Result resultCode, Intent intent)
{
base.OnActivityResult(requestCode, resultCode, intent);
if (requestCode == PickImageId)
{
if ((resultCode == Result.Ok) && (intent != null))
{
Android.Net.Uri uri = intent.Data;
Stream stream = ContentResolver.OpenInputStream(uri);
// Set the Stream as the completion of the Task
PickImageTaskCompletionSource.SetResult(stream);
}
else
{
PickImageTaskCompletionSource.SetResult(null);
}
}
}
}
OnActivityResult
替代指示所选图片文件具有 Android Uri
对象,但是可以通过调用从活动的 ContentResolver
属性中获得的 ContentResolver
对象的 OpenInputStream
方法将其转换为 .NET Stream
对象。
与 iOS 实现一样,任务完成时,Android 实现将使用 TaskCompletionSource
进行通知。 该 TaskCompletionSource
对象在 MainActivity
类中定义为公共属性。 这样便可在 Android 项目中的 PhotoPickerService
类中引用该属性。 这是使用 GetImageStreamAsync
方法的类:
[assembly: Dependency(typeof(PhotoPickerService))]
namespace DependencyServiceDemos.Droid
{
public class PhotoPickerService : IPhotoPickerService
{
public Task<Stream> GetImageStreamAsync()
{
// Define the Intent for getting images
Intent intent = new Intent();
intent.SetType("image/*");
intent.SetAction(Intent.ActionGetContent);
// Start the picture-picker activity (resumes in MainActivity.cs)
MainActivity.Instance.StartActivityForResult(
Intent.CreateChooser(intent, "Select Picture"),
MainActivity.PickImageId);
// Save the TaskCompletionSource object as a MainActivity property
MainActivity.Instance.PickImageTaskCompletionSource = new TaskCompletionSource<Stream>();
// Return Task object
return MainActivity.Instance.PickImageTaskCompletionSource.Task;
}
}
}
该方法访问 MainActivity
类有几个目的:实现 Instance
属性、PickImageId
字段、TaskCompletionSource
属性,以及调用 StartActivityForResult
。 该方法是由 FormsAppCompatActivity
类定义的,此类是 MainActivity
的基类。
UWP 实现
与 iOS 和 Android 实现不同,用于通用 Windows 平台的照片选取器的实现不需要 TaskCompletionSource
类。 PhotoPickerService
类使用 FileOpenPicker
类访问照片库。 由于 FileOpenPicker
的 PickSingleFileAsync
方法本身是异步的,GetImageStreamAsync
方法可以简单地将 await
与该方法(以及其他异步方法)一起使用,并返回 Stream
对象:
[assembly: Dependency(typeof(PhotoPickerService))]
namespace DependencyServiceDemos.UWP
{
public class PhotoPickerService : IPhotoPickerService
{
public async Task<Stream> GetImageStreamAsync()
{
// Create and initialize the FileOpenPicker
FileOpenPicker openPicker = new FileOpenPicker
{
ViewMode = PickerViewMode.Thumbnail,
SuggestedStartLocation = PickerLocationId.PicturesLibrary,
};
openPicker.FileTypeFilter.Add(".jpg");
openPicker.FileTypeFilter.Add(".jpeg");
openPicker.FileTypeFilter.Add(".png");
// Get a file and return a Stream
StorageFile storageFile = await openPicker.PickSingleFileAsync();
if (storageFile == null)
{
return null;
}
IRandomAccessStreamWithContentType raStream = await storageFile.OpenReadAsync();
return raStream.AsStreamForRead();
}
}
}
在共享代码中实现
为每个平台实现接口后,现在 .NET Standard 库中的共享代码即可利用该接口。
UI 包含可通过单击选择照片的 Button
:
<Button Text="Pick Photo"
Clicked="OnPickPhotoButtonClicked" />
Clicked
事件处理程序使用 DependencyService
类调用 GetImageStreamAsync
。 这会导致调用平台项目。 如果该方法返回 Stream
对象,则处理程序会将 image
对象的 Source
属性设置为 Stream
数据:
async void OnPickPhotoButtonClicked(object sender, EventArgs e)
{
(sender as Button).IsEnabled = false;
Stream stream = await DependencyService.Get<IPhotoPickerService>().GetImageStreamAsync();
if (stream != null)
{
image.Source = ImageSource.FromStream(() => stream);
}
(sender as Button).IsEnabled = true;
}