Punching holes in Android views

In this tutorial, I’m going to show you how you can “punch through” a part of an Android view in order to expose what’s behind it. While this might not be a very common UI paradigm, I’ve personally used it in a production app — and it looked pretty neat! So that you know where we’re heading, here’s an example of this in action.

Android punch through example

To achieve this effect, we’re going to be creating a custom view class. I’ve subclassed ConstraintLayout (because it’s my preferred way to create Android layouts), but you can use a different superclass, if you prefer. The only restriction is that you need to use a ViewGroup, because we’re going to use a child view to define our “window”. Here’s the relevant XML excerpt.

I’ve used an ImageView to make my example more fun, but it’s the view’s bounds that locate the window, so any view will do.

One thing to note in this layout is the two custom attributes, windowView_viewand windowView_drawable. These are described later in this tutorial, but here’s where they’re defined, in a <resources> XML element.

Now let’s take a look at the implementation of WindowView. As I said above, my class extends ConstraintLayout. Here are the member variables we’re going to need:

  • int mWindowViewId — The ID of the view that defines the window’s bounds.

  • View mWindowView — The window’s view.

  • Drawable mWindowDrawable — A drawable that defines the window’s shape. This is a kind of mask: non-transparent pixels will define the window. I used a circle (which I defined in an XML drawable) in my example video, but it can be any shape you like.

  • Bitmap mWindowBitmap — The window’s drawable after being converted to a bitmap (if necessary).

  • int[2] mViewCoords — The coordinates of this view in its window. Stored as a member variable to avoid repeated allocations inside onDraw.

  • int[2] mWindowCoords — Similar to the above, but the coordinates of our window.

  • Paint mPaint — A Paint used to draw the window. Again, stored as a member variable to avoid repeated allocations.

First, we need to do a little initialisation. Calling setLayerType is required to avoid some strange rendering issues that can occur using a software layer. Note the call to setXfermode. This is critical, as it is the use of the Porter/Duff DstOut blend mode that erases the background. The rest of the code is extracting the view and drawable attributes that we set in the layout XML. (Note: error checking omitted for brevity.) Here’s our constructor.

Now we can get on with the actual work of drawing our view. To get the effect we’re looking for, we override onDraw.

The first block checks whether or not we’ve already got a reference to the window’s view and, if not, looks it up.

The second block does some basic checks, then performs the following steps:

  1. Creates the window’s bitmap, if necessary;

  2. Gets the locations of the view and the window;

  3. Calculates the coordinates at which the window should be drawn;

  4. Draws the window’s bitmap at the calculated coordinates.

There are a couple of supporting methods being used in onDraw, which I’ll include here. First, here’s checkBitmap:

And here is bitmapFromDrawable:

Of course, there are many ways in which you can customise or extend this solution. One obvious addition would be the ability to add multiple windows. This could be achieved by passing and parsing strings for the two XML attributes, instead of single references. This I shall leave as an exercise for the reader!

Previous
Previous

Flutter: First Impressions

Next
Next

Small Teams and Process