Created
August 20, 2023 15:23
-
-
Save osamaqarem/f54a81539d440e35914fd5ba14afd2c1 to your computer and use it in GitHub Desktop.
Android: Overlay with Circle Cutout ViewGroup
This file contains hidden or 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
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(); | |
} | |
} |
This file contains hidden or 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
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