Udostępnij za pośrednictwem


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 Activityklasy . 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 OnActivityResultwskazują 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ę DependencyServiceGetImageStreamAsync. 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;
}