Search code examples
androidandroid-custom-viewsurfaceviewandroid-framelayout

Custom view to mask a parent and make transparent circle at center of parent


I want to make a circular suface view (porthole effect). Surface view is inside a Frame layout. I want to make a custom view that i can add to Frame layout on top of surface view and mask whole Frame layout to produce porthole effect so that surface view will be shown as circle.

I searched and a lot for answer on Web and Stackoverflow but failed. Then i saw this question and i tried this custom view to mask frame layout(and hence surfaceview) but i am not getting the desired result.

What i want is a custom view that can take height and width of it's parent (parent is square in shape) and make a transparent circle at it's center touching all four sides at middle of the boundaries, rest(view - circle) of the view will be of color that i can set.

public class FocusView extends View {

private Paint mTransparentPaint;
private Paint mSemiBlackPaint;
private Path mPath = new Path();

public static float radius , xCor , yCor;

public FocusView(Context context) {
    super(context);
    initPaints();
}

public FocusView(Context context, AttributeSet attrs) {
    super(context, attrs);
    initPaints();
}

public FocusView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    initPaints();
}

private void initPaints() {
    mTransparentPaint = new Paint();
    mTransparentPaint.setColor(Color.GREEN);
    mTransparentPaint.setStrokeWidth(10);

    mSemiBlackPaint = new Paint();
    mSemiBlackPaint.setColor(Color.TRANSPARENT);
    mSemiBlackPaint.setStrokeWidth(10);
}

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);

    mPath.reset();

    mPath.addCircle(xCor,yCor,radius, Path.Direction.CW);
    mPath.setFillType(Path.FillType.INVERSE_EVEN_ODD);

    canvas.drawCircle(xCor,yCor,radius, mTransparentPaint);

    canvas.drawPath(mPath, mSemiBlackPaint);
    canvas.clipPath(mPath);
    canvas.drawColor(Color.parseColor("#FFFFFF"));        //A6000000
}

}

Please if somebody can help me. Thanks in advance.


Solution

  • This is an example of a view that paints the whole view pink and cuts a centered, circular hole making the parent visible:

    
    public class FocusView extends View {
    
        private Paint mCutPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        private Bitmap mBitmap;
        private Canvas mInternalCanvas;
    
        public FocusView(Context context) {
            super(context);
            init();
        }
    
        public FocusView(Context context, AttributeSet attrs) {
            super(context, attrs);
            init();
        }
    
        public FocusView(Context context, AttributeSet attrs, int defStyle) {
            super(context, attrs, defStyle);
            init();
        }
    
        private void init() {
            mCutPaint.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 (mInternalCanvas != null) {
                mInternalCanvas.setBitmap(null);
                mInternalCanvas = null;
            }
    
            if (mBitmap != null) {
                mBitmap.recycle();
                mBitmap = null;
            }
    
            mBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
            mInternalCanvas = new Canvas(mBitmap);
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
            if (mInternalCanvas == null || mBitmap == null) {
                return;
            }
    
            final int width = getWidth();
            final int height = getHeight();
    
            // make the radius as large as possible within the view bounds
            final int radius = Math.min(width, height) / 2;
    
            mInternalCanvas.drawColor(0xFFFF00FF);
            mInternalCanvas.drawCircle(width / 2, height / 2, radius, mCutPaint);
    
            canvas.drawBitmap(mBitmap, 0, 0, null);
        }
    }
    
    

    The reason for drawing to an internal Bitmap first is that if you apply PorterDuff.Mode.CLEAR to the original Canvas it will cut away everything that's been previously drawn to the canvas, including the parent view.

    There may be better solutions out there, but this one is simple enough to understand.