友情链接:
Android自定义View【实战教程】3⃣️—-Paint类、Path类以及PathEffect类详解
神马是Canvas
基本概念
Canvas:可以理解为是一个为我们提供了各种工具的画布,我们可以在上面尽情的绘制(旋转,平移,缩放等等)。可以理解为系统分配给我们一个一个内存空间,然后提供了一些对这个内存空间操作的方法(API), 实际存储是在下面的bitmap。
两种画布
这里canvas可以绘制两种类型的画图,分别是view和surfaceView。
View:是普通画图,适合处理量比较小,帧率比较小的动画,比如说象棋游戏之类的。
SurfaceView:主要用在游戏,高品质动画方面的画图。
区别:在SurfaceView中定义一个专门的线程来完成画图工作,应用程序不需要等待View的刷图,提高性能。
Canvas坐标系与绘图坐标系
Canvas绘图中牵扯到两种坐标系:Canvas坐标系与绘图坐标系。
Canvas坐标系
Canvas坐标系指的是Canvas本身的坐标系,Canvas坐标系有且只有一个,且是唯一不变的,其坐标原点在View的左上角,从坐标原点向右为x轴的正半轴,从坐标原点向下为y轴的正半轴。绘图坐标系
Canvas的drawXXX方法中传入的各种坐标指的都是绘图坐标系中的坐标,而非Canvas坐标系中的坐标。默认情况下,绘图坐标系与Canvas坐标系完全重合,即初始状况下,绘图坐标系的坐标原点也在View的左上角,从原点向右为x轴正半轴,从原点向下为y轴正半轴。但不同于Canvas坐标系,绘图坐标系并不是一成不变的,可以通过调用Canvas的translate方法平移坐标系,可以通过Canvas的rotate方法旋转坐标系,还可以通过Canvas的scale方法缩放坐标系,而且需要注意的是,translate、rotate、scale的操作都是基于当前绘图坐标系的,而不是基于Canvas坐标系,一旦通过以上方法对坐标系进行了操作之后,当前绘图坐标系就变化了,以后绘图都是基于更新的绘图坐标系了。也就是说,真正对我们绘图有用的是绘图坐标系而非Canvas坐标系。
我们看下面代码就可以明白:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//未平移 在原点
canvas.drawLine(0, 0, width, 0, mPaint);//绘制x轴
canvas.drawLine(0, 0, 0, height, mPaint);//绘制y轴
//第一次移动
canvas.translate(200,200);
canvas.drawLine(0, 0, width, 0, mPaint);//绘制x轴
canvas.drawLine(0, 0, 0, height, mPaint);//绘制y轴
canvas.restore();
//第二次移动并旋转
canvas.translate(200,200);
canvas.rotate(30);
canvas.drawLine(0, 0, width, 0, mPaint);//绘制x轴
canvas.drawLine(0, 0, 0, height, mPaint);//绘制y轴
}
每次绘制同样的(startX, startY,stopX,stopY, paint)线,
但是我们发现平移或者旋转之后画出的线坐标发生了变化
那么有童鞋问了,如果我不想让坐标发生变化,或者再回去原点怎么搞?
别担心,只需要执行canvas.restore(),下面详细讲解。
Canvas保存和还原
- canvas.save()
保存当前坐标- canvas.restore()
回复上一次坐标,如果有保存,回到最后一次保存的坐标,如果没保存,则会报错java.lang.IllegalStateException: Underflow in restore - more restores than saves
,要先存再取。- restoreToCount(int saveCount)
回到第几次的保存坐标状态
对Canvas的操作 — 平移,旋转,缩放
Canvas平移
/**
* 画布向(dx,dy)方向平移
*
* 参数1: 向X轴方向移动dx距离
* 参数2: 向Y轴方向移动dy距离
*/
canvas.translate(float dx, float dy);
Canvas缩放
/**
* 在X轴方向放大为原来sx倍,Y轴方向方大为原来的sy倍
* 默认原点为左上角
* 参数1: X轴的放大倍数
* 参数2: Y轴的放大倍数
*/
canvas.scale(float sx, float sy);
/**
* 在X轴方向放大为原来sx倍,Y轴方向方大为原来的sy倍
* 参数1: X轴的放大倍数
* 参数2: Y轴的放大倍数
* 参数3: 原点X坐标
* 参数4: 原点Y坐标
*/
canvas.scale(float sx, float sy, float px, float py);
Canvas旋转
/**
* 原点为中心,旋转degrees度(顺时针方向为正方向 )
* 参数: 旋转角度
*/
canvas.rotate(float degrees);
/**
* 以(px,py)为中心,旋转30度,顺时针方向为正方向
* 参数1: 旋转角度
* 参数2: 原点X坐标
* 参数3: 原点Y坐标
*/
canvas.rotate(float degrees, float px, float py);
绘制
画文字
/**
* 参数1:输入的内容
* 参数2:文本x轴的位置
* 参数3:文本Y轴的位置
* 参数4:画笔对象
*/
drawText(String text, float x, float y, Paint paint)
/**
* 参数1:输入的内容
* 参数2:要从第几个字开始绘制
* 参数3:要绘制到第几个文字
* 参数4:文本x轴的位置
* 参数5:文本Y轴的位置
* 参数6:画笔对象
*/
drawText(String text, int start, int end, float x, float y,Paint paint)
样例:
canvas.drawText("开始写字啦!", 200,200,mPaint);
canvas.drawText("开始写字啦!",2,3, 200,400,mPaint);
画圆
/**
* 参数1:圆心X
* 参数2:圆心Y
* 参数3:半径R
* 参数4:画笔对象
*/
drawCircle(float cx, float cy, float radius, Paint paint)
样例:
mPaint.setStyle(Paint.Style.STROKE);
canvas.drawCircle(300,300,80,mPaint);
mPaint.setStyle(Paint.Style.FILL);
canvas.drawCircle(300,500,80,mPaint);
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
canvas.drawCircle(300,700,80,mPaint);
画线
/*
* 参数1:startX
* 参数2:startY
* 参数3:stopX
* 参数4:stopY
* 参数5:画笔对象
*/
canvas.drawLine(float startX, float startY, float stopX, float stopY,Paint paint);
/*
* 同时绘制多条线。
* 参数1:float数组:每四个一组为一条线。
* 参数2:画笔对象
*/
canvas.drawLines(@Size(multiple=4)float[] pts, Paint paint);
样例:
mPaint.setColor(getResources().getColor(android.R.color.holo_red_dark));
canvas.drawLine(50,50,200,50,mPaint);
mPaint.setColor(getResources().getColor(android.R.color.darker_gray));
canvas.drawLines(new float[]{200,200,300,200,300,300,300,400},mPaint);
画椭圆
/**
* 参数1: 矩形
* 参数2: 画笔
* /
canvas.drawOval(RectF oval, Paint paint);
/**
* 参数1:float left
* 参数2:float top
* 参数3:float right
* 参数4:float bottom
* 参数5:画笔
*/
canvas.drawOval(float left, float top, float right, float bottom, @NonNull Paint paint);
样例:
mPaint.setColor(getResources().getColor(android.R.color.holo_red_dark));
canvas.drawOval(new RectF(50,50,400,400),mPaint);
mPaint.setColor(getResources().getColor(android.R.color.darker_gray));
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
canvas.drawOval(50,500,700,700,mPaint);
}
画弧度
/**
* 参数1:RectF对象。
* 参数2:开始的角度。(水平向右为0度顺时针反向为正方向)
* 参数3:扫过的角度
* 参数4:是否和中心连线
* 参数5:画笔对象
*/
canvas.drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter,Paint paint);
/**
* 参数1:float left
* 参数2:float top
* 参数3:float right
* 参数4:float bottom
* 参数5:开始的角度。(水平向右为0度顺时针反向为正方向)
* 参数6:扫过的角度
* 参数7:是否和中心连线
* 参数8:画笔对象
*/
canvas.drawArc(float left, float top, float right, float bottom,float startAngle, float sweepAngle, boolean useCenter,Paint paint);
样例:
mPaint.setColor(getResources().getColor(android.R.color.holo_red_dark));
canvas.drawArc(new RectF(50,50,400,400),45,135,true,mPaint);
mPaint.setColor(getResources().getColor(android.R.color.darker_gray));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
canvas.drawArc(50,500,700,700,45,135,false,mPaint);
}
矩形
/**
* 矩形
* 参数1:float left
* 参数2:float top
* 参数3:float right
* 参数4:float bottom
* 参数5:画笔
*/
canvas.drawRect(float left, float top, float right, float bottom,Paint paint);
/**
* 参数1:矩形
* 参数2:画笔
*/
canvas.drawRectRect r,Paint paint);
样例:
mPaint.setColor(getResources().getColor(android.R.color.darker_gray));
canvas.drawRect(new RectF(50,50,400,400),mPaint);
mPaint.setColor(getResources().getColor(android.R.color.holo_red_dark));
canvas.drawRect(50,500,700,700,mPaint);
圆角矩形
/**
* 参数1:矩形
* 参数2:x半径
* 参数3:y半径
* 参数4: 画笔
*/
drawRoundRect(@NonNull RectF rect, float rx, float ry, @NonNull Paint paint)
/**
* 参数1:float left
* 参数2:float top
* 参数3:float right
* 参数4:float bottom
* 参数5:x半径
* 参数6:y半径
* 参数4: 画笔
*/
drawRoundRect(float left, float top, float right, float bottom, float rx, float ry, @NonNull Paint paint)
样例:
mPaint.setColor(getResources().getColor(android.R.color.darker_gray));
canvas.drawRoundRect(new RectF(50,50,400,400),20,20,mPaint);
mPaint.setColor(getResources().getColor(android.R.color.holo_red_dark));
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
canvas.drawRoundRect(50,500,700,700,30,50,mPaint);
}
画点
/**
* 参数1、2:点的x、y坐标
*/
canvas.drawPoint(60, 390, p);//画一个点
/**
* 参数1:多个点,每两个值为一个点。最后个数不够两个的值,忽略。
*/
canvas.drawPoints(new float[]{60,400,65,400,70,400}, p);//画多个点
样例:
mPaint.setColor(getResources().getColor(android.R.color.darker_gray));
canvas.drawPoint(50,50,mPaint);
mPaint.setColor(getResources().getColor(android.R.color.holo_red_dark));
canvas.drawPoints(new float[]{100,100,200,200,300, 300, 400,400,500,500,600,600},mPaint);
画图片
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher);
/**
* 参数1:bitmap对象
* 参数2:图像左边坐标点
* 参数3:图像上边坐标点
*/
canvas.drawBitmap(Bitmap bitmap, float left, float top, Paint paint);
样例:
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher);
canvas.drawBitmap(bitmap, 200,300, mPaint);
还有问题可以查看Canvas API
到这里基本属性就讲完了,接下来是一个练习。
代码绘制安卓小机器人
下面是代码 , 相当简单,就是计算一下坐标,就不详细讲了,有问题可以留言。
public class AndroidView extends View {
private float bodyWidth;
private float bodyHeigh;
private float armWidth;
private float armHeight;
private float legWidth;
private float legHeight;
private static final int INTERSPACE = 20;
private Paint mPaint;
private RectF bodyRect;
private RectF legRect;
private RectF armRect;
public AndroidView(Context context) {
this(context, null);
}
public AndroidView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public AndroidView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mPaint = new Paint();
mPaint.setColor(getResources().getColor(android.R.color.holo_green_dark));
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setBodyParams();
setArmParams();
setLegParams();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
//画身体
canvas.drawRoundRect(bodyRect, 20, 20, mPaint);
//画头
canvas.translate(0, -(bodyWidth / 2 + INTERSPACE));
canvas.drawArc(bodyRect, 0, -180, true, mPaint);
//画左胳膊
canvas.drawRoundRect(armRect, 30, 30, mPaint);
//画右胳膊
canvas.translate(bodyWidth + 5 * INTERSPACE, 0);
canvas.drawRoundRect(armRect, 30, 30, mPaint);
//画左腿
canvas.translate(-(bodyWidth + 7 * INTERSPACE),bodyWidth*11/10);
canvas.drawRoundRect(legRect, 30, 30, mPaint);
//画右腿
canvas.translate(2*INTERSPACE+legWidth,0);
canvas.drawRoundRect(legRect, 30, 30, mPaint);
//画左眼
canvas.translate(0,-bodyHeigh-5*INTERSPACE);
mPaint.setColor(getResources().getColor(android.R.color.white));
canvas.drawCircle(getWidth()/2,getHeight()/2,INTERSPACE/2,mPaint);
//画右眼
canvas.translate(-(2*INTERSPACE+legWidth),0);
mPaint.setColor(getResources().getColor(android.R.color.white));
canvas.drawCircle(getWidth()/2,getHeight()/2,INTERSPACE/2,mPaint);
canvas.restore();
mPaint.setTextSize(60);
mPaint.setColor(getResources().getColor(android.R.color.holo_red_dark));
canvas.drawText("我是安卓小机器人",150,100,mPaint);
}
private void setBodyParams() {
bodyWidth = getWidth() * 2 / 5;
bodyHeigh = bodyWidth;
bodyRect = new RectF();
bodyRect.left = (getWidth() - bodyWidth) / 2;
bodyRect.top = (getHeight() - bodyHeigh) / 2;
bodyRect.right = bodyRect.left + bodyWidth;
bodyRect.bottom = bodyRect.top + bodyHeigh;
}
private void setLegParams() {
legWidth = getWidth() * 1 / 13;
legHeight = getHeight() * 1 / 7;
legRect = new RectF();
legRect.left = (getWidth() - legWidth) / 2;
legRect.top = (getHeight() - legHeight) / 2;
legRect.right = legRect.left + legWidth;
legRect.bottom = legRect.top + legHeight;
}
private void setArmParams() {
armWidth = getWidth() * 1 / 13;
armHeight = getHeight() * 1 / 6;
armRect = new RectF();
armRect.left = (getWidth() - bodyWidth) / 2 - INTERSPACE * 4;
armRect.top = getHeight() / 2 + INTERSPACE * 2;
armRect.right = armRect.left + armWidth;
armRect.bottom = armRect.top + armHeight;
}
}