В настоящее время я делаю это в формах Xamarin со специфичными для платформы реализациями сервиса изображений для iOS / Android.На iOS я использую библиотеку ELCImagePicker, чтобы помочь.По-видимому, он больше не поддерживается, но все еще работает нормально для меня.Просто напрямую ссылаюсь на dll из моего проекта iOS, но я не могу найти, где я скачал dll, который я использую, но я включил ссылку на источник ниже.
Мое приложение использует внедрение зависимости для доступа к реализации IImageService.Поэтому мои проекты iOS и Android регистрируют свои классы ImageService в IOC при запуске.Например:
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;
}
}
}
И версия для Android:
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);
}
}