Skip to content

Instantly share code, notes, and snippets.

@toddk
Created January 13, 2016 15:33
Show Gist options
  • Save toddk/5d022aa8dec00f2fbc3b to your computer and use it in GitHub Desktop.
Save toddk/5d022aa8dec00f2fbc3b to your computer and use it in GitHub Desktop.
XLabs.Forms.Controls namespace, ImageButton renderer on Android
#region Usings
using Xamarin.Forms;
using XLabs.Forms.Controls;
#endregion
[assembly: ExportRenderer(typeof(ImageButton), typeof(ImageButtonRenderer))]
namespace XLabs.Forms.Controls
{
#region Usings
using System;
using System.ComponentModel;
using System.Threading.Tasks;
using Android.Graphics;
using Android.Graphics.Drawables;
using Android.Views;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
using XLabs.Enums;
using XLabs.Forms.Extensions;
using Color = Xamarin.Forms.Color;
using Object = Java.Lang.Object;
using View = Android.Views.View;
#endregion
/// <summary>
/// Draws a button on the Android platform with the image shown in the right
/// position with the right size.
/// </summary>
public partial class ImageButtonRenderer : ButtonRenderer
{
private static float Density = float.MinValue;
/// <summary>
/// Gets the underlying control typed as an <see cref="ImageButton" />.
/// </summary>
private ImageButton ImageButton { get { return (ImageButton)Element; } }
/// <summary>
/// Sets up the button including the image.
/// </summary>
/// <param name="e">The event arguments.</param>
protected override async void OnElementChanged(ElementChangedEventArgs<Button> e)
{
base.OnElementChanged(e);
Density = Resources.DisplayMetrics.Density;
var targetButton = Control;
if (targetButton != null) targetButton.SetOnTouchListener(TouchListener.Instance.Value);
if (Element != null && Element.Font != Font.Default && targetButton != null) targetButton.Typeface = Element.Font.ToExtendedTypeface(Context);
if (Element != null && ImageButton.Source != null) await SetImageSourceAsync(targetButton, ImageButton);
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing && Control != null) Control.Dispose();
}
/// <summary>
/// Sets the image source.
/// </summary>
/// <param name="targetButton">The target button.</param>
/// <param name="model">The model.</param>
/// <returns>A <see cref="Task" /> for the awaited operation.</returns>
private async Task SetImageSourceAsync(Android.Widget.Button targetButton, ImageButton model)
{
if (targetButton == null || targetButton.Handle == IntPtr.Zero || model == null) return;
// const int Padding = 10;
var source = model.IsEnabled ? model.Source : model.DisabledSource ?? model.Source;
using (var bitmap = await GetBitmapAsync(source))
{
if (bitmap == null) targetButton.SetCompoundDrawables(null, null, null, null);
else
{
var drawable = new BitmapDrawable(bitmap);
var tintColor = model.IsEnabled ? model.ImageTintColor : model.DisabledImageTintColor;
if (tintColor != Color.Transparent)
{
drawable.SetTint(tintColor.ToAndroid());
drawable.SetTintMode(PorterDuff.Mode.SrcIn);
}
using (var scaledDrawable = GetScaleDrawable(drawable, GetWidth(model.ImageWidthRequest), GetHeight(model.ImageHeightRequest)))
{
Drawable left = null;
Drawable right = null;
Drawable top = null;
Drawable bottom = null;
//System.Diagnostics.Debug.WriteLine($"SetImageSourceAsync intptr{targetButton.Handle}");
targetButton.CompoundDrawablePadding = RequestToPixels(model.Padding);
switch (model.Orientation)
{
case ImageOrientation.ImageToLeft:
targetButton.Gravity = GravityFlags.Left | GravityFlags.CenterVertical;
left = scaledDrawable;
break;
case ImageOrientation.ImageToRight:
targetButton.Gravity = GravityFlags.Right | GravityFlags.CenterVertical;
right = scaledDrawable;
break;
case ImageOrientation.ImageOnTop:
targetButton.Gravity = GravityFlags.Top | GravityFlags.CenterHorizontal;
top = scaledDrawable;
break;
case ImageOrientation.ImageOnBottom:
targetButton.Gravity = GravityFlags.Bottom | GravityFlags.CenterHorizontal;
bottom = scaledDrawable;
break;
case ImageOrientation.ImageCentered:
targetButton.Gravity = GravityFlags.Center; // | GravityFlags.Fill;
top = scaledDrawable;
break;
}
targetButton.SetCompoundDrawables(left, top, right, bottom);
}
}
}
}
/// <summary>
/// Gets a <see cref="Bitmap" /> for the supplied <see cref="ImageSource" />.
/// </summary>
/// <param name="source">The <see cref="ImageSource" /> to get the image for.</param>
/// <returns>A loaded <see cref="Bitmap" />.</returns>
private async Task<Bitmap> GetBitmapAsync(ImageSource source)
{
var handler = GetHandler(source);
var returnValue = (Bitmap)null;
if (handler != null) returnValue = await handler.LoadImageAsync(source, Context);
return returnValue;
}
/// <summary>
/// Called when the underlying model's properties are changed.
/// </summary>
/// <param name="sender">The Model used.</param>
/// <param name="e">The event arguments.</param>
protected override async void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == ImageButton.SourceProperty.PropertyName ||
e.PropertyName == ImageButton.DisabledSourceProperty.PropertyName ||
e.PropertyName == VisualElement.IsEnabledProperty.PropertyName ||
e.PropertyName == ImageButton.ImageTintColorProperty.PropertyName ||
e.PropertyName == ImageButton.DisabledImageTintColorProperty.PropertyName)
await SetImageSourceAsync(Control, ImageButton);
}
/// <summary>
/// Returns a <see cref="Drawable" /> with the correct dimensions from an
/// Android resource id.
/// </summary>
/// <param name="drawable">An android <see cref="Drawable" />.</param>
/// <param name="width">The width to scale to.</param>
/// <param name="height">The height to scale to.</param>
/// <returns>A scaled <see cref="Drawable" />.</returns>
private Drawable GetScaleDrawable(Drawable drawable, int width, int height)
{
var returnValue = new ScaleDrawable(drawable, 0, 100, 100).Drawable;
returnValue.SetBounds(0, 0, RequestToPixels(width), RequestToPixels(height));
return returnValue;
}
/// <summary>
/// Returns a drawable dimension modified according to the current display DPI.
/// </summary>
/// <param name="sizeRequest">The requested size in relative units.</param>
/// <returns>Size in pixels.</returns>
public int RequestToPixels(int sizeRequest)
{
if (Density == float.MinValue)
{
if (Resources.Handle == IntPtr.Zero || Resources.DisplayMetrics.Handle == IntPtr.Zero)
Density = 1.0f;
else
Density = Resources.DisplayMetrics.Density;
}
return (int)(sizeRequest * Density);
}
//Hot fix for the layout positioning issue on Android as described in http://forums.xamarin.com/discussion/20608/fix-for-button-layout-bug-on-android
private class TouchListener
: Object,
IOnTouchListener
{
public static readonly Lazy<TouchListener> Instance = new Lazy<TouchListener>(() => new TouchListener());
/// <summary>
/// Make TouchListener a singleton.
/// </summary>
private TouchListener()
{}
public bool OnTouch(View v, MotionEvent e)
{
var buttonRenderer = v.Tag as ButtonRenderer;
if (buttonRenderer != null && e.Action == MotionEventActions.Down) buttonRenderer.Control.Text = buttonRenderer.Element.Text;
return false;
}
}
}
}

namespace XLabs.Enums
{
/// <summary>
/// Specifies where the image will occur relative to the text on a
/// </summary>
public enum ImageOrientation
{
/// <summary>
/// The image to left
/// </summary>
ImageToLeft = 0,
/// <summary>
/// The image on top
/// </summary>
ImageOnTop = 1,
/// <summary>
/// The image to right
/// </summary>
ImageToRight = 2,
/// <summary>
/// The image on bottom
/// </summary>
ImageOnBottom = 3,
/// <summary>
/// Center the image
/// </summary>
ImageCentered = 4
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment