上篇的学习,Paint
中的基础知识基本结束,笔学完了,学习开始学习画布。本篇记录学习Canvas
中的一些知识。在drawBitmap()
方法中会引出计划下篇要学习的内容Matrix
,drawPath()
方法会引出计划在下下篇学习的贝塞尔曲线,学习的重点也是这两个方法
学习资料:
- 爱哥的自定义控件其实很简单5/12
- Canvas api
本人很菜,有错误,请指出
虽然称Canvas
为画布,但并不是直接在Canvas
画,Canvas
内部默认会创建一个Biatmap
,也可以通过构造方法或者setBitmap()
方法传入一个,像素所有的信息是画在了这个Bitmap
上,然后Bitmap
被保存在了Canvas
之内
这是我看爱哥的博客后,自己做的一个总结,若有错误,请指出。针对
Canvas
的两种构造方法,爱哥针对源码有做分析,可以看看了解一下
在Canvas
的方法中,clip
和draw
方法占据了一大半,在Android 自定义View学习(二)——开始了解Canvas和Paint了解过了几个draw
方法,本篇进行补充学习
1.draw方法补充学习
在draw
一系列方法中,有一个特殊的牛B的存在,drawBitmapMesh()
,这个方法牛B在可以几乎对Bitmap
做任何操作。虽然这个方法很强大,但使用的频率并不算高,也有点鸡肋。一些比较简单的Bitmap
处理可能优先考虑Matrix
,而过于复杂的处理,会耗时比较久,效率可能并不高。一般优先不考虑这个方法,遇到Matrix
实现不了的需求,记得有这么一个方法可以学习,然后使用
1.1 drawBitmap方法
感觉这个方法使用频率很高。一共有6个重载方法,其中两个参数最多的方法已经废弃,也就是需要学习4个
1.1.1 第1个方法
drawBitmap(@NonNull Bitmap bitmap, float left, float top, @Nullable Paint paint)
-
bitmap
要画的目标Bitmap
-
left
左上角的X
轴坐标 -
top
左上角的Y
轴坐标 -
paint
画笔
简单使用
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
final Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test);
final float x = getWidth() / 2 - bitmap.getWidth() / 2;//水平居中
final float y = 0;
canvas.drawBitmap(bitmap, x, y, mPaint);
}
在实际开发中,还会考虑图片宽高的压缩,显示位置,以及padding
的等等
1.1.2 第2个和第3个方法
drawBitmap(@NonNull Bitmap bitmap, @Nullable Rect src, @NonNull Rect dst,@Nullable Paint paint)
drawBitmap(@NonNull Bitmap bitmap, @Nullable Rect src, @NonNull RectF dst,@Nullable Paint paint)
两个方法的差别在于第三个参数Rect
和RectF
-
src
用来截取Bitmap
局部所想要显示的像素块区域,通过构造方法中的四个坐标系点确定范围。这个参数可以为null
,为null
就是整个Bitmap
都作为目标资源显示 -
dst
用来显示的区域,在控件中绘制Bitmap
的区域,可以实现拉伸或者缩放
简单使用
private void init() {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
bitmap = BitmapFactory.decodeResource(getResources(),R.drawable.test);
rect = new Rect(0,0,1080,600);
rectF = new RectF(0f,0f,1080f,600f);
}
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (bitmap != null)
canvas.drawBitmap(bitmap,null,rectF,mPaint);
}
1080
是测试手机的屏幕宽度
第2个参数rect = new Rect(x,y,x1,y1)
,构造方法中四个参数分别为:
-
x
, X轴开始的坐标点,默认为0 -
y
, Y轴开始的坐标点 -
x1
, X轴结束的坐标点,若x1-x
大于了Bitmap
的宽度,截取的有效区域就是Bitmap
的宽度 -
y1
, Y轴结束的坐标点
第3个参数rectF = new RectF(x,y,x1,y1)
,构造方法中四个参数分别为:
-
x
,开始绘制的X轴的坐标点,默认为0 -
y
,开始绘制的Y轴的坐标点 -
x1
,结束绘制的X轴的坐标点,若x1-x
大于src
中x1-x
,就是拉伸;小于就是缩放 -
y1
,结束绘制的Y轴的坐标点
简单修改代码:
rect = new Rect(0,0,400,600);
rectF = new RectF(100f,0f,700f,600f);
此时在控件X
轴100f
位置开始绘制资源Bitmap
的(0,0)到(400,600)
局部区域,最终的显示效果就成了拉伸局部的效果。Y
轴同理
这两个参数的作用,用几个数简单测试一下,比较直观。
1.1.3 第4个方法
drawBitmap(@NonNull Bitmap bitmap, @NonNull Matrix matrix, @Nullable Paint paint)
简单使用
private void init() {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test);
matrix = new Matrix();
matrix.setTranslate(100f,100f);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawBitmap(bitmap,matrix,mPaint);
}
Matrix
是一个3 * 3
的矩阵,下篇进行记录学习
1.2 画Line方法
画一条线的方法
drawLine(float startX, float startY, float stopX, float stopY, @NonNull Paint paint)
画多条线的方法
drawLines(@Size(multiple=4) @NonNull float[] pts, @NonNull Paint paint)
drawLines(@Size(multiple=4) @NonNull float[] pts, int offset, int count,@NonNull Paint paint)
1.2.1 drawLine 画一条线
drawLine()
方法就一个,没有重载方法。画规则曲线可以考虑使用Path
private void init() {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.parseColor("#FF4081"));
mPaint.setStrokeWidth(10f);
}
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawLine(100f,100f,600f,500f,mPaint);
}
方法参数也很简单,四个参数确定两个点的坐标,然后两点一线
1.2.2 drawLines画多条直线
两个参数的drawsLines(float[] pts, Paint paint)
方法简单使用
private void init() {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.parseColor("#FF4081"));
mPaint.setStrokeWidth(10f);
floats = new float[]{100f,100f,300f,300f,400f,200f,600f,200f};//每4个数一组,确定两个点的坐标
}
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawLines(floats,mPaint);
}
方法源码中有这样一个@Size(multiple=4)
注解,要求floats
中每4个值看做一组,每组来确定一条直线的两个端点,不足4个的部分是无效的
两个参数的方法内部还是调用了四个参数的方法,在源码中
public void drawLines(@Size(multiple=4) @NonNull float[] pts, @NonNull Paint paint) {
drawLines(pts, 0, pts.length, paint);
}
在drawLines(float[] pts, int offset, int count,Paint paint)
中,
-
offset
表示跳过floats
中几个值 -
count
表示跳过offset
后,数组的长度
简单修改代码
canvas.drawLines(floats,4,floats.length-4,mPaint);
就只会画出(400f,200f),(600f,200f)
确定的那条水平的短线
1.3 画Potion方法
同1.2画Line
方法一样,画点的方法也是有两种,画一个点和画多个点
画一个点很简单,直接看画多个点
drawPoints(@Size(multiple=2) float[] pts, int offset, int count,@NonNull Paint paint)
根据方法内的注解得知,pts
的大小要大于2,并且每两个一组,多余的无效
简单使用:
private void init() {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.parseColor("#FF4081"));
mPaint.setStrokeWidth(10f);
floats = new float[]{100f,100f,300f,200f,200f};
}
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawPoints(floats,mPaint);
}
在floats
中,有5个值,但实际有效的也就是前4个
1.3 drawPath 方法
在Android 自定义View学习(三)——Paint绘制文字属性中学习了解了setPathEffect(PathEffect effect)
方法在绘制路径时的效果,drawPath()
方法往往都会配合PahtEffect
来使用
drawPath(@NonNull Path path, @NonNull Paint paint)
方法只有一个,简单使用
private void init() {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.parseColor("#FF4081"));
mPaint.setStyle(Paint.Style.STROKE);//设置风格为空心
mPaint.setStrokeWidth(10f);
path = new Path();
path.moveTo(540f,50f);
path.lineTo(740f,300f);
path.lineTo(340f,300f);
path.close();//形成闭合 将(340,300)和(540,50)连接起来
}
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawPath(path, mPaint);
}
绘制出一个等腰三角形
主要就是用到Path
这个类,这个类有很多方法除了可以绘制直线,还可以绘制曲线
1.3.1 Path类
The Path class encapsulates compound (multiple contour) geometric paths consisting of straight line segments, quadratic curves, and cubic curves.
It can be drawn with canvas.drawPath(path, paint), either filled or stroked (based on the paint's Style), or it can be used for clipping or to draw text on a path.
这个类封装了一些可以借助一元直线方程,二远方程曲线,立方曲线的方法来绘制一些较为复杂的组合形式的集合图形,绘制出来的风格则是根据画笔设置的style
来决定,也可以剪切或者绘制一段文字在路径上
除了已经用到的moveTo()
和lineTo()
方法,Path
中还有很多add
开头的方法。有两个重点方法是quadTo()
和cubicTo()
方法
1.3.1 quadTo和cubicTo方法
quadTo()
可以用来绘制二阶贝塞尔曲线,也就是3个点确定一个曲线
private void init() {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.parseColor("#FF4081"));
mPaint.setStyle(Paint.Style.STROKE);//设置风格为空心
mPaint.setStrokeWidth(10f);
path = new Path();
path.moveTo(100f,100f);
floats = new float[]{200f,200f,900f,100f};
path.quadTo(200f,200f,900f,100f);
}
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawPath(path, mPaint);
canvas.drawPoints(floats,mPaint);
}
图中的曲线,是由(100,100),(900,100),(200,200)
三个点来确定。
cubicTo
方法使用和quadTo
用法类似,只是多了一组参数,多了一个点而已
path.cubicTo(200f,200f,400f,100f,900f,200f);
测试效果和quadTo
很容易就分区
依稀记得高三的数学考试最后一道大题往往就是要求绘制会一个点的运动轨迹,大部分都是一段曲线或者一个椭圆之类的,和这里有些类似。
这两方法重要的是理解其中的原理,贝尔塞尔曲线等到Matrix
学习结束后再进行学习
1.3.2 addTo方法
先来看简单用法
private void init() {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.parseColor("#FF4081"));
mPaint.setStyle(Paint.Style.STROKE);//设置风格为空心
mPaint.setStrokeWidth(10f);
path = new Path();
path.moveTo(100f,100f);
rectF = new RectF(100, 100, 400, 400);
path.arcTo(rectF,0,90);
}
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawPath(path, mPaint);
}
这个方法就是画出一段弧线后,将弧线的起始点和moveTo
确定的path
的起始点进行连接起来。弧线截取圆的一部分。圆的直径为300,经过很简单分析,圆心在(250,250)
的点
1.3.3 rLineTo方法
r
就是relative
,相对的缩写
简单使用
private void init() {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.parseColor("#FF4081"));
mPaint.setStyle(Paint.Style.STROKE);//设置风格为空心
mPaint.setStrokeWidth(10f);
path = new Path();
path.moveTo(100f,100f);
path.rLineTo(300,200f);
}
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawPath(path, mPaint);
canvas.drawPoint(300,200,mPaint);
}
rLineTo
方法确定的坐标是相对于moveTo
来说的,实际最终的画出的直线的结束点的坐标为(400,300)
1.3.4 addArc方法
简单使用
private void init() {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.parseColor("#FF4081"));
mPaint.setStyle(Paint.Style.STROKE);//设置风格为空心
mPaint.setStrokeWidth(10f);
path = new Path();
path.moveTo(100f,100f);
path.lineTo(300,200f);
//添加弧形
rectF = new RectF(100, 100, 400, 400);
path.addArc(rectF,0,90);
}
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawPath(path, mPaint);
canvas.drawPoint(300,200,mPaint);
}
addArc
方法和addTo
方法却别是,addArc
是添加一段弧形,并不将绘制的图形连接起来
1.4 canvas.drawTextOnPath 方法
在绘制的路径上,绘制文字
private void init() {
//路径画笔
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.parseColor("#FF4081"));
mPaint.setStyle(Paint.Style.STROKE);//设置风格为空心
mPaint.setStrokeWidth(10f);
//文字画笔
textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
textPaint.setTextSize(60f);
//路径
path = new Path();
rectF = new RectF(100, 100, 300, 400);
path.addOval(rectF, Path.Direction.CW);
chars = new char[]{'a','b','c','d','e'};
}
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//绘制路径
canvas.drawPath(path, mPaint);
//绘制路径上的文字
canvas.drawTextOnPath(chars,0,chars.length,path,0f,chars.length,textPaint);
}
在addOval
方法中,可以改变路径闭合的方向,简单修改代码
path.addOval(rectF, Path.Direction.CCW);
文字在路径内侧绘制,并且逆时针
draw
系列方法中,有一个看起来很有意思的方法,drawTextRun
方法,但这个方法最低要求的23
canvas.drawTextRun(chars,0,chars.length,0,chars.length,100f,100f,false,textPaint);
没有23的真机,就用了虚拟机,看方法名字,以为会按照一定的方法,文字进行滚动,可并没有,设置为false
为abcde
,设置为true
为edcba
,不清楚在真机上啥效果
加上在开始了解Canvas
中的方法,draw
大致就学习到这里
2.Clip方法学习
Clip
开头的方法主要有两个:
-
clipPath()
利用Path
的方法,可以裁切出一块不规则区域画布 -
clipRect()
可以裁切出一块矩形画布
还有一个已经废弃的clipRegion()
,废弃就不学了,直接学替代的方法
2.1 clipRect裁切规则区域画布
简单使用:
private void init() {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.parseColor("#FF4081"));
mPaint.setStyle(Paint.Style.FILL);
//矩形
rectF = new RectF(0, 0, 400, 400);
}
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//绘制底色 黄
canvas.drawColor(Color.YELLOW);
//截取画布
canvas.clipRect(rectF);
//截取后的画布底色
canvas.drawColor(Color.CYAN);
//验证有效区域
canvas.drawRect(300,300,600,600,mPaint);
}
clipRect(rectF)
,就是在在画布裁出以(0,0),(0,400),(400,0),(400,400)
四个点确定的矩形。之后Canvas
有效的区域便就是裁出的矩形区域,再次进行绘制时,超出这个区域便无法绘制,但裁切并不会影响c裁切前已经绘制好的区域,clip
裁切针对的是Canvas
cliprRect
有这样一个cliprRect((@NonNull RectF rect, @NonNull Region.Op op)
重载方法
2.1.1 Region.Op
Op
是Region
类中的一个枚举,有6个值
直接用代码演示效果
private void init() {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.WHITE);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(5f);
//裁切区域1
rectF1 = new RectF(100, 100, 300, 300);
//裁切区域2
rectF2 = new RectF(200, 200, 400, 400);
}
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//绘制底色 蓝
canvas.drawColor(Color.BLUE);
canvas.save();
//截取画布1
canvas.clipRect(rectF1);
//截取画布2
canvas.clipRect(rectF2,Region.Op.DIFFERENCE);
//截取后的有效区域画布底色
canvas.drawColor(Color.RED);
canvas.restore();
//绘制辅助区域
canvas.drawRect(rectF1, mPaint);
canvas.drawRect(rectF2, mPaint);
}
红色区域就代表两次裁切后的有效区域
- Region.Op.DIFFERENCE
取第一次裁切的非交集部分
- Region.Op.INTERSECT
取两次的交集
- Region.Op.REPLACE
第二次替代第一次裁切
- Region.Op.UNION
两次裁切的和
- Region.Op.REVERSE_DIFFERENCE
与DIFFERENCE
相反,取的第2次裁切的非交集区域
- Region.Op.XOR
异或,取两次交集外的区域
有点类似PorterDuffXfermode
图像处理的效果
2.2 CilpPath 裁切不规则画布
裁切出一个圆形区域的画布
private void init() {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.WHITE);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(5f);
path = new Path();
path.addCircle(300,300,100, Path.Direction.CCW);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//绘制底色 蓝
canvas.drawColor(Color.BLUE);
//裁切画布
canvas.clipPath(path);
//绘制裁切后的区域底色
canvas.drawColor(Color.parseColor("#FF4081"));
//绘制辅助圆形
canvas.drawCircle(300,300,100,mPaint);
}
clip
的方法基本就学到这里
3.其他方法
画布除了裁切外,还有可以进行旋转
3.1 rotate 旋转方法
private void init() {
rectP1 = new Paint(Paint.ANTI_ALIAS_FLAG);
rectP1.setColor(Color.BLUE);
rectP2 = new Paint(Paint.ANTI_ALIAS_FLAG);
rectP2.setColor(Color.parseColor("#FF4081"));
rectF1 = new RectF(100,100,400,400);
rectF2 = new RectF(200,200,300,300);
}
/**
* 旋转画布
*/
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//旋转30°
canvas.rotate(30);
canvas.drawRect(rectF1,rectP1);
canvas.drawRect(rectF2,rectP2);
}
整个画布进行了顺时针旋转30°
,参数为正时,是顺时针旋转,负数为逆时针旋转
如果只想让小的红色的矩形进行旋转,而蓝色的大矩形不旋转,需要了解画布中的层
3.2 save和restore方法
简单修改代码,加入save
和restore
两个方法
private void init() {
rectP1 = new Paint(Paint.ANTI_ALIAS_FLAG);
rectP1.setColor(Color.BLUE);
rectP2 = new Paint(Paint.ANTI_ALIAS_FLAG);
rectP2.setColor(Color.parseColor("#FF4081"));
rectF1 = new RectF(100,100,400,400);
rectF2 = new RectF(200,200,300,300);
}
/**
* 旋转画布
*/
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawRect(rectF1,rectP1);
canvas.save();
//旋转30°
canvas.rotate(30);
canvas.drawRect(rectF2,rectP2);
canvas.restore();
}
save()
就是保存当前图层
restore()
就是把图层恢复到最近一次save()
方法前的状态
关于保存图层,还有一个更加强大的saveLayer()
,这个方法就等用到时,再进行学习
3.3 translate 平移画布
这个方法使用很简单
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//平移
canvas.translate(800,200);
//绘制矩形
canvas.drawRect(rectF1,rectP1);
canvas.drawRect(rectF2,rectP2);
//在(100,100)处绘制一个小圆,用来辅助观察坐标系的改变
canvas.drawCircle(100,100,30,rectP2);
}
这个方法需要注意的是,Canvas
的坐标系就进行了改变,观察小圆的位置
3.4 scale 缩放方法
缩放的使用也非常简单
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.scale(0.5F, 1.0F);
canvas.drawBitmap(bitmap,0,0,null);
}
缩放有效值为0~1f
,1表示不进行缩放,原始大小
缩放方法有个重载方法scale(float sx, float sy, float px, float py)
简单修改代码
canvas.scale(0.5F, 1.0F,540,0);
px,py
确定缩放中心,canvas.scale(0.5F, 1.0F)
默认为(0,0)
为缩放中心,指定(540,0)
为缩放中心时,屏幕宽度为1080
,Canvas
就在水平居中缩放
3.5 skew 错切
简单使用
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.skew(0.5f,0);
canvas.drawBitmap(bitmap,0,0,null);
}
这几个方法都只是简单的调用,看了看效果,以后用到就再深入了解学习
4. 最后
Canvas
的基本知识也就学习这些,遗漏的遇到再学习
篇幅有点长,但并不难理解,基本都是调用一下就可以比较直观看出效果的方法
下篇学习Matrix
月饼节到了,中秋快乐 : )