Wybieranie zdjęcia z biblioteki obrazów
W tym artykule opisano tworzenie aplikacji, która umożliwia użytkownikowi wybranie zdjęcia z biblioteki obrazów telefonu. Ponieważ Xamarin.Forms nie obejmuje tej funkcji, należy użyć DependencyService
jej do uzyskiwania dostępu do natywnych interfejsów API na każdej platformie.
Tworzenie interfejsu
Najpierw utwórz interfejs w kodzie udostępnionym, który wyraża żądaną funkcjonalność. W przypadku aplikacji do wybierania zdjęć wymagana jest tylko jedna metoda. Jest to zdefiniowane w interfejsie IPhotoPickerService
w bibliotece .NET Standard przykładowego kodu:
namespace DependencyServiceDemos
{
public interface IPhotoPickerService
{
Task<Stream> GetImageStreamAsync();
}
}
Metoda GetImageStreamAsync
jest definiowana jako asynchroniczna, ponieważ metoda musi zwracać szybko, ale nie może zwrócić Stream
obiektu dla wybranego zdjęcia, dopóki użytkownik nie przejdą do biblioteki obrazów i wybierze jeden.
Ten interfejs jest implementowany na wszystkich platformach przy użyciu kodu specyficznego dla platformy.
Implementacja systemu iOS
Implementacja interfejsu dla systemu IPhotoPickerService
iOS używa UIImagePickerController
elementu zgodnie z opisem w przepisie Wybierz zdjęcie z galerii i przykładowym kodzie.
Implementacja systemu iOS jest zawarta w PhotoPickerService
klasie w projekcie systemu iOS przykładowego kodu. Aby ta klasa był widoczna dla DependencyService
menedżera, należy zidentyfikować klasę z atrybutem [assembly
] typu Dependency
, a klasa musi być publiczna i jawnie zaimplementować IPhotoPickerService
interfejs:
[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;
}
...
}
}
Metoda GetImageStreamAsync
tworzy element UIImagePickerController
i inicjuje go w celu wybrania obrazów z biblioteki zdjęć. Wymagane są dwa programy obsługi zdarzeń: jeden dla sytuacji, gdy użytkownik wybierze zdjęcie, a drugi, gdy użytkownik anuluje wyświetlanie biblioteki zdjęć. Następnie PresentViewController
metoda wyświetla użytkownikowi bibliotekę zdjęć.
W tym momencie GetImageStreamAsync
metoda musi zwrócić Task<Stream>
obiekt do kodu, który go wywołuje. To zadanie jest wykonywane tylko wtedy, gdy użytkownik zakończył interakcję z biblioteką zdjęć, a jeden z programów obsługi zdarzeń jest wywoływany. W takich sytuacjach klasa jest niezbędna TaskCompletionSource
. Klasa udostępnia Task
obiekt odpowiedniego typu ogólnego, który ma zostać zwrócony z GetImageStreamAsync
metody, a klasa może być później zasygnalizowana po zakończeniu zadania.
Procedura FinishedPickingMedia
obsługi zdarzeń jest wywoływana, gdy użytkownik wybrał obraz. Jednak program obsługi udostępnia UIImage
obiekt i Task
musi zwrócić obiekt .NET Stream
. Odbywa się to w dwóch krokach: UIImage
obiekt jest najpierw konwertowany na plik PNG lub JPEG w pamięci przechowywany w NSData
obiekcie, a następnie NSData
obiekt jest konwertowany na obiekt .NET Stream
. Wywołanie SetResult
metody TaskCompletionSource
obiektu kończy zadanie przez podanie Stream
obiektu:
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;
}
}
}
Aplikacja systemu iOS wymaga od użytkownika uprawnień dostępu do biblioteki zdjęć telefonu. Dodaj następujący kod do dict
sekcji pliku Info.plist:
<key>NSPhotoLibraryUsageDescription</key>
<string>Picture Picker uses photo library</string>
Implementacja systemu Android
Implementacja systemu Android używa techniki opisanej w przepisie Select an Image (Wybieranie obrazu) i przykładowym kodzie. Jednak metoda wywoływana, gdy użytkownik wybrał obraz z biblioteki obrazów, jest OnActivityResult
zastąpieniem klasy pochodzącej z Activity
klasy . Z tego powodu klasa normalna MainActivity
w projekcie systemu Android została uzupełniona polem, właściwością i przesłonięć metodę 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);
}
}
}
}
Przesłonięcia OnActivityResult
wskazują wybrany plik obrazu z obiektem systemu Android Uri
, ale można go przekonwertować na obiekt platformy .NET Stream
, wywołując OpenInputStream
metodę ContentResolver
obiektu uzyskanego z właściwości działania ContentResolver
.
Podobnie jak implementacja systemu iOS, implementacja systemu Android używa elementu TaskCompletionSource
do sygnalizatora po zakończeniu zadania. Ten TaskCompletionSource
obiekt jest definiowany jako właściwość publiczna MainActivity
w klasie. Umożliwia to przywołowanie właściwości w PhotoPickerService
klasie w projekcie systemu Android. Jest to klasa z GetImageStreamAsync
metodą :
[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;
}
}
}
Ta metoda uzyskuje dostęp do MainActivity
klasy dla kilku celów: dla Instance
właściwości, dla PickImageId
pola, dla TaskCompletionSource
właściwości i wywołania metody StartActivityForResult
. Ta metoda jest definiowana przez klasę FormsAppCompatActivity
, która jest klasą bazową klasy MainActivity
.
Implementacja platformy UWP
W przeciwieństwie do implementacji systemów iOS i Android implementacja selektora zdjęć dla platforma uniwersalna systemu Windows nie wymaga TaskCompletionSource
klasy . Klasa PhotoPickerService
używa FileOpenPicker
klasy , aby uzyskać dostęp do biblioteki zdjęć. PickSingleFileAsync
Ponieważ metoda FileOpenPicker
jest sama asynchroniczna, GetImageStreamAsync
metoda może po prostu używać await
z tą metodą (i innymi metodami asynchronicznymi) i zwracać Stream
obiekt:
[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();
}
}
}
Implementowanie w kodzie udostępnionym
Teraz, gdy interfejs został zaimplementowany dla każdej platformy, udostępniony kod w bibliotece .NET Standard może z niego korzystać.
Interfejs użytkownika zawiera Button
element, który można kliknąć, aby wybrać zdjęcie:
<Button Text="Pick Photo"
Clicked="OnPickPhotoButtonClicked" />
Procedura Clicked
obsługi zdarzeń wywołuje klasę DependencyService
GetImageStreamAsync
. Spowoduje to wywołanie projektu platformy. Jeśli metoda zwraca Stream
obiekt, program obsługi ustawia Source
właściwość image
obiektu na Stream
dane:
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;
}