Wrapcode

Custom Circular ImageView for Xamarin Android

| 2 min read

Circular ImageView, because you can!

We had a requirement where we needed to make image look circular. Yes, just like those "modern design web and mobile apps". Android does not provide component for circular images out of the box. We had to extend ImageView widget and do some calculations to make it circular. Here's the code -

using Android.Content;
using Android.Content.Res;
using Android.Graphics;
using Android.Graphics.Drawables;
using Android.Util;
using Android.Views;
using Android.Widget;
namespace WrapCode.Util
{
public class CircleImageView : ImageView
{
private int borderWidth;
private int canvasSize;
private Bitmap image;
private Paint paint;
private Paint paintBorder;
public CircleImageView(Context context)
: this(context, null)
{
}
public CircleImageView(Context context, IAttributeSet attrs)
: this(context, attrs, Resource.Attribute.circularImageViewStyle)
{
}
public CircleImageView(Context context, IAttributeSet attrs, int defStyle)
: base(context, attrs, defStyle)
{
// init paint
paint = new Paint();
paint.AntiAlias = true;
paintBorder = new Paint();
paintBorder.AntiAlias = true;
// load the styled attributes and set their properties
TypedArray attributes = context.ObtainStyledAttributes(attrs, Resource.Styleable.CircularImageView, defStyle, 0);
if (attributes.GetBoolean(Resource.Styleable.CircularImageView_border, true))
{
int defaultBorderSize = (int)(4 * context.Resources.DisplayMetrics.Density+ 0.5f);
BorderWidth = attributes.GetDimensionPixelOffset(Resource.Styleable.CircularImageView_border_width, defaultBorderSize);
BorderColor = attributes.GetColor(Resource.Styleable.CircularImageView_border_color, Color.White);
}
if (attributes.GetBoolean(Resource.Styleable.CircularImageView_shadow, false))
{
addShadow();
}
}
public void addShadow()
{
SetLayerType(LayerType.Software, paintBorder);
paintBorder.SetShadowLayer(4.0f, 0.0f, 2.0f, Color.Gray);
}
public virtual int BorderWidth
{
set
{
this.borderWidth = 5;
this.RequestLayout();
this.Invalidate();
}
}
public virtual int BorderColor
{
set
{
if (paintBorder != null)
{
paintBorder.Color = Color.Gray;
}
this.Invalidate();
}
}
protected override void OnDraw(Canvas canvas)
{
// load the bitmap
image = drawableToBitmap(Drawable);
// init shader
if (image != null)
{
canvasSize = canvas.Width;
if (canvas.Height < canvasSize)
{
canvasSize = canvas.Height;
}
BitmapShader shader = new BitmapShader(Bitmap.CreateScaledBitmap(image, canvasSize, canvasSize, false), Shader.TileMode.Clamp, Shader.TileMode.Clamp);
paint.SetShader(shader);
int circleCenter = (canvasSize - (borderWidth * 2)) / 2;
canvas.DrawCircle(circleCenter + borderWidth, circleCenter + borderWidth, ((canvasSize - (borderWidth * 2)) / 2) + borderWidth - 4.0f, paintBorder);
canvas.DrawCircle(circleCenter + borderWidth, circleCenter + borderWidth, ((canvasSize - (borderWidth * 2)) / 2) - 4.0f, paint);
}
}
protected override void OnMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
int width = measureWidth(widthMeasureSpec);
int height = measureHeight(heightMeasureSpec);
SetMeasuredDimension(width, height);
}
private int measureWidth(int measureSpec)
{
int result = 0;
var specMode = MeasureSpec.GetMode(measureSpec);
var specSize = MeasureSpec.GetSize(measureSpec);
if (specMode == MeasureSpecMode.Exactly)
{
// The parent has determined an exact size for the child.
result = specSize;
}
else if (specMode == MeasureSpecMode.AtMost)
{
// The child can be as large as it wants up to the specified size.
result = specSize;
}
else
{
// The parent has not imposed any constraint on the child.
result = canvasSize;
}
return result;
}
private int measureHeight(int measureSpecHeight)
{
int result = 0;
var specMode = MeasureSpec.GetMode(measureSpecHeight);
int specSize = MeasureSpec.GetSize(measureSpecHeight);
if (specMode == MeasureSpecMode.Exactly)
{
// We were told how big to be
result = specSize;
}
else if (specMode == MeasureSpecMode.AtMost)
{
// The child can be as large as it wants up to the specified size.
result = specSize;
}
else
{
// Measure the text (beware: ascent is a negative number)
result = canvasSize;
}
return (result + 2);
}
public virtual Bitmap drawableToBitmap(Drawable drawable)
{
if (drawable == null)
{
return null;
}
else if (drawable is BitmapDrawable)
{
return ((BitmapDrawable)drawable).Bitmap;
}
Bitmap bitmap = Bitmap.CreateBitmap(drawable.IntrinsicWidth, drawable.IntrinsicHeight, Bitmap.Config.Argb8888);
Canvas canvas = new Canvas(bitmap);
drawable.SetBounds(0, 0, canvas.Width, canvas.Height);
drawable.Draw(canvas);
return bitmap;
}
}
}
view raw CircleImageView.cs hosted with ❤ by GitHub

How to use -

  • Add RoundeImageView.cs class (rename or change namespace if you want) to your project.

  • Declare required custom attributes for border, borderwidth, bordercolor, shadow in attr.xml  (create new attr.xml if it does not exist inside Resources/values folder).

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
    <declare-styleable name="CircleImageView">
    <attr name="border" format="boolean"></attr>
    <attr name="border_width" format="dimension"></attr>
    <attr name="border_color" format="color"></attr>
    <attr name="shadow" format="boolean"></attr>
    </declare-styleable>
    </resources>
    view raw attr.xml hosted with ❤ by GitHub

  • Instead of ImageView, use WrapCode.Util.CircleImageView with some custom attributes, refer the the sample layout below -

    <!-- Don't forget to add custom namespace in first xml element, otherwise get ready for exceptions and errors -->
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:custom="http://schemas.android.com/apk/res-auto"
    android:minWidth="25px"
    android:minHeight="25px"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/white"
    android:id="@+id/parentLayout">
    <!-- REPLACE Standard ImageView -->
    <!--<ImageView
    android:src="@drawable/icn_profile_pic"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:minWidth="150dp"
    android:minHeight="150dp"
    android:maxWidth="160dp"
    android:maxHeight="160dp"
    android:id="@+id/imgProfileUserImage" />-->
    <!-- WITH THIS CUSTOM ImageView -->
    <WrapCode.Util.CircleImageView
    android:layout_width="135dp"
    android:layout_height="135dp"
    android:id="@+id/imgProfilePicture"
    android:src="@drawable/icn_profile_picture"
    custom:border="true"
    custom:border_color="#eeeeee"
    custom:border_width="4dp"
    custom:shadow="true" />
    </RelativeLayout>
    view raw myprofile.xml hosted with ❤ by GitHub

Custom elements -

  1. custom:border (type boolean) - set it true if you want picture to have border.
  2. custom:border_color - Color of the border (in hex)
  3. custom:border_width - thickness of border in dp
  4. custom:shadow (type boolean) - set it true for shadow. Don't forget to add custom namespace** xmlns:custom="http://schemas.android.com/apk/res-auto"** in layout to avoid build fails -

I will be adding more customization options in future, I am also working on making a Xamarin Component and Nuget Package out of this. Keep checking this space for updates.

Hope that helped, Cheers..

Rahul.