65
votes

Is there any way to define the clip region of a ViewGroup in android (Honeycomb)? For example, I have a ListView with an image background that has rounded corners. As I scroll through the list, the children stick out past the corners of the background - I would prefer them to clip within the rounded corners. Left: unclipped, Right: clipped

The left image is what it is currently doing, and the right is what I'd like.

I was looking at ClipDrawable, but it seems that this may only be used for progress bars?

Also, I'm trying to do this in a widget. So I cannot use a custom view and override onDraw for masking.

Thank you!

9
Did you figure out a solution? I am also trying to make this happen in a widget. - anavarroma

9 Answers

26
votes

Try subclassing ViewGroup and overriding the OnDraw method as follows, substituting in values for RADIUS_IN_PIXELS:

@Override
protected void onDraw(Canvas canvas) {
    Path clipPath = new Path();
    clipPath.addRoundRect(new RectF(canvas.getClipBounds()), RADIUS_IN_PIXELS, RADIUS_IN_PIXELS, Path.Direction.CW);
    canvas.clipPath(clipPath);
    super.onDraw(canvas);
}

...and also create a custom drawable like this called something like 'rounded', substituting in YOUR_BACKGROUND_COLOR and RADIUS_IN_DP, making sure to match the rounding of the rectangle in DP to your previous clipping radius in PX:

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <corners android:radius="RADIUS_IN_DP" />
    <solid android:color="YOUR_BACKGROUND_COLOR"/>
</shape>

Then you can use that subclass in your layout, adding the lines

android:background="@drawable/rounded"
android:clipChildren="true"

All children will clip to the bounds you specify in the OnDraw() override, and a background will be added based on your 'rounded' drawable.

20
votes

Make sure layout has a background with rounded corners.

Kotlin

layout.outlineProvider = ViewOutlineProvider.BACKGROUND
layout.clipToOutline = true

Java

layout.setOutlineProvider(ViewOutlineProvider.BACKGROUND);
layout.setClipToOutline(true);
5
votes

Create custom layout with overridden onDraw(Canvas canvas)

call canvas.clipRect(0, 0, mCanvasWidth, mCanvasHeight);

this will clip all views that goes out of layout boundary.

And don't forget to call setWillNotDraw(false) in constructor, so your onDraw will execute

1
votes

How about this: How to render an Android view to a bitmap, then clip that bitmap.

Or, another idea, use FrameLayout, stack your clip mask on top of your ViewGroup. Clip mask would have transparent middle, opaque borders.

In both cases, I guess it will be tricky to handle user input...

1
votes
Path p = new Path()

// define your clipping path...

canvas.clipPath(p);
1
votes

If you take a look to WhatsUp you can see that in the contact picture there is an image rounded by a rounded border. To do this I have put the ImageView inside a FrameLayout (FrameLayout because I use ProgressBar in front of picture to notify loading [actually invisible]) that has rounded border. The picture has a square shape but to refine it, you need to work with canvas.

Take another look at this link that will solve your problem ;)

0
votes

May be you can used the Canvas.clipRegion method to clip the differences of the to views.

0
votes

To add onto the answer by @Marco, you can also provide a custom View.outlineProvider. I ran into a similar issue using Google's newer MaterialCardView. For some reason, that view does not clip its children with the rounded corners. Child views would just draw on top of the rounded corner.

Kotlin:

val cornerRadius = 16.dp()
materialCardView.apply {
    clipToOutline = true
    outlineProvider = object : ViewOutlineProvider() {
        override fun getOutline(view: View?, outline: Outline?) {
            view?.apply {
                outline?.setRoundRect(0, 0, width, (height + cornerRadius).toInt(), cornerRadius)
            }
        }
    }
}

enter image description here

-4
votes

Give android:clipToPadding a try. It will solve your problem.