Custom Layout that rounds the corners of its content

android, android-layout

Solution

The right way to create a `ViewGroup` that clip its children is to do it in the `dispatchDraw(Canvas)` method.

This is an example on how you can clip any children of a `ViewGroup` with a circle:

private Path path = new Path();

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);

    // compute the path
    float halfWidth = w / 2f;
    float halfHeight = h / 2f;
    float centerX = halfWidth;
    float centerY = halfHeight;
    path.reset();
    path.addCircle(centerX, centerY, Math.min(halfWidth, halfHeight), Path.Direction.CW);
    path.close();

}

@Override
protected void dispatchDraw(Canvas canvas) {
    int save = canvas.save();
    canvas.clipPath(circlePath);
    super.dispatchDraw(canvas);
    canvas.restoreToCount(save);
}

the `dispatchDraw` method is the one called to clip children. No need to `setWillNotDraw(false)` if your layout just clip its children.

This image is obtained with the code above, I just extended Facebook `ProfilePictureView` (which is a `FrameLayout` including a square `ImageView` with the facebook profile picture):

So to achieve a round border you do something like this:

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);

    // compute the path
    path.reset();
    rect.set(0, 0, w, h);
    path.addRoundRect(rect, radius, radius, Path.Direction.CW);
    path.close();

}

@Override
protected void dispatchDraw(Canvas canvas) {
    int save = canvas.save();
    canvas.clipPath(path);
    super.dispatchDraw(canvas);
    canvas.restoreToCount(save);
}

You can actually create any complex path :)

Remember you can call clipPath multiple times with the "Op" operation you please to intersect multiple clipping in the way you like.

NOTE: I created the Path in the `onSizeChanged` because doing so in the `onDraw` is bad for performance.

NOTE2: clipping a Path is done without anti-aliasing :/ so if you want smooth borders you'll need to do it in some other way. I'm not aware of any way of making clipping use anti-aliasing right now.

UPDATE (Outline)

Since Android Lollipop (API 21) elevation and shadows can be applied to views. A new concept called Outline has been introduced. This is a path that tells the framework the shape of the view to be used to compute the shadow and other things (like ripple effects).

The default `Outline` of the view is a rectangular of the size of the view but can be easily made an oval/circle or a rounded rectangular. To define a custom `Outline` you have to use the method `setOutlineProvider()` on the view, if it's a custom View you may want to set it in the constructor with your custom `ViewOutlineProvider` defined as inner class of your custom View. You can define your own `Outline` provider using a `Path` of your choice, as long as it is a convex path (mathematical concept meaning a closed path with no recess and no holes, as an example neither a star shape nor a gear shape are convex).

You can also use the method `setClipToOutline(true)` to make the Outline also clip (and I think this also works with anti-aliasing, can someone confirm/refute in comments?), but this is only supported for non-`Path` Outline.

Good luck

Problem

I would like to create a generic ViewGroup which can then be reused in XML layouts to round the corners of anything that is put into it. For some reason `canvas.clipPath()` doesn't seem to have an effect. What am I doing wrong? Here is the Java code: ``` package rounded; import static android.graphics.Path.Direction.CCW; public class RoundedView extends FrameLayout { private float radius; private Path path = new Path(); private RectF rect = new RectF(); public RoundedView(Context context, AttributeSet attrs) { super(context, attrs); this.radius = attrs.getAttributeFloatValue(null, "corner_radius", 0f); } @Override protected void onDraw(Canvas canvas) { int savedState = canvas.save(); float w = getWidth(); float h = getHeight(); path.reset(); rect.set(0, 0, w, h); path.addRoundRect(rect, radius, radius, CCW); path.close(); boolean debug = canvas.clipPath(path); super.onDraw(canvas); canvas.restoreToCount(savedState); } } ``` Usage in XML: ``` <?xml version="1.0" encoding="utf-8"?> <rounded.RoundedView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" corner_radius="40.0" > <RelativeLayout android:id="@+id/RelativeLayout1" android:layout_width="match_parent" android:layout_height="match_parent" > ... </RelativeLayout> </rounded.RoundedView> ```

Original source