Xamarin.Forms: Get native image from ImageSource

Sometimes we need to implement images in our custom renderers and as a first solution most of developers take the decision to use a string property but for me is not the better and standard way to manage image resources on Xamarin.Forms, in this blog we will take a look to a helper class that help us to manage images resource on your Xamairn.Forms renderers using a IImageSourceHandler.

There are three IImageSourceHandler helper classes:
1. ImageLoaderSourceHandler: Handle the Uri resources
2. FileImageSourceHandler: Handle the local/embed resources
3. StreamImagesourceHandler: Handle the stream image resources

The first that we need is to check what kind of source have your ImageSource property using the next code

public static IImageSourceHandler GetHandler (this ImageSource source)
{
//Image source handler to return
IImageSourceHandler returnValue = null;
//check the specific source type and return the correct image source handler
if (source is UriImageSource)
{
returnValue = new ImageLoaderSourceHandler();
}
else if (source is FileImageSource)
{
returnValue = new FileImageSourceHandler();
}
else if (source is StreamImageSource)
{
returnValue = new StreamImagesourceHandler();
}
return returnValue;
}
after this, with the returned IImageSourceHandler you can call the LoadImageAsync and passing the source that you want to be native and this method will be return for you an UIImage for iOS or a Bitmap for Android

var imageHandler = base.Element.Source.GetHandler ();
if (imageHandler != null) {
var nativeImage = await imageHandler.LoadImageAsync (base.Element.Source);
}
view raw Example.cs hosted with ❤ by GitHub

Real World Example: "HeartBeatView" Control


Now we will create an example control using the previous code, we have 4 key file
1. HeartBeatView.cs: Contains the main forms control class.
2. ImageSourceExtensions.cs: Contains the "GetNativeHandler" ImageSource extension method.
3. Android: HeartBeatViewRenderer.cs: Contains the Android custom renderer code for HeartBeatView control.
4. iOS: HeartBeatViewRenderer.cs: Contains the iOS custom renderer code for HeartBeatView control.

using System;
using Android.Widget;
using NativeImages.Controls;
using Xamarin.Forms.Platform.Android;
using Xamarin.Forms;
using NativeImages.Droid.Renderers;
using System.Threading.Tasks;
using NativeImages.Extentions;
using Android.Views.Animations;
[assembly: ExportRenderer (typeof(HeartBeatView), typeof(HeartBeatViewRenderer))]
namespace NativeImages.Droid.Renderers
{
public class HeartBeatViewRenderer:ViewRenderer<HeartBeatView, ImageView>
{
public HeartBeatViewRenderer ()
{
}
protected async override void OnElementChanged (ElementChangedEventArgs<HeartBeatView> e)
{
base.OnElementChanged (e);
if (e.OldElement == null) {
SetNativeControl (new ImageView(this.Context));
}
if (e.NewElement != null) {
await BuildHeartBeat ();
UpdateBeatState ();
}
}
protected async override void OnElementPropertyChanged (object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged (sender, e);
if (e.PropertyName == HeartBeatView.SourceProperty.PropertyName) {
await BuildHeartBeat ();
}else if (e.PropertyName == HeartBeatView.IsBeatingProperty.PropertyName) {
UpdateBeatState ();
}
}
async Task BuildHeartBeat()
{
var imageHandler = base.Element.Source.GetHandler ();
if (imageHandler != null) {
var nativeImage = await imageHandler.LoadImageAsync (base.Element.Source, this.Context);
if (nativeImage != null) {
Device.BeginInvokeOnMainThread (() => {
this.Control.SetImageBitmap (nativeImage);
});
}
}
}
void UpdateBeatState()
{
if (base.Element.IsBeating) {
Action act = new Action (() => {
ScaleAnimation Ani = new ScaleAnimation (1, 1.2f, 1, 1.2f, base.Control.Width / 2, base.Control.Height / 2);
Ani.RepeatCount = ScaleAnimation.Infinite;
Ani.Duration = 750;
Ani.Interpolator = new HeartBeatInterpolator ();
base.Control.StartAnimation (Ani);
});
base.Control.Post (act);
} else {
base.Control.ClearAnimation ();
}
}
class HeartBeatInterpolator:Java.Lang.Object, IInterpolator
{
public float GetInterpolation (float input)
{
float x = input < 1/3f? 2 * input : (1 + input) / 2;
return (float) Math.Sin(x * Math.PI);
}
}
}
}
using System;
using Xamarin.Forms;
namespace NativeImages.Controls
{
public class HeartBeatView:View
{
public HeartBeatView ()
{
}
public static readonly BindableProperty SourceProperty =
BindableProperty.Create<HeartBeatView, ImageSource> (
p => p.Source, null);
public ImageSource Source
{
get {
return (ImageSource)GetValue(SourceProperty);
}
set {
SetValue(SourceProperty, value);
}
}
public static readonly BindableProperty IsBeatingProperty =
BindableProperty.Create<HeartBeatView, bool> (
p => p.IsBeating, false);
public bool IsBeating
{
get {
return (bool)GetValue(IsBeatingProperty);
}
set {
SetValue(IsBeatingProperty, value);
}
}
}
}
using System;
using Xamarin.Forms;
#if__IOS__
using Xamarin.Forms.Platform.iOS;
#elif __ANDROID__
using Xamarin.Forms.Platform.Android;
#endif
namespace NativeImages.Extentions
{
public static class ImageSourceExtensions
{
public static IImageSourceHandler GetHandler (this ImageSource source)
{
//Image source handler to return
IImageSourceHandler returnValue = null;
//check the specific source type and return the correct image source handler
if (source is UriImageSource)
{
returnValue = new ImageLoaderSourceHandler();
}
else if (source is FileImageSource)
{
returnValue = new FileImageSourceHandler();
}
else if (source is StreamImageSource)
{
returnValue = new StreamImagesourceHandler();
}
return returnValue;
}
}
}
using System;
using Xamarin.Forms;
using NativeImages.Controls;
using NativeImages.iOS.Renderers;
using UIKit;
using Xamarin.Forms.Platform.iOS;
using System.Threading.Tasks;
using NativeImages.Extentions;
using CoreGraphics;
using CoreAnimation;
using Foundation;
[assembly: ExportRenderer (typeof(HeartBeatView), typeof(HeartBeatViewRenderer))]
namespace NativeImages.iOS.Renderers
{
public class HeartBeatViewRenderer:ViewRenderer<HeartBeatView, UIImageView>
{
public HeartBeatViewRenderer ()
{
}
protected async override void OnElementChanged (ElementChangedEventArgs<HeartBeatView> e)
{
base.OnElementChanged (e);
if (e.OldElement == null) {
SetNativeControl (new UIImageView (this.Frame));
base.Control.ContentMode = UIViewContentMode.ScaleAspectFit;
}
if (e.NewElement != null) {
await BuildHeartBeat ();
UpdateBeatState ();
}
}
protected async override void OnElementPropertyChanged (object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged (sender, e);
if (e.PropertyName == HeartBeatView.SourceProperty.PropertyName) {
await BuildHeartBeat ();
}else if (e.PropertyName == HeartBeatView.IsBeatingProperty.PropertyName) {
UpdateBeatState ();
}
}
async Task BuildHeartBeat()
{
var imageHandler = base.Element.Source.GetHandler ();
if (imageHandler != null) {
var nativeImage = await imageHandler.LoadImageAsync (base.Element.Source);
if (nativeImage != null) {
Device.BeginInvokeOnMainThread (() => {
this.Control.Image = nativeImage;
});
}
}
}
void UpdateBeatState()
{
var hasAnimationRegistered = base.Control.Layer.AnimationForKey ("HeartAnimation") != null;
if (base.Element.IsBeating) {
if (!hasAnimationRegistered) {
CABasicAnimation animation = CABasicAnimation.FromKeyPath ("transform.scale");
animation.AutoReverses = true;
animation.From = new NSNumber (1);
animation.To = new NSNumber (1.2f);
animation.Duration = 0.5;
animation.RepeatCount = int.MaxValue;
animation.TimingFunction = CAMediaTimingFunction.FromName (CAMediaTimingFunction.EaseIn);
base.Control.Layer.AddAnimation (animation, "HeartAnimation");
}
} else {
if(hasAnimationRegistered){
base.Control.Layer.RemoveAnimation("HeartAnimation");
}
}
}
}
}
var heartBeatView = new HeartBeatView {
Source = "Heart",
VerticalOptions = LayoutOptions.FillAndExpand,
HorizontalOptions = LayoutOptions.FillAndExpand
};
heartBeatView.IsBeating = true;
await Task.Delay(5000);
heartBeatView.IsBeating = false;
view raw Usage.cs hosted with ❤ by GitHub
After this you will be able to implement the heartbeat control using your own imagesource and start/stop the "heartbeat" animation using the "IsBeating" property.

Screenshots of the example control





Comentarios

Entradas populares