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

Viewed 38229

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?

8 Answers

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

}

enter image description here

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.

enter image description here

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:

enter image description here


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

Related