image - 选择多个图像并以表格形式显示它们
问题描述
我正在使用 Xamarin.Forms 创建一个 Android + iOS 应用程序。我对此仍然很陌生,但互联网无助于为我遇到的问题找到答案。
我正在尝试创建一个表单,其中一个部分包含在图像选择器上。这个想法是:
- 提供一个打开画廊的按钮
- 用户选择他/她想要的尽可能多的图像
- 图像显示在一张桌子上(表格是一般概念,不是必需的对象)。该表应按所需的任何行包含 3 列。
为了实现这一点,我目前有两个问题:
- 我正在使用 CrossMedia 插件访问图库,但这仅允许我检索一张图像。我还没有找到支持多选的等待。我在互联网上看到的响应要么是特定于平台的,要么不再受支持。
- 如果我设法获取所选图像的列表,如何以表格形式显示它?
这是我目前拥有的:
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
{
//...
}
}
这里发生的是我能够选择一张图像并显示它。现在我需要找到一种方法来进行下一步。
解决方案
我目前正在 Xamarin 表单中执行此操作,并针对 iOS/Android 执行特定于平台的图像服务。在 iOS 上,我使用了一个名为 ELCImagePicker 的库来提供帮助。显然它不再受支持,但对我来说仍然可以正常工作。只是直接从我的 iOS 项目中引用 dll,但我找不到下载我正在使用的 dll 的位置,但我在下面包含了指向源的链接。
我的应用程序使用依赖注入来访问 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; }
}
在一个共享项目中,我定义了图像服务中不特定于平台的部分:
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);
}
}
推荐阅读
- javascript - 访问 Javascript 函数变量
- vbscript - 如何使用模式查找文件?
- c# - 查找与“某人”在同一职位上工作的员工
- javascript - 在 Electron 应用程序中流式传输视频的最有效方式?(获取用户媒体?)
- java - leiningen、lein、clojure 使用 lein_tool_deps 插件在代理服务器后面构建
- rust - 对向量的最后一个值进行可变引用
- python - 熊猫时间戳到posix转换的奇怪行为
- javascript - 将对象数组传递给 curl
- javascript - 如何将返回承诺的函数推送到接受参数的数组
- wxwidgets - 为什么编译openbabel时找不到wxWidgets?