draw rounded corner inside rectangle - android

I designed one view in which i have rounded corners. and inside that view i have other view. i give rounded corners to stroke but its not effecting inside box. you can view in below screenshot.
you can see in both inside corners. there are not properly rounded shape.
so how can I do that with proper rounded shape?
below is code:
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(mBorderColor);
paint.setStyle(Style.STROKE);
float strokeWidth = mHeight / 30.0f;
float strokeWidth2 = strokeWidth / 2;
paint.setStrokeWidth(strokeWidth);
int space = 3;
int headHeight = (int) (strokeWidth + 10.5f);
Path strokepath = RoundedRect(strokeWidth2, headHeight + strokeWidth2, mWidth - strokeWidth2, mHeight - strokeWidth2, 15, 15,
true, true, true, true);
canvas.drawPath(strokepath, paint);
Path fillpath = RoundedRect(strokeWidth+space, headHeight + strokeWidth + topOffset-space, mWidth - strokeWidth-space, mHeight - strokeWidth-space, 15, 15,
true, true, true, true);
canvas.drawPath(fillpath, mViewPaint);
paint.setStyle(Style.FILL);
public static Path RoundedRect(
float left, float top, float right, float bottom, float rx, float ry,
boolean tl, boolean tr, boolean br, boolean bl) {
Path path = new Path();
if (rx < 0) rx = 0;
if (ry < 0) ry = 0;
float width = right - left;
float height = bottom - top;
if (rx > width / 2) rx = width / 2;
if (ry > height / 2) ry = height / 2;
float widthMinusCorners = (width - (2 * rx));
float heightMinusCorners = (height - (2 * ry));
path.moveTo(right, top + ry);
if (tr)
path.rQuadTo(0, -ry, -rx, -ry);//top-right corner
else {
path.rLineTo(0, -ry);
path.rLineTo(-rx, 0);
}
path.rLineTo(-widthMinusCorners, 0);
if (tl)
path.rQuadTo(-rx, 0, -rx, ry); //top-left corner
else {
path.rLineTo(-rx, 0);
path.rLineTo(0, ry);
}
path.rLineTo(0, heightMinusCorners);
if (bl)
path.rQuadTo(0, ry, rx, ry);//bottom-left corner
else {
path.rLineTo(0, ry);
path.rLineTo(rx, 0);
}
path.rLineTo(widthMinusCorners, 0);
if (br)
path.rQuadTo(rx, 0, rx, -ry); //bottom-right corner
else {
path.rLineTo(rx, 0);
path.rLineTo(0, -ry);
}
path.rLineTo(0, -heightMinusCorners);
path.close();//Given close, last lineto can be removed.
return path;
}

With the way that corners are drawn, the inner part of the corner will always be of a smaller radii than the outer part of the corner. To get the right radii for the inner part you have to draw it onto the screen. In this case you have to draw the space between the Stoke Path and the Fill Path. The code below is what is needed to draw the space, if mViewPaint does not have a stroke width.
Paint spacePaint = new Paint();
spacePaint.setAntiAlias(true);
spacePaint.setColor(mBackground);
spacePaint.setStyle(Paint.Style.STROKE);
spacePaint.setStrokeWidth(space*2);
Path spacepath = RoundedRect(strokeWidth + space,
headHeight + strokeWidth + topOffset-space,
mWidth - strokeWidth - space,
mHeight - strokeWidth - space,
15, 15,
true, true, true, true);
canvas.drawPath(spacepath, spacePaint);
You also have to change the following line for this to work
paint.setStyle(Paint.Style.STROKE);
to
paint.setStyle(Paint.Style.FILL_AND_STROKE);
Now as others have suggested you could have used either Canvas.drawRoundRect() or Path.addRoundRect and they would have done the same thing as your method RoundedRect.

Related

Custom Drawable border overlaps with App Bar Layout Header

This is how my custom drawable looks by default.
But when scrolled, it overlaps with the AppBarLayout.
The code for the Drawable goes like this:
#Override
public void draw(#NonNull Canvas canvas) {
// get drawable dimensions
Rect bounds = getBounds();
float width = bounds.right - bounds.left;
float height = bounds.bottom - bounds.top;
float w2 = width / 2;
float h2 = height / 2;
float radius = Math.min(w2, h2) - mStrokeWidth / 2;
mPath.reset();
mPath.addCircle(width / 2, height / 2, radius, Path.Direction.CW);
canvas.clipPath(mPath);
// draw background gradient
float barHeight = height / themeColors.length;
mRectF.left = 0;
mRectF.top = 0;
mRectF.right = width;
mRectF.bottom = height;
for (int i = 0; i < themeColors.length; i++) {
mPaint.setColor(themeColors[i]);
canvas.drawRect(0, i * barHeight, width, (i + 1) * barHeight, mPaint);
}
mRectF.set(0, 0, width, height);
canvas.clipRect(mRectF, Region.Op.REPLACE);
if (mStrokeWidth != 0)
canvas.drawCircle(width / 2, height / 2, width / 2 - mStrokeWidth / 2, mStrokePaint);
}
Support Library Version: 25.3.1, 26.1.0
What I've tried:
- Different Region values for clipping path instead of REPLACE
- Clipping path rectangle first and then clipping circle.
How do I fix this ?
I am posting my solution as answer.
The reason it was getting overlapped was because the canvas was getting clipped twice without saving it.
I removed this statement:
canvas.clipRect(mRectF, Region.Op.REPLACE);
and before clipping the canvas for the first time
i saved its state using
canvas.save();
canvas.clipPath(mPath);
and then when i was drawing the stroke, i need the original canvas so i restored it
canvas.restore();
if (mStrokeWidth != 0)
canvas.drawCircle(width / 2, height / 2, width / 2 - mStrokeWidth / 2, mStrokePaint);
This fixed the issue.
Final Drawable Code:
#Override
public void draw(#NonNull Canvas canvas) {
// get drawable dimensions
Rect bounds = getBounds();
float width = bounds.right - bounds.left;
float height = bounds.bottom - bounds.top;
float w2 = width / 2;
float h2 = height / 2;
float radius = Math.min(w2, h2) - mStrokeWidth / 2;
mPath.reset();
mPath.addCircle(width / 2, height / 2, radius, Path.Direction.CW);
canvas.save();
canvas.clipPath(mPath);
// draw background gradient
float barHeight = height / themeColors.length;
mRectF.left = 0;
mRectF.top = 0;
mRectF.right = width;
mRectF.bottom = height;
for (int i = 0; i < themeColors.length; i++) {
mPaint.setColor(themeColors[i]);
canvas.drawRect(0, i * barHeight, width, (i + 1) * barHeight, mPaint);
}
mRectF.set(0, 0, width, height);
//canvas.clipRect(mRectF, Region.Op.REPLACE);
canvas.restore();
if (mStrokeWidth != 0)
canvas.drawCircle(width / 2, height / 2, width / 2 - mStrokeWidth / 2, mStrokePaint);
}

How to draw a partial round rect on a android canvas?

I want to draw on a canvas a round rectangle fill with color (or bitmap) and with a stroke but without some selected corners and also without the border on some selected sides. any ideas how i can achieve this ?
you can split the drawing process into two parts.
draw the fill area
when trying to draw a shape that is not support by the standard sdk APIs, Canvas.drawPath method will be a good way to do it.you can just define four variables to represent radiuses of four corners.
Path path = new Path();
Rect drawingRect = {the rect area you want to draw}
RectF topLeftArcBound = new RectF();
RectF topRightArcBound = new RectF();
RectF bottomLeftArcBound = new RectF();
RectF bottomRightArcBound = new RectF();
topRightArcBound.set(drawingRect.right - topRightRadius * 2, drawingRect.top, drawingRect.right, drawingRect.top + topRightRadius * 2);
bottomRightArcBound.set(drawingRect.right - bottomRightRadius * 2, drawingRect.bottom - bottomRightRadius * 2, drawingRect.right, drawingRect.bottom);
bottomLeftArcBound.set(drawingRect.left, drawingRect.bottom - bottomLeftRadius * 2, drawingRect.left + bottomLeftRadius * 2, drawingRect.bottom);
topLeftArcBound.set(drawingRect.left, drawingRect.top, drawingRect.left + topLeftRadius * 2, drawingRect.top + topLeftRadius * 2);
path.reset();
path.moveTo(drawingRect.left + topLeftRadius, drawingRect.top);
//draw top horizontal line
path.lineTo(drawingRect.right - topRightRadius, drawingRect.top);
//draw top-right corner
path.arcTo(topRightArcBound, -90, 90);
//draw right vertical line
path.lineTo(drawingRect.right, drawingRect.bottom - bottomRightRadius);
//draw bottom-right corner
path.arcTo(bottomRightArcBound, 0, 90);
//draw bottom horizontal line
path.lineTo(drawingRect.left - bottomLeftRadius, drawingRect.bottom);
//draw bottom-left corner
path.arcTo(bottomLeftArcBound, 90, 90);
//draw left vertical line
path.lineTo(drawingRect.left, drawingRect.top + topLeftRadius);
//draw top-left corner
path.arcTo(topLeftArcBound, 180, 90);
path.close();
paint.setStyle(Paint.Style.FILL);
canvas.drawPath(path, paint);
and you can just set radius to zero for selected corners
draw border
border contains eight parts:
top-left corner
top line
top-right corner
right line
bottom-right corner
bottom line
bottom-left corner
left line
use a int value to contains selected parts will be a good idea
private static final int TOP_LEFT_CORNER = 0x1;
private static final int TOP_LINE = 0x2;
private static final int TOP_RIGHT_CORNER = 0x4;
private static final int RIGHT_LINE = 0x8;
private static final int BOTTOM_RIGHT_CORNER = 0x10;
private static final int BOTTOM_LINE = 0x20;
private static final int BOTTOM_LEFT_CORNER = 0x40;
private static final int LEFT_LINE = 0x80;
private int selectedParts = TOP_LEFT_CORNER | TOP_LINE | TOP_RIGHT_CORNER;
and now we can draw border based on selectedParts:
paint.setStyle(Paint.Style.STROKE);
if((selectedParts & TOP_LINE) > 0){
canvas.drawLine(drawingRect.left + topLeftRadius, drawingRect.top, drawingRect.right - topRightRadius, drawingRect.top);
}
if((selectedParts & TOP_RIGHT_CORNER) > 0){
canvas.drawArc(topRightArcBound, -90, 90, false, paint);
}
if((selectedParts & RIGHT_LINE) > 0){
canvas.drawLine(drawingRect.right, drawingRect.top + topRightRadius, drawingRect.right, drawingRect.bottom - bottomRightRadius, paint);
}
if((selectedParts & BOTTOM_RIGHT_CORNER) > 0){
canvas.drawArc(bottomRightArcBound, 0, 90, false, paint);
}
if((selectedParts & BOTTOM_LINE) > 0){
canvas.drawLine(drawingRect.right - bottomRightRadius, drawingRect.bottom. drawingRect.left + bottomLeftRadius, drawingRect.bottom, paint);
}
if((selectedParts & BOTTOM_LEFT_CORNER) > 0){
canvas.drawArc(bottomLeftArcBound, 90, 90, false, paint);
}
if((selectedParts & LEFT_LINE) > 0){
canvas.drawLine(drawingRect.left, drawingRect.bottom - bottomLeftRadius, drawingRect.left, drawingRect.top + topLeftRadius, paint);
}
if((selectedParts & TOP_LEFT_CORNER) > 0){
canvas.drawArc(topLeftArcBound, 180, 90, false, paint);
}

custom view not align centre

i am drawing canavasarc but some how it always start from left i am it should start from middle
float x = 0.25f;
final float radius = x * (new Float(dpi));
mRadius = Math.round(radius) + 20;
mRect = new RectF(
getWidth() + mStrokeWidth, getWidth() + mStrokeWidth, getWidth() + (mRadius / 2) - mStrokeWidth, getWidth() + (mRadius / 2) - mStrokeWidth
);
canvas.drawArc(mRect, lastDegree, mSectionDegree, false, mPaint);
why this view always starts from left even i have given gravity centre still
float Degree = 270 + (mGap / 2);
for (int i = 0; i < mTotalSections; i++) {
fillColor(i);
canvas.drawArc(mRect, Degree, mDegree, false, mPaint);
Degree += mDegree + mGap;
Paint mPaint1 = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint1.setStrokeWidth(1);
mPaint1.setStyle(Paint.Style.FILL);
mPaint1.setAntiAlias(true);
mPaint1.setTextSize(15 * getResources().getDisplayMetrics().density);
mPaint1.setColor(getResources().getColor(black));
mPaint1.setTextAlign(Paint.Align.CENTER);
canvas.drawText(text, mRect.centerX(), mRect.centerY(), mPaint1);
}
I'm not sure about what you want to do but your rect looks weird. A rect more like that should would be better:
mRect = new RectF(
0 + mStrokeWidth, 0 + mStrokeWidth, getWidth() - mStrokeWidth, getHeight - mStrokeWidth
);
But a little draw about what you want and the values of your angle would be perfect ;). I will edit my answer if you provide it.
Edit:
So if you want a circle in the middle of your custom view you can do something like that:
RectF mRect = new RectF(
50 + mStrokeWidth, 50 + mStrokeWidth, getWidth() -50 - mStrokeWidth, getHeight() -50 - mStrokeWidth
);
canvas.drawArc(mRect, 0,360,false,paint);
result(the blue lines are the limit of my view):
You have to modify the 50 to fit what you want ;).
If you want an arc that stick to the edges with an angle of 180 degrees:
RectF mRect = new RectF(
mStrokeWidth, mStrokeWidth, getWidth() - mStrokeWidth, getHeight() - mStrokeWidth
);
canvas.drawArc(mRect, 0,180,false,paint);
Result:
Let me know if you need something else.

How to draw a triangle, a star, a square or a heart on the canvas?

I am able to draw a circle and a rectangle on canvas by using
path.addCircle()
and
path.addRect().
And now I am wondering how to draw a triangle or a star or a square or a heart?
For future direct answer seekers, I have drawn an almost symmetric star using canvas, as shown in the image:
The main tool is using Paths.
Assuming you have setup:
Paint paint = new Paint();
paint.setColor(Color.WHITE);
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.STROKE);
Path path = new Path();
Then in you onDraw you can use the path like I do below. It will scale properly to any sizes canvas
#Override
protected void onDraw(Canvas canvas) {
float mid = getWidth() / 2;
float min = Math.min(getWidth(), getHeight());
float fat = min / 17;
float half = min / 2;
float rad = half - fat;
mid = mid - half;
paint.setStrokeWidth(fat);
paint.setStyle(Paint.Style.STROKE);
canvas.drawCircle(mid + half, half, rad, paint);
path.reset();
paint.setStyle(Paint.Style.FILL);
// top left
path.moveTo(mid + half * 0.5f, half * 0.84f);
// top right
path.lineTo(mid + half * 1.5f, half * 0.84f);
// bottom left
path.lineTo(mid + half * 0.68f, half * 1.45f);
// top tip
path.lineTo(mid + half * 1.0f, half * 0.5f);
// bottom right
path.lineTo(mid + half * 1.32f, half * 1.45f);
// top left
path.lineTo(mid + half * 0.5f, half * 0.84f);
path.close();
canvas.drawPath(path, paint);
super.onDraw(canvas);
}
For everybody that needs a heart shape:
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Path;
import android.view.View;
public class Heart extends View {
private Path path;
private Paint paint;
public Heart(Context context) {
super(context);
path = new Path();
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// Fill the canvas with background color
canvas.drawColor(Color.WHITE);
paint.setShader(null);
float width = getContext().getResources().getDimension(R.dimen.heart_width);
float height = getContext().getResources().getDimension(R.dimen.heart_height);
// Starting point
path.moveTo(width / 2, height / 5);
// Upper left path
path.cubicTo(5 * width / 14, 0,
0, height / 15,
width / 28, 2 * height / 5);
// Lower left path
path.cubicTo(width / 14, 2 * height / 3,
3 * width / 7, 5 * height / 6,
width / 2, height);
// Lower right path
path.cubicTo(4 * width / 7, 5 * height / 6,
13 * width / 14, 2 * height / 3,
27 * width / 28, 2 * height / 5);
// Upper right path
path.cubicTo(width, height / 15,
9 * width / 14, 0,
width / 2, height / 5);
paint.setColor(Color.RED);
paint.setStyle(Style.FILL);
canvas.drawPath(path, paint);
}
}
Sorry for all the numbers but these worked best for me :) The result looks like this:
You have to find out the math behind that figures. The triangle and the star are quite easy to draw. Here is how you can draw a heart: http://www.mathematische-basteleien.de/heart.htm
To draw special paths you should create them by adding points, ellipses etc. The canvas supports a clipping mask of a specified path, so you can set the clipping mask of a heart, push the paths to the matrix, draw the content of the heart, and then pop it again.
That's what I'm doing to achieve a simulated 2D page curl effect on andriod: http://code.google.com/p/android-page-curl/
Hope this helps!
this method will return a path with the number of corners given inside a square of the given width. Add more parameters to handle small radius and such things.
private Path createStarBySize(float width, int steps) {
float halfWidth = width / 2.0F;
float bigRadius = halfWidth;
float radius = halfWidth / 2.0F;
float degreesPerStep = (float) Math.toRadians(360.0F / (float) steps);
float halfDegreesPerStep = degreesPerStep / 2.0F;
Path ret = new Path();
ret.setFillType(FillType.EVEN_ODD);
float max = (float) (2.0F* Math.PI);
ret.moveTo(width, halfWidth);
for (double step = 0; step < max; step += degreesPerStep) {
ret.lineTo((float)(halfWidth + bigRadius * Math.cos(step)), (float)(halfWidth + bigRadius * Math.sin(step)));
ret.lineTo((float)(halfWidth + radius * Math.cos(step + halfDegreesPerStep)), (float)(halfWidth + radius * Math.sin(step + halfDegreesPerStep)));
}
ret.close();
return ret;
}
If you need to draw a star inside a square, you can use the code below.
posX and posY are the coordinates for the upper left corner of the square that encloses the tips of the star (the square is not actually drawn).
size is the width and height of the square.
a is the top tip of the star. The path is created clockwise.
This is by no means a perfect solution, but it gets the job done very quickly.
public void drawStar(float posX, float posY, int size, Canvas canvas){
int hMargin = size/9;
int vMargin = size/4;
Point a = new Point((int) (posX + size/2), (int) posY);
Point b = new Point((int) (posX + size/2 + hMargin), (int) (posY + vMargin));
Point c = new Point((int) (posX + size), (int) (posY + vMargin));
Point d = new Point((int) (posX + size/2 + 2*hMargin), (int) (posY + size/2 + vMargin/2));
Point e = new Point((int) (posX + size/2 + 3*hMargin), (int) (posY + size));
Point f = new Point((int) (posX + size/2), (int) (posY + size - vMargin));
Point g = new Point((int) (posX + size/2 - 3*hMargin), (int) (posY + size));
Point h = new Point((int) (posX + size/2 - 2*hMargin), (int) (posY + size/2 + vMargin/2));
Point i = new Point((int) posX, (int) (posY + vMargin));
Point j = new Point((int) (posX + size/2 - hMargin), (int) (posY + vMargin));
Path path = new Path();
path.moveTo(a.x, a.y);
path.lineTo(b.x, b.y);
path.lineTo(c.x, c.y);
path.lineTo(d.x, d.y);
path.lineTo(e.x, e.y);
path.lineTo(f.x, f.y);
path.lineTo(f.x, f.y);
path.lineTo(g.x, g.y);
path.lineTo(h.x, h.y);
path.lineTo(i.x, i.y);
path.lineTo(j.x, j.y);
path.lineTo(a.x, a.y);
path.close();
canvas.drawPath(path, paint);
}
In addition to ellipse and rectangular you will need those two (as minimum):
drawLine(float startX, float startY, float stopX, float stopY, Paint paint)
drawLines(float[] pts, int offset, int count, Paint paint)
example:
How to draw a line in android
Documentation on Canvas: http://developer.android.com/reference/android/graphics/Canvas.html
Its very good to use instance of Shape class ))
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
HeartShape shape = new HeartShape();
ShapeDrawable shapeDrawable = new ShapeDrawable(shape);
shapeDrawable.setColorFilter(new LightingColorFilter(0, Color.BLUE));
LinearLayout linearLayout = new LinearLayout(this);
linearLayout.setLayoutParams(new LinearLayout.LayoutParams(350 * 3, 350 * 3));
linearLayout.setBackground(shapeDrawable);
setContentView(linearLayout);
}
Create a shape class which was render nice Heart
public class HeartShape extends Shape {
private final int INVALID_SIZE = -1;
private Path mPath = new Path();
private RectF mRectF = new RectF();
private float mOldWidth = INVALID_SIZE;
private float mOldHeight = INVALID_SIZE;
private float mScaleX, mScaleY;
public HeartShape() {
}
#Override
public void draw(Canvas canvas, Paint paint) {
canvas.save();
canvas.scale(mScaleX, mScaleY);
float width = mRectF.width();
float height = mRectF.height();
float halfWidth = width/2;
float halfHeight = height/2;
float stdDestX = 5 * width / 14;
float stdDestY = 2 * height / 3;
PointF point1 = new PointF(stdDestX, 0);
PointF point2 = new PointF(0, height / 15);
PointF point3 = new PointF(stdDestX / 5, stdDestY);
PointF point4 = new PointF(stdDestX, stdDestY);
// Starting point
mPath.moveTo(halfWidth, height / 5);
mPath.cubicTo(point1.x, point1.y, point2.x, point2.y, width / 28, 2 * height / 5);
mPath.cubicTo(point3.x, point3.y, point4.x, point4.y, halfWidth, height);
canvas.drawPath(mPath, paint);
canvas.scale(-mScaleX, mScaleY, halfWidth, halfHeight);
canvas.drawPath(mPath, paint);
canvas.restore();
}
#Override
protected void onResize(float width, float height) {
mOldWidth = mOldWidth == INVALID_SIZE ? width : Math.max(1, mOldWidth);
mOldHeight = mOldHeight == INVALID_SIZE ? height : Math.max(1, mOldHeight);
width = Math.max(1, width);
height = Math.max(1, height);
mScaleX = width / mOldWidth;
mScaleY = height / mOldHeight;
mOldWidth = width;
mOldHeight = height;
mRectF.set(0, 0, width, height);
}
#Override
public void getOutline(#NonNull Outline outline) {
// HeartShape not supported outlines
}
#Override
public HeartShape clone() throws CloneNotSupportedException {
HeartShape shape = (HeartShape) super.clone();
shape.mPath = new Path(mPath);
return shape;
}
}
Well if you want to draw only the shapes you specify I recommend first creat a class for each shape and make each shape implement draw() method where you can pass canvas and paint object.
For example to create multiple stars, first create a class "Star" and implement the logic there.
class Star(
var cx: Float = 0f,
var cy: Float = 0f,
var radius: Float = 0f,
var angle: Float = 0f,
var color: Int = Color.WHITE,
var numberOfSpikes: Int = 5,
var depth: Float = 0.4f
) {
val path: Path = Path()
val point: PointF = PointF()
fun init() {
path.rewind()
var totalAngle = 0f
for (i in 0 until numberOfSpikes * 2) {
val x = cx
var y = cy
y -= if (i % 2 != 0) (radius * depth)
else (radius * 1f)
StaticMethods.rotate(cx, cy, x, y, totalAngle, false, point)
totalAngle += 360f / (numberOfSpikes * 2)
if (i == 0) {
path.moveTo(point.x, point.y)
} else {
path.lineTo(point.x, point.y)
}
}
}
fun draw(canvas: Canvas, paint: Paint) {
paint.apply {
style = Paint.Style.FILL
color = this#Star.color
}
canvas.drawPath(path, paint)
}
}
object StaticMethods {
/**
* Rotate a point around a center with given angle
* #param cx rotary center point x coordinate
* #param cy rotary center point y coordinate
* #param x x coordinate of the point that will be rotated
* #param y y coordinate of the point that will be rotated
* #param angle angle of rotation in degrees
* #param anticlockWise rotate clockwise or anti-clockwise
* #param resultPoint object where the result rotational point will be stored
*/
fun rotate(cx: Float, cy: Float, x: Float, y: Float, angle: Float, anticlockWise: Boolean = false, resultPoint: PointF = PointF()): PointF {
if (angle == 0f) {
resultPoint.x = x
resultPoint.y = y
return resultPoint
}
val radians = if (anticlockWise) {
(Math.PI / 180) * angle
} else {
(Math.PI / -180) * angle
}
val cos = Math.cos(radians)
val sin = Math.sin(radians)
val nx = (cos * (x - cx)) + (sin * (y - cy)) + cx
val ny = (cos * (y - cy)) - (sin * (x - cx)) + cy
resultPoint.x = nx.toFloat()
resultPoint.y = ny.toFloat()
return resultPoint
}
/**
* Inline function that is called, when the final measurement is made and
* the view is about to be draw.
*/
inline fun View.afterMeasured(crossinline function: View.() -> Unit) {
viewTreeObserver.addOnGlobalLayoutListener(object :
ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
if (measuredWidth > 0 && measuredHeight > 0) {
viewTreeObserver.removeOnGlobalLayoutListener(this)
function()
}
}
})
}
}
And then create a custom view where you can draw your shapes, below is the code that creates 100 random stars and draws them on canvas.
class StarView : View {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
val stars: ArrayList<Helper.Star> = arrayListOf()
val paint: Paint = Paint().apply {
isAntiAlias = true
}
init {
// after the view is measured and we have the width and height
afterMeasured {
// create 100 stars
for (i in 0 until 100) {
val star = Helper.Star(
cx = width * Math.random().toFloat(),
cy = height * Math.random().toFloat(),
radius = width * 0.1f * Math.random().toFloat(),
)
star.init()
stars.add(star)
}
}
}
override fun onDraw(canvas: Canvas) {
stars.forEach {
it.draw(canvas, paint)
}
}
}
Here is the result:
If you want to create a lot of different complex shapes you can use this library. Where you can pass svg shapes using the Path Data as a string. By first creating complex shapes using any SVG vector editor such as
Microsoft Expression Design
Adobe Illustrator
Inkscape

How to use android canvas to draw a Rectangle with only topleft and topright corners round?

I found a function for rectangles with all 4 corners being round, but I want to have just the top 2 corners round. What can I do?
canvas.drawRoundRect(new RectF(0, 100, 100, 300), 6, 6, paint);
For API 21 and above the Path class added a new method addRoundRect() which you can use it like this.
corners = new float[]{
80, 80, // Top left radius in px
80, 80, // Top right radius in px
0, 0, // Bottom right radius in px
0, 0 // Bottom left radius in px
};
final Path path = new Path();
path.addRoundRect(rect, corners, Path.Direction.CW);
canvas.drawPath(path, mPaint);
in Kotlin
val corners = floatArrayOf(
80f, 80f, // Top left radius in px
80f, 80f, // Top right radius in px
0f, 0f, // Bottom right radius in px
0f, 0f // Bottom left radius in px
)
val path = Path()
path.addRoundRect(rect, corners, Path.Direction.CW)
canvas.drawPath(path, mPaint)
Use a path. It has the advantage of working for APIs less than 21 (Arc is also limited thusly, which is why I quad). Which is a problem because not everybody has Lollipop yet. You can however specify a RectF and set the values with that and use arc back to API 1, but then you wouldn't get to use a static (without declaring a new object to build the object).
Drawing a rounded rect:
path.moveTo(right, top + ry);
path.rQuadTo(0, -ry, -rx, -ry);
path.rLineTo(-(width - (2 * rx)), 0);
path.rQuadTo(-rx, 0, -rx, ry);
path.rLineTo(0, (height - (2 * ry)));
path.rQuadTo(0, ry, rx, ry);
path.rLineTo((width - (2 * rx)), 0);
path.rQuadTo(rx, 0, rx, -ry);
path.rLineTo(0, -(height - (2 * ry)));
path.close();
As a full function:
static public Path RoundedRect(float left, float top, float right, float bottom, float rx, float ry, boolean conformToOriginalPost) {
Path path = new Path();
if (rx < 0) rx = 0;
if (ry < 0) ry = 0;
float width = right - left;
float height = bottom - top;
if (rx > width/2) rx = width/2;
if (ry > height/2) ry = height/2;
float widthMinusCorners = (width - (2 * rx));
float heightMinusCorners = (height - (2 * ry));
path.moveTo(right, top + ry);
path.rQuadTo(0, -ry, -rx, -ry);//top-right corner
path.rLineTo(-widthMinusCorners, 0);
path.rQuadTo(-rx, 0, -rx, ry); //top-left corner
path.rLineTo(0, heightMinusCorners);
if (conformToOriginalPost) {
path.rLineTo(0, ry);
path.rLineTo(width, 0);
path.rLineTo(0, -ry);
}
else {
path.rQuadTo(0, ry, rx, ry);//bottom-left corner
path.rLineTo(widthMinusCorners, 0);
path.rQuadTo(rx, 0, rx, -ry); //bottom-right corner
}
path.rLineTo(0, -heightMinusCorners);
path.close();//Given close, last lineto can be removed.
return path;
}
You'd want to line all the way to those corner bits, rather than quad across them. This is what setting true to conformToOriginalPost does. Just line to the control point there.
If you want to do that all but don't care about pre-Lollipop stuff, and urgently insist that if your rx and ry are high enough, it should draw a circle.
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
static public Path RoundedRect(float left, float top, float right, float bottom, float rx, float ry, boolean conformToOriginalPost) {
Path path = new Path();
if (rx < 0) rx = 0;
if (ry < 0) ry = 0;
float width = right - left;
float height = bottom - top;
if (rx > width/2) rx = width/2;
if (ry > height/2) ry = height/2;
float widthMinusCorners = (width - (2 * rx));
float heightMinusCorners = (height - (2 * ry));
path.moveTo(right, top + ry);
path.arcTo(right - 2*rx, top, right, top + 2*ry, 0, -90, false); //top-right-corner
path.rLineTo(-widthMinusCorners, 0);
path.arcTo(left, top, left + 2*rx, top + 2*ry, 270, -90, false);//top-left corner.
path.rLineTo(0, heightMinusCorners);
if (conformToOriginalPost) {
path.rLineTo(0, ry);
path.rLineTo(width, 0);
path.rLineTo(0, -ry);
}
else {
path.arcTo(left, bottom - 2 * ry, left + 2 * rx, bottom, 180, -90, false); //bottom-left corner
path.rLineTo(widthMinusCorners, 0);
path.arcTo(right - 2 * rx, bottom - 2 * ry, right, bottom, 90, -90, false); //bottom-right corner
}
path.rLineTo(0, -heightMinusCorners);
path.close();//Given close, last lineto can be removed.
return path;
}
So,
conformToOriginalPost actually draws a rounded rect without the bottom two bits rounded.
I would draw two rectangles:
canvas.drawRect(new RectF(0, 110, 100, 290), paint);
canvas.drawRoundRect(new RectF(0, 100, 100, 200), 6, 6, paint);
Or something like that, you just overlap them so that the upper corners will be round. Preferably you should write a method for this
I changed this answer so you can set which corner you want to be round and which one you want to be sharp. also works on pre-lolipop
Usage Example:
only top-right and botton-right corners are rounded
Path path = RoundedRect(0, 0, fwidth , fheight , 5,5,
false, true, true, false);
canvas.drawPath(path,myPaint);
RoundRect:
public static Path RoundedRect(
float left, float top, float right, float bottom, float rx, float ry,
boolean tl, boolean tr, boolean br, boolean bl
){
Path path = new Path();
if (rx < 0) rx = 0;
if (ry < 0) ry = 0;
float width = right - left;
float height = bottom - top;
if (rx > width / 2) rx = width / 2;
if (ry > height / 2) ry = height / 2;
float widthMinusCorners = (width - (2 * rx));
float heightMinusCorners = (height - (2 * ry));
path.moveTo(right, top + ry);
if (tr)
path.rQuadTo(0, -ry, -rx, -ry);//top-right corner
else{
path.rLineTo(0, -ry);
path.rLineTo(-rx,0);
}
path.rLineTo(-widthMinusCorners, 0);
if (tl)
path.rQuadTo(-rx, 0, -rx, ry); //top-left corner
else{
path.rLineTo(-rx, 0);
path.rLineTo(0,ry);
}
path.rLineTo(0, heightMinusCorners);
if (bl)
path.rQuadTo(0, ry, rx, ry);//bottom-left corner
else{
path.rLineTo(0, ry);
path.rLineTo(rx,0);
}
path.rLineTo(widthMinusCorners, 0);
if (br)
path.rQuadTo(rx, 0, rx, -ry); //bottom-right corner
else{
path.rLineTo(rx,0);
path.rLineTo(0, -ry);
}
path.rLineTo(0, -heightMinusCorners);
path.close();//Given close, last lineto can be removed.
return path;
}
you can easily achieve this by using Path:
val radiusArr = floatArrayOf(
15f, 15f,
15f, 15f,
0f, 0f,
0f, 0f
)
val myPath = Path()
myPath.addRoundRect(
RectF(0f, 0f, 400f, 400f),
radiusArr,
Path.Direction.CW
)
canvas.drawPath(myPath, paint)
Simple helper function written in Kotlin.
private fun Canvas.drawTopRoundRect(rect: RectF, paint: Paint, radius: Float) {
// Step 1. Draw rect with rounded corners.
drawRoundRect(rect, radius, radius, paint)
// Step 2. Draw simple rect with reduced height,
// so it wont cover top rounded corners.
drawRect(
rect.left,
rect.top + radius,
rect.right,
rect.bottom,
paint
)
}
Usage:
canvas.drawTopRoundRect(rect, paint, radius)
public static Path composeRoundedRectPath(RectF rect, float topLeftDiameter, float topRightDiameter,float bottomRightDiameter, float bottomLeftDiameter){
Path path = new Path();
topLeftDiameter = topLeftDiameter < 0 ? 0 : topLeftDiameter;
topRightDiameter = topRightDiameter < 0 ? 0 : topRightDiameter;
bottomLeftDiameter = bottomLeftDiameter < 0 ? 0 : bottomLeftDiameter;
bottomRightDiameter = bottomRightDiameter < 0 ? 0 : bottomRightDiameter;
path.moveTo(rect.left + topLeftDiameter/2 ,rect.top);
path.lineTo(rect.right - topRightDiameter/2,rect.top);
path.quadTo(rect.right, rect.top, rect.right, rect.top + topRightDiameter/2);
path.lineTo(rect.right ,rect.bottom - bottomRightDiameter/2);
path.quadTo(rect.right ,rect.bottom, rect.right - bottomRightDiameter/2, rect.bottom);
path.lineTo(rect.left + bottomLeftDiameter/2,rect.bottom);
path.quadTo(rect.left,rect.bottom,rect.left, rect.bottom - bottomLeftDiameter/2);
path.lineTo(rect.left,rect.top + topLeftDiameter/2);
path.quadTo(rect.left,rect.top, rect.left + topLeftDiameter/2, rect.top);
path.close();
return path;
}
I achieved this by following the below steps.
These are the pre-requisites for the rounded rectangle to look neat
The radius of the edges have to be equal to the (height of the rectangle / 2). This is because if its any different value then the place where the curve meets straight line of the rectangle will not be
Next is the steps to draw the rounded rectangle.
First we draw 2 circles on the left and right side, with the radius = height of rectange / 2
Then we draw a rectangle between these circles to get the desired rounded rectangle.
I am posting the code below
private void drawRoundedRect(Canvas canvas, float left, float top, float right, float bottom) {
float radius = getHeight() / 2;
canvas.drawCircle(radius, radius, radius, mainPaint);
canvas.drawCircle(right - radius, radius, radius, mainPaint);
canvas.drawRect(left + radius, top, right - radius, bottom, mainPaint);
}
Now this results in a really nice rounded rectangle like the one shown below
This is an old question, however I wanted to add my solution because it uses the native SDK without lots of custom code or hacky drawing. This solution is supported back to API 1.
The way to do this properly is to create a path (as mentioned in other answers) however the previous answers seem to overlook the addRoundedRect function call that takes radii for each corner.
Variables
private val path = Path()
private val paint = Paint()
Setup Paint
paint.color = Color.RED
paint.style = Paint.Style.FILL
Update Path with Size Changes
Call this somewhere that isn't onDraw, such as onMeasure for a view or onBoundChange for a drawable. If it doesn't change (like this example) you could put this code where you set up your paint.
val radii = floatArrayOf(
25f, 25f, //Top left corner
25f, 25f, //Top right corner
0f, 0f, //Bottom right corner
0f, 0f, //Bottom left corner
)
path.reset() //Clears the previously set path
path.addRoundedRect(0f, 0f, 100f, 100f, radii, Path.Direction.CW)
This code creates a 100x100 rounded rect with the top corners rounded with a 25 radius.
Draw Path
Call this in onDraw for a view or draw for a drawable.
canvas.drawPath(path, paint)
A Path#arcTo() version to draw the rounded side if the radius is half of the height.
fun getPathOfRoundedRectF(
rect: RectF,
topLeftRadius: Float = 0f,
topRightRadius: Float = 0f,
bottomRightRadius: Float = 0f,
bottomLeftRadius: Float = 0f
): Path {
val tlRadius = topLeftRadius.coerceAtLeast(0f)
val trRadius = topRightRadius.coerceAtLeast(0f)
val brRadius = bottomRightRadius.coerceAtLeast(0f)
val blRadius = bottomLeftRadius.coerceAtLeast(0f)
with(Path()) {
moveTo(rect.left + tlRadius, rect.top)
//setup top border
lineTo(rect.right - trRadius, rect.top)
//setup top-right corner
arcTo(
RectF(
rect.right - trRadius * 2f,
rect.top,
rect.right,
rect.top + trRadius * 2f
), -90f, 90f
)
//setup right border
lineTo(rect.right, rect.bottom - trRadius)
//setup bottom-right corner
arcTo(
RectF(
rect.right - brRadius * 2f,
rect.bottom - brRadius * 2f,
rect.right,
rect.bottom
), 0f, 90f
)
//setup bottom border
lineTo(rect.left + blRadius, rect.bottom)
//setup bottom-left corner
arcTo(
RectF(
rect.left,
rect.bottom - blRadius * 2f,
rect.left + blRadius * 2f,
rect.bottom
), 90f, 90f
)
//setup left border
lineTo(rect.left, rect.top + tlRadius)
//setup top-left corner
arcTo(
RectF(
rect.left,
rect.top,
rect.left + tlRadius * 2f,
rect.top + tlRadius * 2f
),
180f,
90f
)
close()
return this
}
}
One simple and efficient way to draw a solid side is to use clipping - rect clipping is essentially free, and a lot less code to write than a custom Path.
If I want a 300x300 rect, with the top left and right rounded by 50 pixels, you can do:
canvas.save();
canvas.clipRect(0, 0, 300, 300);
canvas.drawRoundRect(new RectF(0, 0, 300, 350), 50, 50, paint);
canvas.restore();
This approach will only work for rounding on 2 or 3 adjacent corners, so it's a little less configurable than a Path based approach, but using round rects is more efficient, since drawRoundRect() is fully hardware accelerated (that is, tessellated into triangles) while drawPath() always falls back to software rendering (software-draw a path bitmap, and upload that to be cached on the GPU).
Not a huge performance issue for small infrequent drawing, but if you're animating paths, the cost of software draw can make your frame times longer, and increase your chance to drop frames. The path mask also costs memory.
If you do want to go with a Path-based approach, I'd recommend using GradientDrawable to simplify the lines of code (assuming you don't need to set a custom shader, e.g. to draw a Bitmap).
mGradient.setBounds(0, 0, 300, 300);
mGradient.setCornerRadii(new int[] {50,50, 50,50, 0,0, 0,0});
With GradientDrawable#setCornerRadii(), you can set any corner to be any roundedness, and reasonably animate between states.
Here is my answer to the above question. Here, I have created Kotlin extension function which uses Path along with the quadTo function which can be used in lower-level APIs also.
fun Canvas.drawRoundRectPath(
rectF: RectF,
radius: Float,
roundTopLeft: Boolean,
roundTopRight: Boolean,
roundBottomLeft: Boolean,
roundBottomRight: Boolean,
paint: Paint) {
val path = Path()
//Move path cursor to start point
if (roundBottomLeft) {
path.moveTo(rectF.left, rectF.bottom - radius)
} else {
path.moveTo(rectF.left, rectF.bottom)
}
// drawing line and rounding top left curve
if (roundTopLeft) {
path.lineTo(rectF.left, rectF.top + radius)
path.quadTo(rectF.left, rectF.top, rectF.left + radius, rectF.top)
} else {
path.lineTo(rectF.left, rectF.top)
}
// drawing line an rounding top right curve
if (roundTopRight) {
path.lineTo(rectF.right - radius, rectF.top)
path.quadTo(rectF.right, rectF.top, rectF.right, rectF.top + radius)
} else {
path.lineTo(rectF.right, rectF.top)
}
// drawing line an rounding bottom right curve
if (roundBottomRight) {
path.lineTo(rectF.right, rectF.bottom - radius)
path.quadTo(rectF.right, rectF.bottom, rectF.right - radius, rectF.bottom)
} else {
path.lineTo(rectF.right, rectF.bottom)
}
// drawing line an rounding bottom left curve
if (roundBottomLeft) {
path.lineTo(rectF.left + radius, rectF.bottom)
path.quadTo(rectF.left, rectF.bottom, rectF.left, rectF.bottom - radius)
} else {
path.lineTo(rectF.left, rectF.bottom)
}
path.close()
drawPath(path, paint)
}
We can call the function with canvas object and pass the RectF with the dimension on which we want to apply the curve.
Also, we can pass the boolean for the corners which we want to round.
This answer can further be customized to accept radius for individual corners.
You can draw that piece by piece using drawLine() and drawArc() functions from the Canvas.
Maybe the following code can help you
Paint p = new Paint();
p.setColor(color);
float[] corners = new float[]{
15, 15, // Top, left in px
15, 15, // Top, right in px
15, 15, // Bottom, right in px
15, 15 // Bottom,left in px
};
final Path path = new Path();
path.addRoundRect(rect, corners, Path.Direction.CW);
// Draw
canvas.drawPath(path, p);
Use PaintDrawable could be better:
val topLeftRadius = 10
val topRightRadius = 10
val bottomLeftRadius = 0
val bottomRightRadius = 0
val rect = Rect(0, 0, 100, 100)
val paintDrawable = PaintDrawable(Color.RED)
val outter = floatArrayOf(topLeftRadius, topLeftRadius, topRightRadius, topRightRadius,
bottomLeftRadius, bottomLeftRadius, bottomRightRadius, bottomRightRadius)
paintDrawable.setCornerRadii(outter)
paintDrawable.bounds = rect
paintDrawable.draw(canvas)
draw round rect with left rounded corners
private void drawRoundRect(float left, float top, float right, float bottom, Paint paint, Canvas canvas) {
Path path = new Path();
path.moveTo(left, top);
path.lineTo(right, top);
path.lineTo(right, bottom);
path.lineTo(left + radius, bottom);
path.quadTo(left, bottom, left, bottom - radius);
path.lineTo(left, top + radius);
path.quadTo(left, top, left + radius, top);
canvas.drawPath(path, onlinePaint);
}

Categories

Resources