背景
马上就到2018年过年了,然后我又刚好有兴致,就来玩玩Android中的简单几何图形的绘制和使用Path类来绘制路径。
Path和Canvas
在Android中,和我们平时画图一样是有画笔和画布的,Path是画笔,Canvas是画布。与画的样式属性有关,如大小或者颜色等,是由Path来完成的;与画的形状,即画什么东西是由Canva完成的。关于这两个类的各个属性和方法的具体使用,可以浏览几篇文章。在这里,只是用它们简单的几个函数画一些简单的图形,最后还会给出一个综合一点的demo,主要是为了加强认识绘制时坐标关系。
先贴上我的代码: 布局文件:
复制代码
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; } }}复制代码
关于画笔和画布的使用,到这里是未完的,其他的效果,以后有时间再补充。谢谢大家的观看。