【问题标题】:Select multiple images and show them in a form选择多个图像并以表格形式显示它们
【发布时间】:2019-05-09 04:34:06
【问题描述】:

我正在使用 Xamarin.Forms 创建一个 Android + iOS 应用程序。我还是新手,但互联网并没有帮助我找到问题的答案。

我正在尝试创建一个表单,其中一个部分包含在图像选择器上。 这个想法是:

  • 提供打开图库的按钮
  • 用户可以根据需要选择任意数量的图像
  • 图像显示在表格上(表格为通用概念,不是必需的对象)。该表应有 3 列,无论需要什么行。

为了实现这一点,我目前有两个问题:

  1. 我正在使用 CrossMedia 插件访问图库,但这仅允许我检索一张图像。我还没有找到支持多选的等待。我在互联网上看到的回复要么是特定于平台的,要么不再受支持。
  2. 如果我设法获取所选图像的列表,如何以表格形式显示它?

这是我目前拥有的:

XAML

在 TableSection 中

<ViewCell>
  <Grid x:Name="imageGrid" Margin="15" >
    <Grid.RowDefinitions>
      <RowDefinition Height="Auto" />
    </Grid.RowDefinitions>
    <Label Text="Photos" Grid.Column="0" />
    <Button Text="Add" Clicked="Select_Photos" Grid.Column="1" HorizontalOptions="End" VerticalOptions="Start" />

  </Grid>
</ViewCell>
<ImageCell x:Name="img_selected" Text="Bla" ImageSource="http://xamarin.com/images/index/ide-xamarin-studio.png" />

CS

async void Select_Photos(object sender, System.EventArgs e)
{
  try
  {
    var status = await CrossPermissions.Current.CheckPermissionStatusAsync(Permission.Storage);
    if (status != PermissionStatus.Granted)
    {
      if (await CrossPermissions.Current.ShouldShowRequestPermissionRationaleAsync(Permission.Storage))
      {
        await DisplayAlert("Need Storage", "Gunna need that Storage", "OK");
      }

      var results = await CrossPermissions.Current.RequestPermissionsAsync(Permission.Storage);
      status = results[Permission.Storage];
    }

    if (status == PermissionStatus.Granted)
    {
      await CrossMedia.Current.Initialize();
      if (!CrossMedia.Current.IsPickPhotoSupported)
      {
        await DisplayAlert("no upload", "picking a photo is not supported", "ok");
        return;
      }

      var file = await CrossMedia.Current.PickPhotoAsync();
      if (file == null)
        return;

      img_selected.ImageSource = ImageSource.FromStream(file.GetStream);
    }
    else if (status != PermissionStatus.Unknown)
    {
      await DisplayAlert("Storage Permission Denied", "Can not continue, try again.", "OK");
    }
  }
  catch
  {
    //...
  }
}

这里发生的是我能够选择一张图像并显示它。 现在我需要想办法迈出下一步。

【问题讨论】:

    标签: image xamarin xamarin.forms gallery multi-select


    【解决方案1】:

    我目前在 Xamarin 表单中执行此操作,并针对 iOS/Android 执行特定于平台的图像服务。在 iOS 上,我使用了一个名为 ELCImagePicker 的库来提供帮助。显然它不再受支持,但对我来说仍然可以正常工作。只是直接从我的 iOS 项目中引用了 dll,但我找不到我在哪里下载了我正在使用的 dll,但我在下面包含了一个指向源的链接。

    我的应用程序使用dependency injection 来访问 IImageService 实现。所以我的 iOS 和 Android 项目在启动时向 IOC 注册了他们的 ImageService 类。 像这样:

    SimpleIoc.Default.Register<IImageService>(() => new ImageService());
    

    您也可以通过在启动期间直接传递引用或其他方式让它们直接将其实现提供给您的 xamarin 表单代码。

    这是我定义的接口,在我的 Xamarin Forms 项目中:

    public interface IImageService
    {
        Task<List<MediaFile>> PickImages(int maxImagesCount = 1);
        Task<MediaFile> TakePhoto();
    
        Stream GenerateThumbnail(MediaFile file);
    
        bool IsPickImagesSupported { get; }
        bool IsTakePhotoSupported { get; }
    }
    

    Shared 项目中,我定义了图像服务中不特定于平台的部分:

    public partial class ImageService : IImageService
    {
        public bool IsPickImagesSupported => CrossMedia.Current.IsPickPhotoSupported;
    
        public bool IsTakePhotoSupported => CrossMedia.Current.IsTakePhotoSupported;
    
        public Task<MediaFile> TakePhoto() => CrossMedia.Current.TakePhotoAsync(
            new StoreCameraMediaOptions
            {
                 CompressionQuality = 92
            });
    }
    

    iOS 项目内部的 iOS 实现使用ECLImagePicker 支持多图拾取:

    public partial class ImageService : IImageService
    {
        public ImageService()
        {
            ELCImagePickerViewController.OverlayImage = UIImage.FromFile("BlueCheck.png");
        }
    
        public Stream GenerateThumbnail(MediaFile file)
        {
            try
            {
                using (var image = UIImage.FromFile(file.Path))
                {
                    if (image is null)
                        return null;
                    var thumb = image.ResizeImageWithAspectRatio(150, 150);
    
                    return thumb.AsJPEG(.92f).AsStream();
                }
            }
            catch (Exception ex)
            {
                App.LogError(ex);
                return null;
            }
        }
    
        public async Task<List<MediaFile>> PickImages(int maxImagesCount = 1)
        {
            var images = new List<MediaFile>();
            try
            {
                if (maxImagesCount == 1)
                {
                    var image = await CrossMedia.Current.PickPhotoAsync(new PickMediaOptions { CompressionQuality = 92 });
                    if (image != null) images.Add(image);
                }
                else
                {
                    var picker = ELCImagePickerViewController.Create(maxImagesCount);
    
                    try
                    {
                        var topController = UIApplication.SharedApplication.KeyWindow.RootViewController;
                        while (topController.PresentedViewController != null)
                        {
                            topController = topController.PresentedViewController;
                        }
                        topController.PresentViewController(picker, true, null);
    
                        var items = await picker.Completion;
    
                        if (items != null && items.Any())
                        {
                            foreach (var item in items)
                            {
                                images.Add(new MediaFile(
                                    path: GetPathToImage(item.Image, item.Name),
                                    streamGetter: () => item.Image.AsJPEG(0.92f).AsStream()
                                ));
                            }
                        }
                    }
                    catch (OperationCanceledException) { }
                    finally
                    {
                        picker.BeginInvokeOnMainThread(() =>
                        {
                            //dismiss the picker
                            picker.DismissViewController(true, null);
                        });
                    }
                }
            }
            catch (Exception ex)
            {
                App.LogError(ex);
            }
    
            return images;
        }
    
        public static string GetPathToImage(UIImage image, string name)
        {
            var tempDirectory = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
            string jpgFilename = Path.Combine(tempDirectory, name); 
    
            var imgData = image.AsJPEG(.92f);
            NSError err = null;
            if (imgData.Save(jpgFilename, false, out err))
            {
                return jpgFilename;
            }
            else
            {
                return null;
            }
        }
    
    
    }
    

    还有安卓版本:

    public partial class ImageService : IImageService
    {
        public const int IMAGES_SELECTED = 200;
    
        readonly TimeSpan TIMEOUT = TimeSpan.FromSeconds(300);
        readonly Activity sourceActivity;
    
        List<MediaFile> pickedImages = null;
        bool waitingForimages = false;
    
        public ImageService(Activity sourceActivity)
        {
            this.sourceActivity = sourceActivity;
        }
    
        public System.IO.Stream GenerateThumbnail(MediaFile file)
        {
            try
            {
                var imagePath = file.Path;
    
                var originalImage = BitmapFactory.DecodeFile(imagePath);
                double desiredWidth = 150;
                double desiredHeight = originalImage.Height * (desiredWidth / originalImage.Width);
                var rotation = GetRotation(imagePath);
                Bitmap finalImage = originalImage;
    
                if (rotation != 0)
                {
                    var matrix = new Matrix();
                    matrix.PostRotate(rotation);
                    finalImage = Bitmap.CreateBitmap(originalImage, 0, 0, originalImage.Width, originalImage.Height, matrix, true);
                    originalImage.Recycle();
                    originalImage.Dispose();
                }
    
                finalImage = Bitmap.CreateScaledBitmap(finalImage, Convert.ToInt32(desiredWidth), Convert.ToInt32(desiredHeight), true);
                var ms = new MemoryStream();
                finalImage.Compress(Bitmap.CompressFormat.Jpeg, 92, ms);
                ms.Seek(0, SeekOrigin.Begin);
    
                finalImage.Recycle();
                finalImage.Dispose();
                // Dispose of the Java side bitmap.
                GC.Collect();
    
                return ms;
            }
            catch (Exception) 
            { return null;}
        }
    
    
    
        public async Task<List<MediaFile>> PickImages(int maxImagesCount = 1)
        {
            if (maxImagesCount > 1)
            {
                Toast.MakeText(sourceActivity.BaseContext, $"Select a maximum of {maxImagesCount} images", ToastLength.Long).Show();
    
                var imageIntent = new Intent(Intent.ActionPick);
                imageIntent.SetType("image/*");
                imageIntent.PutExtra(Intent.ExtraAllowMultiple, true);
                imageIntent.SetAction(Intent.ActionGetContent);
    
                var startTime = DateTime.Now;
                pickedImages = null;
                sourceActivity.StartActivityForResult(Intent.CreateChooser(imageIntent, "Select photos"), IMAGES_SELECTED);
    
                Debug.WriteLine("Waiting for images...");
    
                waitingForimages = true;
                while (waitingForimages && (DateTime.Now - startTime) < TIMEOUT)
                {await Task.Delay(250);}
    
                Debug.WriteLine("Wait for images finished.");
    
                waitingForimages = false;
                return pickedImages;
            }
            else
            {
                var images = new List<MediaFile>();
    
                var image = await CrossMedia.Current.PickPhotoAsync(new PickMediaOptions { CompressionQuality = 92 });
                if (image != null) images.Add(image);
    
                return images;
            }
        }
    
        public async Task OnPickImagesResult(int requestCode, Result resultCode, Intent data, ContentResolver contentResolver)
        {
            if (requestCode != IMAGES_SELECTED) throw new ArgumentException("invalid request code for images service:" + requestCode); 
            if (resultCode != Result.Ok)
            {//Canceled or failed
                pickedImages = null;
                waitingForimages = false;
                return;
            }
    
            try
            {
                if (data != null)
                {
                    var images = new List<MediaFile>();
                    ClipData clipData = data.ClipData;
                    if (clipData != null)
                    {
                        for (int i = 0; i < clipData.ItemCount; i++)
                        {
                            ClipData.Item item = clipData.GetItemAt(i);
                            Android.Net.Uri uri = item.Uri;
                            var path = await GetFileForUriAsync(sourceActivity, uri);
                            if (!string.IsNullOrEmpty(path))
                            {
                                var image = await ImageToMediaFile(path);
                                images.Add(image);
                            }
                            else throw new Exception($"Image import {i+1} of {clipData.ItemCount} failed", new Exception(uri.ToString()));
                        }
                    }
                    else
                    {
                        Android.Net.Uri uri = data.Data;
                        var path = await GetFileForUriAsync(sourceActivity, uri);
                        if (!string.IsNullOrEmpty(path))
                        {
                            var image = await ImageToMediaFile(path);
                            images.Add(image);
                        }
                        else throw new Exception("Image import failed");
                    }
    
                    pickedImages = images;
                }
            }
            catch (Exception ex)
            {
                App.LogError(ex);
                Toast.MakeText(sourceActivity, ex.Message, ToastLength.Short).Show();
            }
    
            waitingForimages = false;
        }
    
        static async Task<MediaFile> ImageToMediaFile(string imagePath)
        {
            MediaFile imageFile = null;
    
            var originalImage = BitmapFactory.DecodeFile(imagePath);
            var rotation = GetRotation(imagePath);
            Bitmap finalImage = originalImage;
    
            if (rotation != 0)
            {
                var matrix = new Matrix();
                matrix.PostRotate(rotation);
                finalImage = Bitmap.CreateBitmap(originalImage, 0, 0, originalImage.Width, originalImage.Height, matrix, true);
                originalImage.Recycle();
                originalImage.Dispose();
            }
    
            var ms = new MemoryStream();
            await finalImage.CompressAsync(Bitmap.CompressFormat.Jpeg, 92, ms);
            imageFile = new MediaFile(imagePath, () =>
            {
                ms.Seek(0, SeekOrigin.Begin);
                return ms;
            });
    
            finalImage.Recycle();
            finalImage.Dispose();
            // Dispose of the Java side bitmap.
            GC.Collect();
    
            return imageFile;
        }
    
        static int GetRotation(string filePath)
        {
            using (var ei = new ExifInterface(filePath))
            {
                var orientation = (Android.Media.Orientation)ei.GetAttributeInt(ExifInterface.TagOrientation, (int)Android.Media.Orientation.Normal);
    
                switch (orientation)
                {
                    case Android.Media.Orientation.Rotate90:
                        return 90;
                    case Android.Media.Orientation.Rotate180:
                        return 180;
                    case Android.Media.Orientation.Rotate270:
                        return 270;
                    default:
                        return 0;
                }
            }
        }
    
    
        /// <summary>
        /// Gets the file for URI, including making a local temp copy. 
        /// Imported from media picker plugin source. 
        /// https://github.com/jamesmontemagno/MediaPlugin/blob/master/src/Media.Plugin.Android/MediaPickerActivity.cs
        /// </summary>
        internal static Task<string> GetFileForUriAsync(Context context, Android.Net.Uri uri, bool isPhoto = true, bool saveToAlbum = false)
        {
            var tcs = new TaskCompletionSource<string>();
    
            if (uri.Scheme == "file")
                tcs.SetResult(new System.Uri(uri.ToString()).LocalPath);
            else if (uri.Scheme == "content")
            {
                Task.Factory.StartNew(() =>
                {
                    ICursor cursor = null;
                    try
                    {
                        string[] proj = null;
                        if ((int)Android.OS.Build.VERSION.SdkInt >= 22)
                            proj = new[] { MediaStore.MediaColumns.Data };
    
                        cursor = context.ContentResolver.Query(uri, proj, null, null, null);
                        if (cursor is null || !cursor.MoveToNext())
                            tcs.SetResult(null);
                        else
                        {
                            var column = cursor.GetColumnIndex(MediaStore.MediaColumns.Data);
                            string contentPath = null;
    
                            if (column != -1)
                                contentPath = cursor.GetString(column);
    
    
    
                            // If they don't follow the "rules", try to copy the file locally
                            if (contentPath is null || !contentPath.StartsWith("file", StringComparison.InvariantCultureIgnoreCase))
                            {
                                string fileName = null;
                                try
                                {
                                    fileName = System.IO.Path.GetFileName(contentPath);
                                }
                                catch (Exception ex)
                                {
                                    System.Diagnostics.Debug.WriteLine("Unable to get file path name, using new unique " + ex);
                                }
    
    
                                var outputPath = GetOutputMediaFile(context, "temp", fileName, isPhoto, false);
    
                                try
                                {
                                    using (var input = context.ContentResolver.OpenInputStream(uri))
                                    using (var output = File.Create(outputPath.Path))
                                        input.CopyTo(output);
    
                                    contentPath = outputPath.Path;
                                }
                                catch (Java.IO.FileNotFoundException fnfEx)
                                {
                                    // If there's no data associated with the uri, we don't know
                                    // how to open this. contentPath will be null which will trigger
                                    // MediaFileNotFoundException.
                                    System.Diagnostics.Debug.WriteLine("Unable to save picked file from disk " + fnfEx);
                                }
                            }
    
                            tcs.SetResult(contentPath);
                        }
                    }
                    finally
                    {
                        if (cursor != null)
                        {
                            cursor.Close();
                            cursor.Dispose();
                        }
                    }
                }, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default);
            }
            else
                tcs.SetResult(null);
    
            return tcs.Task;
        }
    
        public static Uri GetOutputMediaFile(Context context, string subdir, string name, bool isPhoto, bool saveToAlbum)
        {
            subdir = subdir ?? string.Empty;
    
            if (string.IsNullOrWhiteSpace(name))
            {
                var timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss", CultureInfo.InvariantCulture);
                if (isPhoto)
                    name = "IMG_" + timestamp + ".jpg";
                else
                    name = "VID_" + timestamp + ".mp4";
            }
    
            var mediaType = (isPhoto) ? Environment.DirectoryPictures : Environment.DirectoryMovies;
            var directory = saveToAlbum ? Environment.GetExternalStoragePublicDirectory(mediaType) : context.GetExternalFilesDir(mediaType);
            using (var mediaStorageDir = new Java.IO.File(directory, subdir))
            {
                if (!mediaStorageDir.Exists())
                {
                    if (!mediaStorageDir.Mkdirs())
                        throw new IOException("Couldn't create directory, have you added the WRITE_EXTERNAL_STORAGE permission?");
    
                    if (!saveToAlbum)
                    {
                        // Ensure this media doesn't show up in gallery apps
                        using (var nomedia = new Java.IO.File(mediaStorageDir, ".nomedia"))
                            nomedia.CreateNewFile();
                    }
                }
    
                return Android.Net.Uri.FromFile(new Java.IO.File(GetUniquePath(mediaStorageDir.Path, name, isPhoto)));
            }
        }
    
        private static string GetUniquePath(string folder, string name, bool isPhoto)
        {
            var ext = Path.GetExtension(name);
            if (ext == string.Empty)
                ext = ((isPhoto) ? ".jpg" : ".mp4");
    
            name = Path.GetFileNameWithoutExtension(name);
    
            var nname = name + ext;
            var i = 1;
            while (File.Exists(Path.Combine(folder, nname)))
                nname = name + "_" + (i++) + ext;
    
            return Path.Combine(folder, nname);
        }
    }
    

    【讨论】:

    • 感谢本的帮助。不幸的是,正如我所说,我还是 Xamarin 的新手,所以我对如何实现这一点有点迷茫: - 什么是 Preserve 属性? - IImageService 来自哪里? - 如何从 xaml 调用它? - 编译器如何知道调用哪一段代码?
    • 嗨 Nahoot,我已经更新了我的答案,将接口和完整的实现。如果您不确定如何设置共享项目,我提供了一个应该有帮助的链接。我删除了 preserve 属性以避免混淆 - 它用于确保链接器不会删除代码,这是一个单独的主题,您需要了解何时/如果您准备将应用程序发布到商店。您不会从 xaml 调用它,而是从代码隐藏或视图模型中的命令调用图像服务(如果使用 mvvm)。
    猜你喜欢
    • 1970-01-01
    • 2022-01-24
    • 2023-03-15
    • 2014-06-29
    • 2019-01-28
    • 2022-10-24
    • 1970-01-01
    • 1970-01-01
    • 2020-11-13
    相关资源
    最近更新 更多