背景
我的博客: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;
}
}
}
关于画笔和画布的使用,到这里是未完的,其他的效果,以后有时间再补充。谢谢大家的观看。