64

I am creating a pixel-hunting game. So my activity shows an ImageView. And I want to create a hint "show me where is the object". For this I need to blur whole image except a circle around a point where the object is located. Instead of blur I can show a just semitransparent black background. There is there is no problem to draw a semitransparent rectangle on the Canvas. But I don't know how to crop a transparent circle from it. The result should look like like this: enter image description here

Please help me to achieve same result on Android SDK.

Kara
  • 6,115
  • 16
  • 50
  • 57
Robert
  • 3,471
  • 3
  • 21
  • 24

11 Answers11

54

So finally I managed to do this.

Firstly I draw a semitransparent black rectangle on whole view. After that using PorterDuff.Mode.CLEAR I cut a transparent circle to show cat's position.

I had problem with PorterDuff.Mode.CLEAR: firstly I was getting a black circle instead of a transparent one.

Thanks to Romain Guy's comments here: comment here I understood that my window is opaque and I should draw on another bitmap. And only after draw on View's canvas.

Here is my onDraw method:

private Canvas temp;
private Paint paint;
private Paint p = new Paint();
private Paint transparentPaint;

private void init(){
    Bitmap bitmap = Bitmap.createBitmap(canvas.getWidth(), canvas.getHeight(), Bitmap.Config.ARGB_8888);
    temp = new Canvas(bitmap);
    paint = new Paint();
    paint.setColor(0xcc000000);
    transparentPaint = new Paint();
    transparentPaint.setColor(getResources().getColor(android.R.color.transparent));
    transparentPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
}

protected void onDraw(Canvas canvas) {
    temp.drawRect(0, 0, temp.getWidth(), temp.getHeight(), paint);
    temp.drawCircle(catPosition.x + radius / 2, catPosition.y + radius / 2, radius, transparentPaint);
    canvas.drawBitmap(bitmap, 0, 0, p);
}
Dmitriy Puchkov
  • 1,530
  • 17
  • 41
Robert
  • 3,471
  • 3
  • 21
  • 24
  • 14
    you should avoid all those allocations in onDraw since this method may be called repeatedly. see: http://stackoverflow.com/questions/13528550/punch-a-hole-in-a-rectangle-overlay-with-hw-acceleration-enabled-on-view – marmor Nov 27 '13 at 13:17
  • 1
    +1, please correct the code by removing all those allocations! – joe1806772 Nov 13 '14 at 16:23
  • 1
    wow, allocating a Bitmap the size of the whole canvas for every onDraw, this is the perfect example of what NOT to dol. – rupps Nov 24 '14 at 09:02
  • 4
    `Bitmap.createBitmap(canvas.getWidth(), canvas.getHeight()` will throw NPE because canvas is null at that point. – Pedro Oliveira Feb 18 '15 at 09:51
  • Can I get Your overlay View's Source Code? – Cyph3rCod3r Aug 12 '15 at 06:38
  • Thank you very much, that saved me a lot of trouble. – Iman Akbari Jan 01 '16 at 10:54
  • 1
    What is the magic bitmap instance and where is method init() called? Please, give full worked code. – RevakoOA Feb 24 '16 at 21:36
  • If it is still necessary please check my answer at the bottom based on this solution. – Yelnar May 24 '16 at 15:48
  • It's a bit late, however I do have one question about that. I want to do that exact same thing (hole in rectangle), however my problem is that my view needs to take up the whole screen, therefore the bitmap created will have the size of the screen which will be about 4 MBs - 10 MBs depending on the size of the screen. I guess that creating such large bitmap might cause an OutOfMemoryError, right? Is there any workaround about that? – steliosf May 13 '17 at 17:34
  • @Robert can you please upload your full ans plz – Rucha Bhatt Joshi Jun 07 '17 at 11:14
  • @Robert, give us full code for this class, please. How can we use it right? – zayn1991 Apr 04 '18 at 10:10
44

I have done this way by creating custom LinearLayout:

Check the Screenshot:

enter image description here

CircleOverlayView.java

import android.annotation.TargetApi;
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.os.Build;
import android.util.AttributeSet;
import android.widget.LinearLayout;

/**
 * Created by hiren on 10/01/16.
 */
public class CircleOverlayView extends LinearLayout {
    private Bitmap bitmap;

    public CircleOverlayView(Context context) {
        super(context);
    }

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

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

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public CircleOverlayView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

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

        if (bitmap == null) {
            createWindowFrame(); 
        }
        canvas.drawBitmap(bitmap, 0, 0, null);
    }

    protected void createWindowFrame() {
        bitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888); 
        Canvas osCanvas = new Canvas(bitmap);

        RectF outerRectangle = new RectF(0, 0, getWidth(), getHeight());

        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setColor(getResources().getColor(R.color.colorPrimary));
        paint.setAlpha(99);
        osCanvas.drawRect(outerRectangle, paint);

        paint.setColor(Color.TRANSPARENT); 
        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OUT)); 
        float centerX = getWidth() / 2;
        float centerY = getHeight() / 2;
        float radius = getResources().getDimensionPixelSize(R.dimen.radius);
        osCanvas.drawCircle(centerX, centerY, radius, paint);
    }

    @Override
    public boolean isInEditMode() {
        return true;
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        bitmap = null; 
    }
}

CircleDrawActivity.java:

public class CircleDrawActivity  extends AppCompatActivity{

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_circle_draw);
    }
}

activity_circle_draw.xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/rlParent"
    android:layout_width="match_parent"
    android:layout_height="match_parent">


    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@drawable/lighthouse"
        android:scaleType="fitXY" />

    <common.customview.CircleOverlayView
        android:id="@+id/cicleOverlay"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

    </common.customview.CircleOverlayView>


</RelativeLayout>

colors.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="colorPrimary">#3F51B5</color>
    <color name="colorPrimaryDark">#303F9F</color>
    <color name="colorAccent">#FF4081</color>
</resources>

dimens.xml:

<resources>
    <!-- Default screen margins, per the Android Design guidelines. -->
    <dimen name="nav_header_vertical_spacing">16dp</dimen>
    <dimen name="nav_header_height">160dp</dimen>
    <!-- Default screen margins, per the Android Design guidelines. -->
    <dimen name="activity_horizontal_margin">16dp</dimen>
    <dimen name="activity_vertical_margin">16dp</dimen>
    <dimen name="fab_margin">16dp</dimen>

    <dimen name="radius">50dp</dimen>
</resources>

Hope this will help you.

Hiren Patel
  • 52,124
  • 21
  • 173
  • 151
44

I found a solution without bitmap drawing and creation. Here is the result of my implementation: Example of overlay drawing

You need to create a custom FrameLayout and drawCircle with Clear paint:

    mBackgroundPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));

Also do not forget to disable Hardware acceleration and call setWillNotDraw(false) because we will override onDraw method

    setWillNotDraw(false);
    setLayerType(LAYER_TYPE_HARDWARE, null);

The full example is here:

public class TutorialView extends FrameLayout {
    private static final float RADIUS = 200;

    private Paint mBackgroundPaint;
    private float mCx = -1;
    private float mCy = -1;

    private int mTutorialColor = Color.parseColor("#D20E0F02");

    public TutorialView(Context context) {
        super(context);
        init();
    }

    public TutorialView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public TutorialView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public TutorialView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init();
    }

    private void init() {
        setWillNotDraw(false);
        setLayerType(LAYER_TYPE_HARDWARE, null);

        mBackgroundPaint = new Paint();
        mBackgroundPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        mCx = event.getX();
        mCy = event.getY();
        invalidate();
        return true;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawColor(mTutorialColor);
        if (mCx >= 0 && mCy >= 0) {
            canvas.drawCircle(mCx, mCy, RADIUS, mBackgroundPaint);
        }
    }
}

PS: This implementation just draws hole inside itself, you need to put background in your layout and add this TutorialView on top.

Oleksandr
  • 6,226
  • 1
  • 46
  • 54
  • 9
    my eraser trail was all black until i set `setLayerType(LAYER_TYPE_HARDWARE, null)` in my view's constructor. hunh. – homerman Sep 22 '16 at 08:23
  • Hii @Alexandr thank you for this ans.. can you help me more in [this](https://stackoverflow.com/questions/44392108/how-to-unblur-square-portion-of-blurry-image-on-touch-android) question, i tried to achieve same image as in this question but i'm not able to blur image as shown in the picture as well my unblur image is also not clear.. i used your solution to achieve my requirement but still failed, thank you in advance. :) – Rucha Bhatt Joshi Jun 07 '17 at 13:55
  • 1
    This is super awesome. – Madona wambua May 06 '18 at 03:19
  • Works for me!! Thanks @Oleksandr – Victor Gomes Jan 02 '20 at 22:22
4

@Robert's answer actually showed me how to solve this problem but his code doesn't work. So I have updated his solution and made it work:

public class CaptureLayerView extends View {

  private Bitmap bitmap;
  private Canvas cnvs;
  private Paint p = new Paint();
  private Paint transparentPaint = new Paint();;
  private Paint semiTransparentPaint = new Paint();;
  private int parentWidth;
  private int parentHeight;
  private int radius = 100;

  public CaptureLayerView(Context context) {
      super(context);
      init();
  }

  public CaptureLayerView(Context context, AttributeSet attrs) {
      super(context, attrs);
      init();
  }

  private void init() {
      transparentPaint.setColor(getResources().getColor(android.R.color.transparent));
      transparentPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));

      semiTransparentPaint.setColor(getResources().getColor(R.color.colorAccent));
      semiTransparentPaint.setAlpha(70);
  }

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

      bitmap = Bitmap.createBitmap(parentWidth, parentHeight, Bitmap.Config.ARGB_8888);
      cnvs = new Canvas(bitmap);
      cnvs.drawRect(0, 0, cnvs.getWidth(), cnvs.getHeight(), semiTransparentPaint);
      cnvs.drawCircle(parentWidth / 2, parentHeight / 2, radius, transparentPaint);
      canvas.drawBitmap(bitmap, 0, 0, p);
  }

  @Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

      parentWidth = MeasureSpec.getSize(widthMeasureSpec);
      parentHeight = MeasureSpec.getSize(heightMeasureSpec);

      this.setMeasuredDimension(parentWidth, parentHeight);
      super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  }
}

And now use this view in any layout like this:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

<SurfaceView
    android:id="@+id/surfaceView"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center">
</SurfaceView>

<com.example.myapp.CaptureLayerView
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

<Button
    android:id="@+id/btnTakePicture"
    android:layout_width="match_parent"
    android:layout_height="80dp"
    android:onClick="onClickPicture"
    android:text="@string/take_picture">
</Button>

Here I wanted a semi transparent layer on the SurfaceView with transparent circle in the center. P.S. This code is not optimized one because it creates Bitmap in onDraw method, it is because I couldn't get parent view's width and height in init method, so I only could know them in onDraw.

Yelnar
  • 674
  • 1
  • 7
  • 17
4

A simple 2020 kotlin solution without any warnings.


1. Code for custom view with hole

class HolePosition(var x: Float, var y: Float, var r: Float)

class HoleView @JvmOverloads constructor(
    context: Context?,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
): View(context, attrs, defStyleAttr) {

    private val paint: Paint = Paint()
    private var holePaint: Paint = Paint()
    private var bitmap: Bitmap? = null
    private var layer: Canvas? = null

    //position of hole
    var holePosition: HolePosition = HolePosition(0.0f, 0.0f, 0.0f)
        set(value) {
            field = value
            //redraw
            this.invalidate()
        }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        if (bitmap == null) { configureBitmap() }

        //draw background
        layer?.drawRect(0.0f, 0.0f, width.toFloat(), height.toFloat(), paint)
        //draw hole
        layer?.drawCircle(holePosition.x, holePosition.y, holePosition.r, holePaint);
        //draw bitmap
        canvas.drawBitmap(bitmap!!, 0.0f, 0.0f, paint);
    }

    private fun configureBitmap() {
        //create bitmap and layer
        bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
        layer = Canvas(bitmap!!)
    }

    init {
        //configure background color
        val backgroundAlpha = 0.8
        paint.color = ColorUtils.setAlphaComponent(resources.getColor(R.color.mainDark, null), (255 * backgroundAlpha).toInt() )

        //configure hole color & mode
        holePaint.color = resources.getColor(android.R.color.transparent, null)
        holePaint.xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR)
    }
}

2. Layout example

<com.your_company.package.HoleView
    android:id="@+id/hole_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
</com.your_company.package.HoleView>

3. Hole set usage

val holeView = findViewById<HoleView>(R.id.hole_view)
holeView.holePosition = HolePosition(x = 350.0f, y = 350.0f, r = 180.0f)

4. Results

Results

Mikhail Vasilev
  • 720
  • 6
  • 13
1

I don't have much to add to your answer, But if anyone's interested I moved the bitmap allocation and all to onSizeChanged so it's better performance wise.

Here you can find a FrameLayout with a "Hole" in the middle of it ;)

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.os.Handler;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.FrameLayout;

/**
 * Created by blackvvine on 1/1/16.
 */
public class SteroidFrameLayout extends FrameLayout {

    private Paint transPaint;
    private Paint defaultPaint;

    private Bitmap bitmap;
    private Canvas temp;

    public SteroidFrameLayout(Context context) {
        super(context);
        __init__();
    }

    public SteroidFrameLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        __init__();
    }

    public SteroidFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        __init__();
    }

    private void __init__() {
        transPaint = new Paint();
        defaultPaint = new Paint();
        transPaint.setColor(Color.TRANSPARENT);
        transPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
        setWillNotDraw(false);
    }


    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
        temp = new Canvas(bitmap);
    }

    @Override
    protected void dispatchDraw(Canvas canvas) {

        temp.drawColor(Color.TRANSPARENT);
        super.dispatchDraw(temp);
        temp.drawCircle(cx, cy, getWidth()/4, transPaint);

        canvas.drawBitmap(bitmap, 0, 0, defaultPaint);

        if (p < 1)
            invalidate();
        else
            animRunning = false;

    }

}

p.s: although it's much more efficient than the original answer it's still a relatively heavy task to do in a draw() method, so if you're using this technique in an animation like me, don't expect a 60.0fps smooth one

Iman Akbari
  • 2,167
  • 26
  • 31
1
public class CircleBlur extends Activity implements View.OnTouchListener {
SeekBar seekBar;
ImageView image,image1;

private Paint paint;
Bitmap circle,blurimg;

private Matrix matrix = new Matrix();
private Matrix savedMatrix = new Matrix();
private static final int NONE = 0;
private static final int DRAG = 1;
private static final int ZOOM = 2;
private int mode = NONE;
private PointF start = new PointF();
private PointF mid = new PointF();
private float oldDist = 1f;
float newRot = 0f;
private float d = 0f;
private float[] lastEvent = null;
private float radius=12;
Bitmap blurbitmap;
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1)
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN);
    setContentView(R.layout.activity_blurimage);
    image=findViewById(R.id.image);
    seekBar=findViewById(R.id.seekbar);
    image1=findViewById(R.id.image1);

    paint = new Paint(Paint.ANTI_ALIAS_FLAG);

    //here your image bind to Imageview
    image.setImageResource(R.drawable.nas1);

    image1.setOnTouchListener(this);
    seekBar.setProgress(12);

    seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
        @Override
        public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
            radius = (float) CircleBlur.this.seekBar.getProgress();
            blurbitmap=createBlurBitmap(blurimg, radius);
            CircleBlur();
        }
        @Override
        public void onStartTrackingTouch(SeekBar seekBar) {
        }
        @Override
        public void onStopTrackingTouch(SeekBar seekBar) {
        }
    });

}
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1)
private Bitmap createBlurBitmap(Bitmap src, float r) {
    if (r <= 0) {
        r = 0.1f;
    } else if (r > 25) {
        r = 25.0f;
    }
    Bitmap bitmap = Bitmap.createBitmap(src.getWidth(), src.getHeight(), Bitmap.Config.ARGB_8888);
    RenderScript renderScript = RenderScript.create(this);

    Allocation blurInput = Allocation.createFromBitmap(renderScript, src);
    Allocation blurOutput = Allocation.createFromBitmap(renderScript, bitmap);

    ScriptIntrinsicBlur blur = ScriptIntrinsicBlur.create(renderScript, Element.U8_4(renderScript));
    blur.setInput(blurInput);
    blur.setRadius(r);
    blur.forEach(blurOutput);

    blurOutput.copyTo(bitmap);
    renderScript.destroy();

    return bitmap;
}

@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1)
public void CircleBlur()
{
    Bitmap result;
    // your circle image
    circle = BitmapFactory.decodeResource(getResources(),R.drawable.cicleouter);
        result = Bitmap.createBitmap(blurimg.getWidth(), blurimg.getHeight(), Bitmap.Config.ARGB_8888);
        Canvas mCanvas = new Canvas(result);
        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
        paint.setDither(true);
        mCanvas.drawBitmap(blurimg,0,0, null);
        mCanvas.drawBitmap(circle, matrix, paint);
        paint.setXfermode(null);
    image1.setImageBitmap(result);
}

@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1)
@Override
public boolean onTouch(View v, MotionEvent event) {
        image1 = (ImageView) v;
        float x = event.getX(), y = event.getY();
        switch (event.getAction() & MotionEvent.ACTION_MASK) {
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_DOWN:
                savedMatrix.set(matrix);
                start.set(x, y);
                mode = DRAG;
                lastEvent = null;
                break;
            case MotionEvent.ACTION_POINTER_DOWN:
                oldDist = spacing(event);
                if (oldDist > 10f) {
                    savedMatrix.set(matrix);
                    midPoint(mid, event);
                    mode = ZOOM;
                }
                lastEvent = new float[4];
                lastEvent[0] = event.getX(0);
                lastEvent[1] = event.getX(1);
                lastEvent[2] = event.getY(0);
                lastEvent[3] = event.getY(1);
                d = rotation(event);
                break;
            // case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_POINTER_UP:
                mode = NONE;
                lastEvent = null;
                break;
            case MotionEvent.ACTION_MOVE:
                if (mode == DRAG) {
                    matrix.set(savedMatrix);
                    float dx = x - start.x;
                    float dy = y - start.y;
                    matrix.postTranslate(dx, dy);
                } else if (mode == ZOOM) {
                    float newDist = spacing(event);
                    if (newDist > 10f) {
                        matrix.set(savedMatrix);
                        float scale = (newDist / oldDist);
                        matrix.postScale(scale, scale, mid.x, mid.y);
                    }
                    if (lastEvent != null && event.getPointerCount() == 2 || event.getPointerCount() == 3) {
                        newRot = rotation(event);
                        float r = newRot - d;
                        float[] values = new float[9];
                        matrix.getValues(values);
                        float tx = values[2];
                        float ty = values[5];
                        float sx = values[0];
                        float xc = (image.getWidth() / 2) * sx;
                        float yc = (image.getHeight() / 2) * sx;
                        matrix.postRotate(r, tx + xc, ty + yc);
                    }
                }
                break;
        }
    CircleBlur();
        return true;
}
private float spacing(MotionEvent event) {
    float x = event.getX(0) - event.getX(1);
    float y = event.getY(0) - event.getY(1);
    float s=x * x + y * y;
    return (float)Math.sqrt(s);
}
private void midPoint(PointF point, MotionEvent event) {
    float x = event.getX(0) + event.getX(1);
    float y = event.getY(0) + event.getY(1);
    point.set(x / 2, y / 2);
}
private float rotation(MotionEvent event) {
    double delta_x = (event.getX(0) - event.getX(1));
    double delta_y = (event.getY(0) - event.getY(1));
    double radians = Math.atan2(delta_y, delta_x);
    return (float) Math.toDegrees(radians);
}

}

0

This worked for me:

canvas.drawCircle(x,y,radius,new Paint(Color.TRANSPARENT))
max
  • 330
  • 3
  • 13
0

If you're having trouble getting a transparent circle cutout over a view with an opaque background, see this answer. To get it to work, I set my custom layout view to have a transparent background in XML, then drew on the background color I wanted for the layout with the line

cv.drawColor(Color.BLUE); //replace with your desired background color

The full OnDraw method from the answer I linked above:

@Override
protected void onDraw(Canvas canvas) {

    int w = getWidth();
    int h = getHeight();
    int radius = w > h ? h / 2 : w / 2;

    bm.eraseColor(Color.TRANSPARENT);
    cv.drawColor(Color.BLUE);
    cv.drawCircle(w / 2, h / 2, radius, eraser);
    canvas.drawBitmap(bm, 0, 0, null);
    super.onDraw(canvas);
}
Jacob Hatwell
  • 91
  • 1
  • 4
0

Kotlin version without Bitmap allocation. But instead of a circle, a rounded rectangle is drawn here. I'm sure you can adapt it to circle by yourself.

    private val scanAreaBorder = Paint().apply {
    color = ContextCompat.getColor(context, R.color.postyellow)
    style = Paint.Style.STROKE
    strokeWidth = dpToPx(context, resources.getDimension(R.dimen.scan_area_border_stroke_width).toInt())
}

private val scanArea = Paint().apply {
    color = ContextCompat.getColor(context, android.R.color.transparent)
    xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR)
}

private val background = Paint().apply {
    color = ContextCompat.getColor(context, R.color.scanner_area)
}

 init {
    setLayerType(LAYER_TYPE_HARDWARE, null)
}

    override fun onDraw(canvas: Canvas) {
    super.onDraw(canvas)
    canvas.run {
        drawRect(0f, 0f, width.toFloat(), height.toFloat(), background)
        val top = (height / 2f) - dpToPx(context, resources.getDimension(R.dimen.scan_area_height).toInt())
        val bottom = (height / 2f) + dpToPx(context, resources.getDimension(R.dimen.scan_area_height).toInt())
        val margin = dpToPx(context, resources.getDimension(R.dimen.scan_area_margin).toInt())
        val cornerRadius = dpToPx(context, resources.getDimension(R.dimen.scan_area_corner_radius).toInt())
        drawRoundRect(margin, top, width.toFloat() - margin, bottom, cornerRadius, cornerRadius, scanArea)
        drawRoundRect(margin, top, width.toFloat() - margin, bottom, cornerRadius, cornerRadius, scanAreaBorder)
    }
}
Camino2007
  • 796
  • 10
  • 17
0

This can be actually done in a much more efficient way, and with much less code using a Path object.

enter image description here

code: In the constructor of a View overlay class:

    path.addCircle(600,1000, 200, Path.Direction.CW);
    path.setFillType(Path.FillType.INVERSE_EVEN_ODD);
    paint.setColor(0x55_00_00_00);

(The numbers for x, y and radius above should be replaces with whatever you need).

And override the view's onDraw(Canvas):

@Override
protected void onDraw(Canvas canvas) {
    canvas.drawPath(path,paint);
}

That's it...

Amir Uval
  • 14,425
  • 4
  • 50
  • 74