Android, как нарисовать треугольник, звезда, квадрат, сердце на холсте

Я могу нарисовать круг и прямоугольник на холсте, используя path.addCircle() и path.addRect(). Как и я хочу на ощупь, я могу нарисовать треугольник, звезду, квадрат и сердце. Как я могу это сделать? Дайте мне пример с примерами. Спасибо

8 ответов

Решение

Вы должны выяснить математику за этими цифрами. Треугольник и звезду довольно легко нарисовать. Вот как вы можете нарисовать сердце: http://www.mathematische-basteleien.de/heart.htm

Чтобы рисовать специальные контуры, вы должны создавать их, добавляя точки, эллипсы и т. Д. Холст поддерживает обтравочную маску указанного пути, поэтому вы можете установить обтравочную маску сердца, выдвинуть пути к матрице, нарисовать содержимое сердца, а затем вставьте его снова.

Вот что я делаю, чтобы добиться эффекта имитации скручивания 2D-страницы на andriod: http://code.google.com/p/android-page-curl/

Надеюсь это поможет!

Для будущих ищущих прямого ответа я нарисовал почти симметричную звезду, используя холст, как показано на рисунке:

Звездное изображение

Основным инструментом является использование путей.

Предполагая, что у вас есть настройки:

Paint paint = new Paint();
paint.setColor(Color.WHITE);
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.STROKE);

Path path = new Path();

Тогда у вас в onDraw вы можете использовать путь, как я делаю ниже. Он будет правильно масштабироваться под любые размеры холста.

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

}

Для всех, кому нужна форма сердца:

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

            }
    }

Извините за все числа, но они работали лучше всего для меня:) Результат выглядит так:

Этот метод вернет путь с количеством углов, заданных внутри квадрата заданной ширины. Добавьте больше параметров для обработки небольшого радиуса и тому подобного.

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

Если вам нужно нарисовать звезду внутри квадрата, вы можете использовать код ниже.

posX а также posY это координаты верхнего левого угла квадрата, который охватывает кончики звезды (квадрат на самом деле не нарисован).

size это ширина и высота квадрата.

a верхний конец звезды. Путь создается по часовой стрелке.

Это ни в коем случае не идеальное решение, но оно выполняет работу очень быстро.

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

В дополнение к эллипсу и прямоугольнику вам понадобятся эти два (как минимум):

DrawLine (float startX, float startY, float stopX, float stopY, Paint paint)
drawLines (float [] pts, int offset, int count, Paint paint)

пример: как нарисовать линию в Android

Документация по Canvas: http://developer.android.com/reference/android/graphics/Canvas.html

Что ж, если вы хотите рисовать только указанные вами формы, я рекомендую сначала создать класс для каждой формы и заставить каждую форму реализовывать метод draw(), в котором вы можете передавать холст и рисовать объект.

Например, чтобы создать несколько звезд, сначала создайте класс «Звезда» и реализуйте там логику.

      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()
                }
            }
        })
     }
}

А затем создайте настраиваемый вид, в котором вы можете рисовать свои фигуры, ниже приведен код, который создает 100 случайных звезд и рисует их на холсте.

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

Вот результат:


Если вы хотите создать множество различных сложных фигур, вы можете использовать эту библиотеку . Где вы можете передавать формы svg, используя данные пути в виде строки. Сначала создавая сложные формы с помощью любого векторного редактора SVG, например

  • Дизайн Microsoft Expression
  • Adobe Illustrator
  • Inkscape

Очень хорошо использовать экземпляр класса Shape))

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

Создайте класс формы, который был отрендерен

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

}

Другие вопросы по тегам