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

8

13

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?

Zina answered 10/8, 2011 at 7:46 Comment(0)
C
12

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!

Centralism answered 10/8, 2011 at 7:51 Comment(0)
C
32

For future direct answer seekers, I have drawn an almost symmetric star using canvas, as shown in the image:

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

}
Closemouthed answered 3/2, 2013 at 19:54 Comment(1)
Good Answer! Thanks! I think path.lineTo(mid + half * 0.5f, half * 0.84f); in the end (before path close) not needed as path.close() will handle that automatically!Allynallys
A
14

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:

enter image description here

Abecedarium answered 20/12, 2016 at 21:52 Comment(0)
C
12

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!

Centralism answered 10/8, 2011 at 7:51 Comment(0)
B
11

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;
}
Bordy answered 26/8, 2013 at 9:13 Comment(1)
Answered to my own question, to set the star pointing north: 1. Set FillType.WINDING. 2. Set max to (2 * PI - PI / 2). 3. Set ret.moveTo(halfWidth, 0).Dissonancy
D
2

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);
}
Dupre answered 21/10, 2016 at 13:29 Comment(0)
E
1

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

Enounce answered 10/8, 2011 at 7:56 Comment(1)
You need to divide complex shapes to primitive ones. For example you can draw heart using two half circles and two lines. Triangle - select 3 points and connect them with drawLine() or even better pass array pf points to drawLines();Enounce
A
0

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

Aleksandropol answered 7/10, 2017 at 22:2 Comment(0)
P
0

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 = [email protected]
            }
            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

Poulson answered 27/10, 2021 at 13:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.