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