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
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
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
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var imageHandler = base.Element.Source.GetHandler (); | |
if (imageHandler != null) { | |
var nativeImage = await imageHandler.LoadImageAsync (base.Element.Source); | |
} |
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"); | |
} | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var heartBeatView = new HeartBeatView { | |
Source = "Heart", | |
VerticalOptions = LayoutOptions.FillAndExpand, | |
HorizontalOptions = LayoutOptions.FillAndExpand | |
}; | |
heartBeatView.IsBeating = true; | |
await Task.Delay(5000); | |
heartBeatView.IsBeating = false; |
Screenshots of the example control
Full Example Code: https://github.com/AlejandroRuiz/HeartBeatXForms
Comentarios
Publicar un comentario