Let’s drill a hole in your view

Let’s drill a hole in your view

Sometimes you can see this kind of tutorial, that is called overlay tutorial. If you want to use it in your app, there’re already some libraries available like MaterialShowCase or TourGuide. But have you ever wonder how it can be done, or exactly how to draw an overlay with a hole? In this post, I will introduce to you 3 methods to achieve that.

I. Bitmap

Using a bitmap may be the way that most people will come up with. All those libraries I mentioned above are using this method. Basically, you create a bitmap with same size as your screen, fill it with color, then erase a region of its.

OK, here is the code:

//Create a bitmap with same size as screen.
mBitmap = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.ARGB_8888);
mBitmap.eraseColor(Color.TRANSPARENT);
//Fill it with color
Canvas c = new Canvas(mBitmap);
c.drawColor(mBackgroundColor);
Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
p.setStyle(Paint.Style.FILL);
p.setColor(0xFFFFFFFF);
p.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
//Erase color
c.drawCircle(mX, mY, mHoleRadius, p);

It’s quite simple. The only magic here is calling setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); on your Paint object so it can erase color. Now you can draw this Bitmap into your canvas:

canvas.drawBitmap(mBitmap, 0, 0, null);

The drawback of this method is the bitmap may take up a large of memory. If you don’t use it carefully, you may soon see some OutOfMemory crashes, especially on low devices.

II. Path

You can use Path to create a complex shape, in this case is a rectangle with a hole.

mPath = new Path();
mPath.setFillType(Path.FillType.WINDING);
mPath.addRect(0, 0, mWidth, mHeight, Path.Direction.CW);
mPath.addCircle(mX, mY, mHoleRadius, Path.Direction.CCW);

Now, simply create a Paint object and draw the path:

Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(mBackgroundColor);
canvas.drawPath(mPath, mPaint);

The drawback is drawPath() can be slow because Path are always rendered using the CPU.

III. Gradient

It sound weird but you can totally use gradient with some tricky setting to achieve the effect.

First, create a RadialGradient shader:

RadialGradient mShader = new RadialGradient(mX, mY, mHoleRadius,new int[]{Color.TRANSPARENT, Color.TRANSPARENT, mBackgroundColor}, new float[]{0f, 0.99f, 1f}, Shader.TileMode.CLAMP);

Now, set this shader to Paint object and simply draw a rectangle:

Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setShader(mShader);
canvas.drawRect(0, 0, mWidth, mHeight, mPaint);

The drawback is the edge of the hole is not sharp as using Bitmap or Path, but it isn’t a big different.

IV. Conclusion

Each of those methods I mentioned has its own drawback. Feel free to choose ones you familiar with. As for me, I’ll stick with Gradient method.

The source code is available on Github.