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>
To do that right click on drawable (under resources), choose New, choose Drawable Resource File. Name the resource rounded_blue_border and make the root element shape. We will use the attributes stroke, corners and padding. The stroke determines the width and color of the border.
You can simply use MaterialAlertDialogBuilder to create custom dialog with rounded corners. then create a Alert Dialog object in Java class like this : AlertDialog alertDialog = new MaterialAlertDialogBuilder(this,R. style.
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
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();
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With