Android中绘制简单几何图形和路径Path


图片

背景

我的博客:http://zhangsunyucong.top

马上就到2018年过年了,然后我又刚好有兴致,就来玩玩Android中的简单几何图形的绘制和使用Path类来绘制路径。

Path和Canvas

在Android中,和我们平时画图一样是有画笔和画布的,Path是画笔,Canvas是画布。与画的样式属性有关,如大小或者颜色等,是由Path来完成的; 与画的形状,即画什么东西是由Canva完成的。关于这两个类的各个属性和方法的具体使用,可以浏览爱哥的博客几篇文章。在这里,只是用它们简单的几个函数画一些简单的图形,最后还会给出一个综合一点的demo,主要是为了加强认识绘制时坐标关系。

先贴上我的代码:
布局文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:id="@+id/root_draw_view"
    android:gravity="center_horizontal"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="有点意思"/>

    <com.example.hyj.ht_test.widget.draw.MyDrawView
        android:layout_width="300dp"
        android:layout_height="300dp" />

</LinearLayout>

MyDrawView.java

public class MyDrawView extends View {

    private Paint mPointPaint;
    private float[] mFPts;
    private RectF mRectF;
    private RectF mRectOvalF;
    private RectF mRightBottomRectF;
    private Path mPath;
    private Path mPath1;

    public MyDrawView(Context context) {
        super(context);
        init();
    }

    public MyDrawView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public MyDrawView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private int mPointStrokeWidth;

    private void init() {
        mPointStrokeWidth = 20;
        mPointPaint = new Paint();
        mPointPaint.setColor(Color.RED);
        mPointPaint.setStrokeWidth(mPointStrokeWidth);
        mPointPaint.setStyle(Paint.Style.FILL);

        mPath = new Path();
        mPath1 = new Path();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);

        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        int widthResult = 100;
        int heightResult = 100;

        if(widthMode != MeasureSpec.AT_MOST) {
            widthResult = widthSize;
        }

        if(heightMode != MeasureSpec.AT_MOST) {
            heightResult = heightSize;
        }

        int resultSize = widthResult > heightResult
                ? heightResult : widthResult;

        setMeasuredDimension(resultSize, resultSize);
    }


    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mFPts = new float[] {
                0, 0,
                getMeasuredWidth() / 2, 0,
                getMeasuredWidth(), 0,
                getMeasuredWidth(), getMeasuredHeight() / 2,
                getMeasuredWidth(), getMeasuredHeight(),
                getMeasuredWidth() / 2, getMeasuredHeight(),
                0, getMeasuredHeight(),
                0, getMeasuredHeight() /2,
                getMeasuredHeight() / 2, getMeasuredHeight() /2
        };
        mRectF = new RectF(0, 0, getMeasuredWidth(), getMeasuredHeight());
        mRectOvalF = new RectF(mPointStrokeWidth, mPointStrokeWidth,
                getMeasuredWidth() - mPointStrokeWidth, getMeasuredHeight() / 2);
        mRightBottomRectF = new RectF(getMeasuredWidth() / 2, getMeasuredHeight() /2,
                getMeasuredWidth() - mPointStrokeWidth, getMeasuredHeight() - mPointStrokeWidth);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        canvas.drawLine(mFPts[0], mFPts[1], mFPts[2], mFPts[2], mPointPaint);
        mPointPaint.setColor(Color.BLUE);
        canvas.drawLines(mFPts, mPointPaint);

    }
}

MyDrawView中没有考虑padding的影响。

画点

几何图形中,最简单的就是点了,首先画点。

drawPoint(float x, float y, Paint paint)
drawPoints(float[] pts, Paint paint)
drawPoints(float[] pts, int offset, int count, Paint paint)

x是点的横坐标,y是点的纵坐标。坐标的点也可以放到数组pts中,可见数组的个数一般是偶数个,offset是开始绘制前,数组中忽略的元素个数。count是忽略了offset个点后,开始取count个元素来绘制点。

canvas.drawPoints(mFPts, mPointPaint);
mPointPaint.setColor(Color.BLUE);
canvas.drawPoint(getMeasuredWidth() / 2, 0, mPointPaint);

图片

画线

由点组成线,两点确定一条直线。

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

第一个是,直接指定直线的两个点坐标。pts是点的坐标,每两个数组元素确定一个点坐标,每四个元素确定直线的两个点的坐标。

canvas.drawLine(mFPts[0], mFPts[1], mFPts[2], mFPts[2], mPointPaint);
mPointPaint.setColor(Color.BLUE);
canvas.drawLines(mFPts, mPointPaint);

图片

画矩形

由线可以组成面。矩形可以是长方形,也可以是正方形。

RectF和Rect的区别是参数的类型不同,RectF的参数类型是float,Rect的参数类型是int。

drawRect(float left, float top, float right, float bottom, Paint paint)
drawRect(float left, float top, float right, float bottom, Paint paint)
drawRect(Rect r, Paint paint)
drawRect( RectF rect, Paint paint)

也就是,可以在RectF或者Rect中指定好顶点坐标再传给drawRect,也可以在drawRect方法中直接指定顶点坐标。

mRectF = new RectF(0, 0, getMeasuredWidth(), getMeasuredHeight());
canvas.drawRect(mRectF, mPointPaint);

代码说明,第一行代码是在onSizeChanged重写方法中的,第二行代码是在onDraw方法中的。因为onDraw方法是会不断被调用的,不适合在里面创建对象。

图片

圆角矩形

圆角矩形是在矩形的基础上生成的。

drawRoundRect(RectF rect, float rx, float ry, Paint paint)
drawRoundRect(float left, float top, float right, float bottom, float rx, float ry, Paint paint)

rx是生成圆角的椭圆的X轴半径
ry是生成圆角的椭圆的Y轴半径

canvas.drawRoundRect(mRectF, getMeasuredWidth() / 4, getMeasuredHeight() / 4, mPointPaint);

图片

画圆

圆要指定圆心的坐标和半径的大小。

drawCircle(float cx, float cy, float radius, Paint paint)

cx和cy分别是圆心的横坐标和纵坐标,radius为半径。

canvas.drawCircle(getMeasuredWidth() / 2, getMeasuredHeight() / 2,
                getMeasuredHeight() / 2 - mPointStrokeWidth, mPointPaint);

图片

画椭圆

椭圆是在矩形基础上生成的,以矩形的长为长轴,矩形的宽为短轴。特殊的,当长轴等于短轴时,椭圆就是圆。

drawOval(RectF oval, @NonNull Paint paint)
drawOval(float left, float top, float right, float bottom, Paint paint)

mRectOvalF = new RectF(mPointStrokeWidth, mPointStrokeWidth,
                getMeasuredWidth() - mPointStrokeWidth, getMeasuredHeight() / 2);

canvas.drawOval(mRectOvalF, mPointPaint);

图片

画弧

弧是在椭圆上按一定角度截取的一部分。

drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint)
drawArc(float left, float top, float right, float bottom, float startAngle,
float sweepAngle, boolean useCenter, Paint paint)

oval是椭圆基于的矩形顶点的矩阵,或者在方法中直接指定四个顶点,startAngle是截取的起始角度,sweepAngle是弧持续的角度,useCenter是否显示长短半径。

canvas.drawArc(mRectOvalF, 0, 90, true, mPointPaint);

图片

canvas.drawArc(mRectOvalF, 0, 90, false, mPointPaint);

图片

Path

在View的绘制过程中,有一个类叫做Path,Path可以帮助我们实现很多自定义形状的路径,特别是配合xfermode属性来使用的时候,可以实现很多效果。

moveTo

路径开始绘制的点叫起始点坐标,默认是(0,0)。可以使用moveTo将绘制路径的起始点移动到某个位置。moveTo不进行绘制,一般用来移动画笔。

lineTo

lineTo用来绘制一条直线路径。

mPath.moveTo(getMeasuredWidth()/ 2, getMeasuredHeight() / 2);
mPath.lineTo(getMeasuredWidth(), getMeasuredHeight());
canvas.drawPath(mPath, mPointPaint);

直线路径的起始点是(getMeasuredWidth()/ 2, getMeasuredHeight() / 2),终点是(getMeasuredWidth(), getMeasuredHeight())

图片

quadTo

quadTo用来画由一个控制点控制的贝塞尔曲线。

mPath.moveTo(mPointStrokeWidth, getMeasuredHeight() / 2);
mPath.quadTo(0, 0, getMeasuredWidth() / 2, mPointStrokeWidth);
canvas.drawPath(mPath, mPointPaint);

起始点是(mPointStrokeWidth, getMeasuredHeight() / 2),控制点是(0, 0),终点是(getMeasuredWidth() / 2, mPointStrokeWidth)

图片

cubicTo

cubicTo用来画由两个控制点控制的贝塞尔曲线。

mPath.moveTo(mPointStrokeWidth, getMeasuredHeight() / 2);
mPath.cubicTo(0, 0, getMeasuredWidth() / 2, mPointStrokeWidth,
         getMeasuredWidth(), getMeasuredHeight() / 2);
canvas.drawPath(mPath, mPointPaint);

起始点是(mPointStrokeWidth, getMeasuredHeight() / 2),两个控制点是(0, 0)和(getMeasuredWidth() / 2, mPointStrokeWidth),终点是(getMeasuredWidth(), getMeasuredHeight() / 2)。

图片

arcTo

arcTo用来画一条圆弧路径。与前面画圆弧一样的,圆弧是截取椭圆的一部分,而椭圆是基于矩形的。

mRectOvalF = new RectF(mPointStrokeWidth, mPointStrokeWidth,
    getMeasuredWidth() - mPointStrokeWidth, getMeasuredHeight() / 2);
mPath.arcTo(mRectOvalF, 0, 90, false);
canvas.drawPath(mPath, mPointPaint);

和刚开始的圆弧参数定义一样,指定基于的矩形的四个顶点,startAngle截取的起始角度,sweepAngle弧持续的角度,useCenter是否显示长短半径。

图片

Path的addArc、addRoundRect、addOval、addRect、addCircle

它们实现的几何路径,可以自己尝试一下。

Path.Op

在开头,mPointPaint首先设置画笔的样式为描边STROKE,后面为了更好看出Path.Op的效果会改为FILL填充。

mRightBottomRectF = new RectF(getMeasuredWidth() / 2, getMeasuredHeight() /2, getMeasuredWidth() - mPointStrokeWidth, getMeasuredHeight() - mPointStrokeWidth);

mPath.addCircle(getMeasuredWidth() / 2, getMeasuredHeight() / 2,
    getMeasuredHeight() / 2 - mPointStrokeWidth, Path.Direction.CCW);
canvas.drawPath(mPath, mPointPaint);
mPath1.addRect(mRightBottomRectF, Path.Direction.CCW);
canvas.drawPath(mPath1, mPointPaint);

图片

Path.Op.DIFFERENCE
mPath.addCircle(getMeasuredWidth() / 2, getMeasuredHeight() / 2,
                getMeasuredHeight() / 2 - mPointStrokeWidth,
                Path.Direction.CCW);
mPath1.addRect(mRightBottomRectF, Path.Direction.CCW);
mPath.op(mPath1, Path.Op.DIFFERENCE);
canvas.drawPath(mPath, mPointPaint);

图片

Path.Op.INTERSECT
mPath.addCircle(getMeasuredWidth() / 2, getMeasuredHeight() / 2,
                getMeasuredHeight() / 2 - mPointStrokeWidth,
                Path.Direction.CCW);
mPath1.addRect(mRightBottomRectF, Path.Direction.CCW);
mPath.op(mPath1, Path.Op.INTERSECT);
canvas.drawPath(mPath, mPointPaint);

图片

Path.Op.REVERSE_DIFFERENCE
mPath.addCircle(getMeasuredWidth() / 2, getMeasuredHeight() / 2,
                getMeasuredHeight() / 2 - mPointStrokeWidth,
                Path.Direction.CCW);
mPath1.addRect(mRightBottomRectF, Path.Direction.CCW);
mPath.op(mPath1, Path.Op.REVERSE_DIFFERENCE);
canvas.drawPath(mPath, mPointPaint);

图片

Path.Op.XOR
mPath.addCircle(getMeasuredWidth() / 2, getMeasuredHeight() / 2,
                getMeasuredHeight() / 2 - mPointStrokeWidth,
                Path.Direction.CCW);
mPath1.addRect(mRightBottomRectF, Path.Direction.CCW);
mPath.op(mPath1, Path.Op.XOR);
canvas.drawPath(mPath, mPointPaint);

图片

最后,例子

(一)例子一

public class MyDrawView extends View {

    private Paint mGraphPaint;
    private Paint mPointPaint;
    private Paint mRectPaint;
    private RectF mRectF;
    private Paint mLinesPaint;
    private float mFPts[];
    private float mFLinePts[];
    private Path mPath;

    public MyDrawView(Context context) {
        super(context);
        init();
    }

    public MyDrawView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public MyDrawView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private int mPointStrokeWidth;

    private void init() {
        mGraphPaint = new Paint();
        mGraphPaint.setColor(Color.GREEN);
        mGraphPaint.setStrokeWidth(5);
        mGraphPaint.setStyle(Paint.Style.STROKE);
        mGraphPaint.setShadowLayer(50, 30,30, Color.BLUE);

        mPointStrokeWidth = 20;
        mPointPaint = new Paint();
        mPointPaint.setColor(Color.RED);
        mPointPaint.setStrokeWidth(mPointStrokeWidth);
        mPointPaint.setStyle(Paint.Style.FILL);

        mRectPaint = new Paint();
        mRectPaint.setColor(Color.BLACK);
        mRectPaint.setStrokeWidth(5);
        mRectPaint.setStyle(Paint.Style.STROKE);

        mLinesPaint = new Paint();
        mLinesPaint.setColor(Color.GRAY);
        mLinesPaint.setStrokeWidth(5);
        mLinesPaint.setStyle(Paint.Style.STROKE);

        mPath = new Path();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);

        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        int widthResult = 100;
        int heightResult = 100;

        if(widthMode != MeasureSpec.AT_MOST) {
            widthResult = widthSize;
        }

        if(heightMode != MeasureSpec.AT_MOST) {
            heightResult = heightSize;
        }

        int resultSize = widthResult > heightResult
                ? heightResult : widthResult;

        setMeasuredDimension(resultSize, resultSize);
    }

    private int num = 30;

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mRectF = new RectF(
                num - mPointStrokeWidth / 2,
                num - mPointStrokeWidth / 2,
                getMeasuredWidth() - num +  mPointStrokeWidth / 2,
                getMeasuredHeight() - num + mPointStrokeWidth / 2);

        mFPts = new float[] {
                getMeasuredWidth() / 2, num,
                getMeasuredWidth() - num, getMeasuredHeight()/ 2,
                getMeasuredWidth() / 2 ,getMeasuredHeight() - num,
                num, getMeasuredHeight()/ 2
        };
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        canvas.drawCircle( getMeasuredHeight() / 2,
                getMeasuredHeight() / 2,
                getMeasuredHeight() / 2 - num,
                mGraphPaint);

        canvas.drawRect(mRectF, mRectPaint);

        canvas.drawPoints(mFPts, mPointPaint);

        canvas.drawLines(mFPts, mLinesPaint);

        mPath.moveTo(mFPts[0], mFPts[1]);
        mPath.lineTo(mFPts[2], mFPts[3]);
        mPath.lineTo(mFPts[4], mFPts[5]);
        mPath.lineTo(mFPts[6], mFPts[7]);
        mPath.close();
        canvas.drawPath(mPath, mLinesPaint);

        canvas.drawLine(mFPts[0], mFPts[1], mFPts[4], mFPts[5], mLinesPaint);
        canvas.drawLine(mFPts[2], mFPts[3], mFPts[6], mFPts[7], mLinesPaint);

        mPath.moveTo(mFPts[6], mFPts[7]);
        mPath.quadTo(mFPts[0], mFPts[1], mFPts[2], mFPts[3]);
        canvas.drawPath(mPath, mLinesPaint);

        mPath.moveTo(num - mPointStrokeWidth / 2,
                getMeasuredHeight() - num + mPointStrokeWidth / 2);
        mPath.cubicTo(mFPts[4], mFPts[5],
                getMeasuredWidth() - num +  mPointStrokeWidth / 2, getMeasuredHeight() - num +
                        mPointStrokeWidth / 2,
                mFPts[2], mFPts[3]);
        canvas.drawPath(mPath, mLinesPaint);

    }
}

效果图:

图片

(二)例子二

例子引用自这里

public class MyDrawView extends View {

    private Paint mPaint;

    private int mOffsetX;
    private int mOffsetY;

    public MyDrawView(Context context) {
        super(context);
        init();
    }

    public MyDrawView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public MyDrawView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }


    private void init() {
        mPaint = new Paint();
        mPaint.setColor(Color.RED);
        mPaint.setStrokeWidth(5);
        mPaint.setStyle(Paint.Style.STROKE);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);

        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        int widthResult = 100;
        int heightResult = 100;

        if(widthMode != MeasureSpec.AT_MOST) {
            widthResult = widthSize;
        }

        if(heightMode != MeasureSpec.AT_MOST) {
            heightResult = heightSize;
        }

        int resultSize = widthResult > heightResult
                ? heightResult : widthResult;

        setMeasuredDimension(resultSize, resultSize);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mOffsetX = w / 2;
        mOffsetY = h / 2 - 55;
    }

    private Point getHeartPoint(float angle) {
        float t = (float) (angle / Math.PI);
        float x = (float) (19.5 * (16 * Math.pow(Math.sin(t), 3)));
        float y = (float) (-20 * (13 * Math.cos(t) - 5 * Math.cos(2 * t) - 2 * Math.cos(3 * t) - Math.cos(4 * t)));
        return new Point(mOffsetX + (int) x, mOffsetY + (int) y);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        float angle = 10;
        while (angle < 180) {
            Point p = getHeartPoint(angle);
            canvas.drawPoint(p.x, p.y, mPaint);
            angle = angle + 0.02f;
        }

    }
}

图片

关于画笔和画布的使用,到这里是未完的,其他的效果,以后有时间再补充。谢谢大家的观看。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值