Skip to content

Instantly share code, notes, and snippets.

@osamaqarem
Created August 20, 2023 15:23
Show Gist options
  • Save osamaqarem/f54a81539d440e35914fd5ba14afd2c1 to your computer and use it in GitHub Desktop.
Save osamaqarem/f54a81539d440e35914fd5ba14afd2c1 to your computer and use it in GitHub Desktop.
Android: Overlay with Circle Cutout ViewGroup
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.View;
public class CustomCircleProgressBar extends View {
private Paint paint;
private final RectF rect = new RectF();
private float progress = 100f;
public CustomCircleProgressBar(Context context) {
super(context);
init();
}
public CustomCircleProgressBar(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public CustomCircleProgressBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
void init() {
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setStyle(Paint.Style.STROKE);
paint.setColor(getResources().getColor(R.color.green));
DisplayMetrics metrics = getResources().getDisplayMetrics();
int strokeWidth = 8;
float dpStrokeWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, strokeWidth, metrics);
paint.setStrokeWidth(dpStrokeWidth);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
float windowWidth = this.getWidth();
float windowHeight = this.getHeight();
float edgeOffset = 10;
rect.set(
edgeOffset,
edgeOffset,
windowWidth - edgeOffset,
windowHeight - edgeOffset
);
float max = 100f;
float sweep = progress / max * 360;
canvas.drawArc(rect, -90, sweep, false, paint);
}
public void setProgress(final float newProgress) {
progress = newProgress;
invalidate();
}
}
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.RectF;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
/**
* This is a ViewGroup that draws an overlay with a circular cutout,
* and lays out the children in the rectangle around the circular cutout.
*/
public class CustomOverlay extends ViewGroup {
private Paint paint;
private Bitmap bitMap;
private Canvas bitmapCanvas;
// used to determine children layout with respect to circular cutout.
// also used to determine if face rectangle is within the circular cutout.
private final RectF circleBoundingRect = new RectF();
public CustomOverlay(Context context) {
super(context);
init();
}
public CustomOverlay(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public CustomOverlay(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
/**
* The children are laid out according to the drawing by the parent.
*/
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// did not draw yet.
if (circleBoundingRect.top == 0) return;
// expecting a single child (CustomCircleProgressBar)
final View child = getChildAt(0);
child.layout((int) circleBoundingRect.left, (int) circleBoundingRect.top,
(int) circleBoundingRect.right, (int) circleBoundingRect.bottom);
}
private void init() {
setWillNotDraw(false);
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if (bitmapCanvas != null) {
bitmapCanvas.setBitmap(null);
bitmapCanvas = null;
}
if (bitMap != null) {
bitMap.recycle();
bitMap = null;
}
bitMap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
bitmapCanvas = new Canvas(bitMap);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawBitmap(bitMap, 0, 0, null);
float windowWidth = this.getWidth();
float windowHeight = this.getHeight();
float cx = windowWidth / 2f;
float r = windowWidth / 2.5f;
float cy = windowHeight / 3f;
float top = (cy - r);
float bottom = (cy - r) + r * 2;
float left = (windowWidth - r * 2) / 2;
float right = ((windowWidth - r * 2) / 2) + r * 2;
if (top != circleBoundingRect.top ||
bottom != circleBoundingRect.bottom ||
left != circleBoundingRect.left ||
right != circleBoundingRect.right
) {
circleBoundingRect.top = top;
circleBoundingRect.bottom = bottom;
circleBoundingRect.left = left;
circleBoundingRect.right = right;
// layout children based on new circleBoundingRect
requestLayout();
}
// clear previous overlay draw
bitmapCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
// background color
bitmapCanvas.drawColor(getResources().getColor(R.color.transparent));
// clear area (cutout)
bitmapCanvas.drawCircle(cx, cy, r, paint);
}
public RectF getFaceRectWithRespectToCustomOverlay(RectF faceRect) {
RectF rectAdjusted = new RectF(faceRect);
rectAdjusted.set(
getWidth() * (1 - faceRect.right),
(getHeight() * faceRect.top),
getWidth() * (1 - faceRect.left),
(getHeight() * faceRect.bottom)
);
return rectAdjusted;
}
public RectF getCircleBoundingRect() {
return circleBoundingRect;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment