A custom layout that rounds the corners of its contents

I would like to create a generic ViewGroup, which can then be reused in XML layouts to round the corners of everything that fits 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> 
+6
source share
7 answers

The proper way to create a ViewGroup that writes its children is to do this in the dispatchDraw(Canvas) method.

This is an example of how you can crop any child ViewGroup elements 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 that is called for the clips. There is no need to setWillNotDraw(false) if your layout just fixed it.

This image is obtained using the code above, I just expanded Facebook ProfilePictureView (which is FrameLayout , including the ImageView square with facebook profile image):

circle cropping

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); } 

circular cropping

In fact, you can create any difficult path :)

Remember that you can repeatedly call clipPath using the "Op" operation to traverse multiple clips as you like.

NOTE. I created a path in onSizeChanged because this in onDraw bad for performance.

NOTE2: path trimming is performed without smoothing: /, so if you want smooth borders, you will need to do it in a different way. I don’t know how to trim using anti-aliasing right now.

UPDATE (schema)

Since you can apply elevation and shadow to Android Lollipop (API 21). A new concept called Outline has been introduced. This is the path that tells the structure that the presentation form is used to compute the shadow and other things (such as ripple effects).

By default, the Outline this view is a view-size rectangle, but you can easily make it an oval / circle or a rounded rectangle. To define a custom Outline , you must use the setOutlineProvider() method in the view if it is a custom view, which you can set in the constructor with your custom ViewOutlineProvider defined as the inner class of your custom view. You can define your own Outline provider using the Path of your choice if it is a convex path (the meaning of the mathematical concept is a closed path without a notch and without holes, as an example there can be neither a star shape, nor a gear shape).

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

Good luck.

+32
source

You can override the draw(Canvas canvas) method:

 public class RoundedLinearLayout extends LinearLayout { Path mPath; float mCornerRadius; public RoundedLinearLayout(Context context) { super(context); } public RoundedLinearLayout(Context context, AttributeSet attrs) { super(context, attrs); } public RoundedLinearLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @Override public void draw(Canvas canvas) { canvas.save(); canvas.clipPath(mPath); super.draw(canvas); canvas.restore(); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); RectF r = new RectF(0, 0, w, h); mPath = new Path(); mPath.addRoundRect(r, mCornerRadius, mCornerRadius, Direction.CW); mPath.close(); } public void setCornerRadius(int radius) { mCornerRadius = radius; invalidate(); } } 
+17
source

ViewGroup (and therefore its subclasses) sets a flag indicating that by default it does not draw any drawing. In the source, it looks something like this:

 // ViewGroup doesn't draw by default if (!debugDraw()) { setFlags(WILL_NOT_DRAW, DRAW_MASK); } 

So your onDraw(...) probably doesn't hit right now. If you want to do some hand drawing, call setWillNotDraw(false) .

+2
source

Remember to request onDraw to call using setWillNotDraw (false); and set the value to mRadius, then just do something like this:

 @Override protected void onDraw(Canvas canvas) { mPath.reset(); mRect.set(0, 0, canvas.getWidth(), canvas.getHeight()); mPath.addRoundRect(mRect, mRadius, mRadius, Direction.CCW); mPath.close(); canvas.clipPath(mPath); } 
+2
source

onDraw for FrameLayout is not called if Ur Background layout is not set;

You must override dispatchDraw;

0
source

U need to override the drawChild () method for the childViews clip.

 @Override protected boolean drawChild(Canvas canvas, View child, long drawingTime) { int flag = canvas.save(); canvas.clipPath(pathToClip); boolean result=super.drawChild(canvas, child, drawingTime); canvas.restoreToCount(flag); return result; } 

If you want to copy the background of the ViewGroup, override draw () instead. How to do it

 @Override public void draw(Canvas canvas) { int flag = canvas.save(); canvas.clipPath(pathToClip); super.draw(canvas); canvas.restoreToCount(flag); } 
-2
source

Why not just define ShapeDrawable as the background of your layout and just change the radius of the angle of the extruded at run time.

-3
source

All Articles