Skip to content

Instantly share code, notes, and snippets.

@mobiRic
Created January 29, 2018 12:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mobiRic/b390545ad3ddcafd9fb7206cef0d4a44 to your computer and use it in GitHub Desktop.
Save mobiRic/b390545ad3ddcafd9fb7206cef0d4a44 to your computer and use it in GitHub Desktop.
Yet another circle ImageView for Android
/*
* Copyright (C) 2018 Glowworm Software
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package mobi.glowworm.lib.ui.widget;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.os.Build;
import android.support.annotation.ColorInt;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.widget.ImageView;
/**
* A simple {@link ImageView} that crops to a circle shape.
*/
@SuppressLint("AppCompatCustomView")
public class CircleImageView extends ImageView {
private static final int DEFAULT_FRAME_WIDTH_DP = 1 /*dp*/;
@ColorInt
private static final int DEFAULT_FRAME_COLOR = Color.BLACK;
private Paint framePaint;
private float frameWidth;
private int x;
private int y;
private float radius;
private Path frameClip;
public CircleImageView(Context context) {
super(context);
init(context, null, 0, 0);
}
public CircleImageView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context, attrs, 0, 0);
}
public CircleImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs, defStyleAttr, 0);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public CircleImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(context, attrs, defStyleAttr, defStyleRes);
}
private void init(Context context, @Nullable AttributeSet attrs, int defStyle, int defStyleRes) {
// TODO initialise xml properties
float scale = getResources().getDisplayMetrics().density;
frameWidth = (int) (DEFAULT_FRAME_WIDTH_DP * scale + 0.5f);
framePaint = new Paint();
framePaint.setAntiAlias(true);
framePaint.setColor(DEFAULT_FRAME_COLOR);
framePaint.setStyle(Paint.Style.STROKE);
framePaint.setStrokeWidth(frameWidth);
frameClip = new Path();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// check if anything has changed
if (((getMeasuredWidth() / 2) == x) && ((getMeasuredHeight() / 2) == y)) {
return;
}
x = getMeasuredWidth() / 2;
y = getMeasuredHeight() / 2;
radius = Math.min(x, y) - (frameWidth / 2);
frameClip.reset();
frameClip.addCircle(
x, y,
radius,
Path.Direction.CW);
frameClip.close();
}
@Override
protected void onDraw(Canvas canvas) {
// circle crop the canvas
final int save = canvas.save();
canvas.clipPath(frameClip);
{
super.onDraw(canvas);
}
canvas.restoreToCount(save);
// cropping does not anti-alias so mask with a frame
canvas.drawCircle(
x, y,
radius,
framePaint);
}
}
@mobiRic
Copy link
Author

mobiRic commented Jan 29, 2018

A simple circle ImageView implementation.

This version attempts to optimise for memory and does not create an extra bitmap in memory like many implementations.

Instead the drawing canvas is clipped to a circular path before drawing. There are 2 considerations to this approach:

  1. circular clipping of the canvas is not anti-aliased
  2. clipping is a slow process

The first issue is worked around by overdrawing a circular frame which masks the aliasing. The circle drawing is anti-aliased, and this is fine for most use cases.

The second issue is the compromise for not creating a second bitmap in memory - trading off speed for memory (although no performance tests have been done on this yet).


TODO:

  • allow XML configuration of properties
  • extend AppCompatImageView to add support for advanced features

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment