根据文档说明我们知道想要draw something有四个基本的要素:
- 一个保存像素的Bitmap
- 一个Canvas进行Bitmap的绘制
- 绘制的东西
- 画笔Paint
获取Canvas
获取Canvas实例我们一般都使用两种方式,一种是重写View的onDraw方法获得Canvas,另一种就是自己创建一个Canvas对象,创建Canvas对象我们需要一个Bitmap对象
Bitmap bitmap = Bitmap.createBitmap(200, 200, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
//或者
Canvas canvas = new Canvas();
canvas.setBitmap(bitmap);
canvas的方法
填充
- drawRGB(int r, int g, int b)
- drawARGB(int a, int r, int g, int b)
- drawColor(int color)
- drawColor(int color, PorterDuff.Mode mode)
- drawPaint(Paint paint)
这几个方法都是用颜色去填充Bitmap,但都指出受限制于当前的clip,这个我们稍后再说
几何图形
画点
- drawPoints(float[] pts, int offset, int count,Paint paint)
- drawPoints(loat[] pts, Paint paint)
drawPoint(float x, float y, Paint paint)
画线
- 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)
画矩形
- drawRect(RectF rect, Paint paint)
- drawRect(Rect r, Paint paint)
- drawRect(float left, float top, float right, float bottom, Paint paint)
画椭圆
- drawOval(RectF oval, Paint paint)
- drawOval(float left, float top, float right, float bottom, Paint paint)
画弧
- 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)
画圆角矩形
- drawRoundRect(float left, float top, float right, float bottom, float rx, float ry,Paint paint)
- drawRoundRect(RectF rect, float rx, float ry, Paint paint)
画路径
- drawPath(Path path, Paint paint)
- drawTextOnPath(char[] text, int index, int count, Path path, float hOffset, float vOffset, Paint paint)
- drawTextOnPath(String text, Path path, float hOffset,float vOffset,Paint paint)
画Bitmap
- drawBitmap(Bitmap bitmap, float left, float top, Paint paint)
- drawBitmap(Bitmap bitmap, Rect src, RectF dst,Paint paint)
- drawBitmap(Bitmap bitmap,Rect src, Rect dst,Paint paint)
- drawBitmap(Bitmap bitmap, Matrix matrix, Paint paint)
效果图
常用的大致就是上面的方法,看代码具体的运用
效果图
package com.lzy.lzy_canvas;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;
/**
* Created by lzy on 2016/10/20.
*/
public class CustomView extends View {
private Paint mPaint;
private float[] pts = {300.0f, 60.0f, 320.0f, 60.0f, 340.0f, 60.0f};
public CustomView(Context context, AttributeSet attrs) {
super(context, attrs);
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setColor(Color.RED);
}
@Override
protected void onDraw(Canvas canvas) {
Paint paintBlack = new Paint();
paintBlack.setColor(Color.BLACK);
Paint paintBlue = new Paint();
paintBlue.setColor(Color.parseColor("#29C2D3"));
Paint paint1 = new Paint();
paint1.setColor(Color.parseColor("#68A11F"));
Paint paint2 = new Paint();
paint2.setColor(Color.parseColor("#5A1B8C"));
paint2.setStyle(Paint.Style.STROKE);
paint2.setStrokeWidth(5);
//填充背景
canvas.drawColor(Color.WHITE);
//画点
mPaint.setTextSize(60);
canvas.drawText("画点", 50.0f, 100.0f, mPaint);
canvas.drawPoints(pts, mPaint);
//画线
canvas.drawText("画线", 50.0f, 200.0f, mPaint);
canvas.drawLine(100.0f, 300.0f, 800.0f, 300.0f, paintBlack);
canvas.drawLines(new float[]{100.0f, 300.0f, 400.0f, 400.0f, 400.0f, 400.0f, 800.0f, 300.0f}, paintBlack);
//画矩形
canvas.drawRect(new Rect(0, 400, 1000, 600), mPaint);
// canvas.drawRect(0, 400, 1000,600,mPaint);
//画椭圆
canvas.drawOval(new RectF(0f, 400f, 1000f, 600f), paintBlack);
//画弧
canvas.drawArc(new RectF(300f, 100f, 400f, 200f), 0f, 270f, true, paintBlue);
canvas.drawArc(new RectF(500f, 100f, 700f, 200f), 0f, 120f, false, paintBlue);
//圆角矩形
canvas.drawRoundRect(new RectF(100f, 700f, 300f, 900f), 20f, 20f, paint1);
//Path
Path path = new Path();
path.moveTo(400f, 700f);
path.lineTo(600f, 700f);
path.lineTo(700f, 800f);
path.close();
canvas.drawPath(path, paint1);
//贝塞尔曲线
Path path1 = new Path();
path1.moveTo(800f, 700f);
path1.quadTo(1000f, 700f, 1200f, 1500f);
canvas.drawPath(path1, paint2);
Path path2 = new Path();
paint2.setTextSize(30);
path2.moveTo(500f, 1200f);
path2.quadTo(500f, 2000f, 200f, 1300f);
canvas.drawPath(path2, paint2);
paint2.setStyle(Paint.Style.FILL);
String text = "加快了解罚款链接了框架安抚";
//第三个参数是距离开始点的距离,第四个参数是距离线的距离
canvas.drawTextOnPath(text, path2, 50, 0, paint2);
//Bitmap
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher);
//其中两个参数是决定位置的
canvas.drawBitmap(bitmap, 0, 1200, new Paint());
//第二个参数相当于裁剪,意思是要截取Bitmap哪一部分来显示
//第三个参数是放置位置,放在canvas的哪个位置
canvas.drawBitmap(bitmap, new Rect(40, 40, 100, 100), new Rect(200, 1600, 260, 1660), null);
}
}
Translate
canvas.translate()画布的平移操作,画布的顶点默认位置是在(0,0),当调用canvas.translate(100,100)把画布顶点平移到(100,100)
@Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(Color.WHITE);
canvas.drawRect(new Rect(50, 50, 250, 250), mPaint);
canvas.translate(100, 100);
//顶点在(100,100)
canvas.drawRect(new Rect(50, 50, 250, 250), mPaint);
canvas.translate(100, 100);
//顶点在(200,200)
canvas.drawRect(new Rect(50, 50, 250, 250), mPaint);
Rect frame = new Rect(50, 300, 300, 310);
for (int i = 0; i < 20; i++) {
canvas.drawRect(frame,mPaint);
canvas.translate(0,20);
}
}
Scale
- scale(float sx, float sy)
- scale(float sx, float sy, float px, float py)
有两个方法,先看效果
@Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(Color.WHITE);
mPaint.setColor(Color.RED);
canvas.drawRect(new Rect(50, 50, 250, 250), mPaint);
canvas.scale(0.5f,0.5f,150f,150f);
// canvas.scale(0.5f, 0.5f);
mPaint.setColor(Color.CYAN);
canvas.drawRect(new Rect(50, 50, 250, 250), mPaint);
}
第一张是使用canvas.scale(0.5f, 0.5f)的结果,我们发现四个参数都减少了一半。
第二张是使用canvas.scale(0.5f,0.5f,150f,150f)的结果,明显不一样,看源码
public final void scale(float sx, float sy, float px, float py) {
translate(px, py);
scale(sx, sy);
translate(-px, -py);
}
可以发现把顶点先移动到指定位置,然后缩放,最后再平移到原来的位置,意思是后面指定的点就是缩放的顶点,不变。
利用scale可以简单实现下面的效果
@Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(Color.WHITE);
mPaint.setColor(Color.RED);
mPaint.setStyle(Paint.Style.STROKE);
for (int i = 0; i < 10; i++) {
canvas.drawRect(new Rect(50, 50, 550, 550), mPaint);
canvas.scale(0.8f, 0.8f, 300, 300);
}
}
Rotate
- rotate(float degrees)
- rotate(float degrees, float px, float py)
和Scale类似,也是两个方法,第二方法传入的坐标点也和scale一样,相当于基准点
@Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(Color.WHITE);
mPaint.setColor(Color.RED);
canvas.drawRect(new Rect(50, 50, 350, 350), mPaint);
//原点旋转
// canvas.rotate(30);
//绕中心点旋转
canvas.rotate(30,200,200);
mPaint.setColor(Color.BLUE);
canvas.drawRect(new Rect(50, 50, 350, 350), mPaint);
}
分别是绕原点和中心点的效果
Skew
- skew(float sx, float sy)
- sx,sy分别是x,y方向的倾斜tan值
y方向倾斜45度
这个变化后的值是怎么计算的?
比如右上角的点为(50,50)
由于x方向为0,只进行y方向的变化,变化后的y为
y+sy*x,sy就是传入的第二个参数,即50+1*350=400,所以变化后坐标为(350,400)
@Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(Color.WHITE);
mPaint.setColor(Color.RED);
canvas.drawRect(new Rect(50, 50, 350, 350), mPaint);
canvas.skew(0, 1);
mPaint.setColor(Color.BLUE);
canvas.drawRect(new Rect(50, 50, 350, 350), mPaint);
}
save()和restore()
这是用于Canvas的状态保存和回滚,save()可以保存当前的matrix和clip操作到一个私有的栈中,restore()可以回滚到之前的保存状态,比如下面的代码,就是先保存当前的状态,然后把画布平移到了(10,10)这里,但是当执行了restore()之后,画布又回到了之前的状态,也就是(0,0)
canvas.save();
canvas.translate(10, 10);
drawSample(canvas);
canvas.restore();
裁剪
主要是clipRect(),clipPath()方法,前者显然是裁剪矩形,后者是裁剪路径
裁剪即裁剪画布,只有裁剪的区域才能显示出绘制的内容。试验一下
@Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(Color.WHITE);
canvas.clipRect(new Rect(100,100, 200, 200));
canvas.drawColor(Color.RED);
}
效果如下,可以看到红色只是在裁剪的区域显示出来
当我们调用方法时可能发现有的方法后面需要传入一个Region.Op参数,那么这个Region.Op又是什么东西?容我喝口水慢慢道来~
它表示当前裁剪区域和之前裁剪区域的关系,意思是需要至少裁剪两次这个参数才会起作用,对吧。这个参数的值是枚举类型,具体看看有哪些取值
- DIFFERENCE:之前剪切过除去当前要剪切的区域
- INTERSECT:当前要剪切的区域在之前剪切过内部的部分
- UNION:当前要剪切的区域加上之前剪切过内部的部分
- XOR:异或,当前要剪切的区域与之前剪切过的进行异或
- REVERSE_DIFFERENCE:与DIFFERENCE相反,以当前要剪切的区域为参照物,当前要剪切的区域除去之前剪切过的区域
- REPLACE:用当前要剪切的区域代替之前剪切过的区域
其实当我们不传入这个参数时,它会默认给INTERSECT
是不是很清楚啦?没有?那看看下面的具体例子吧
package com.lzy.lzy_canvas;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Region;
import android.util.AttributeSet;
import android.view.View;
/**
* Created by lzy on 2016/10/20.
*/
public class CustomView2 extends View {
private Paint mPaint;
public CustomView2(Context context, AttributeSet attrs) {
super(context, attrs);
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setStrokeWidth(6);
mPaint.setTextSize(16);
mPaint.setTextAlign(Paint.Align.RIGHT);
}
@Override
protected void onDraw(Canvas canvas) {
canvas.save();
canvas.translate(10, 10);
drawSample(canvas);
canvas.restore();
canvas.save();
canvas.translate(560, 10);
//剪切掉外面一层
canvas.clipRect(50, 50, 450, 450);
//DIFFERENCE:之前剪切过除去当前要剪切的区域.
// 去掉中间剪切的区域,所以只剩一个环
canvas.clipRect(150, 150, 350, 350, Region.Op.DIFFERENCE);
drawSample(canvas);
canvas.restore();
canvas.save();
canvas.translate(10, 560);
canvas.clipRect(150, 150, 350, 350);
//REPLACE:当前剪切的区域覆盖之前剪切的区域
canvas.clipRect(50, 50, 450, 450, Region.Op.REPLACE);
drawSample(canvas);
canvas.restore();
canvas.save();
canvas.translate(560, 560);
canvas.clipRect(0, 0, 160, 160);
//UNION:当前的剪切区域加上之前剪切的区域
canvas.clipRect(100, 100, 200, 200, Region.Op.UNION);
drawSample(canvas);
canvas.restore();
canvas.save();
canvas.translate(10, 1110);
canvas.clipRect(0, 0, 160, 160);
//XOR:当前截取区域和之前剪切区域进行异或,就是UNION去掉重叠的部分
canvas.clipRect(100, 100, 200, 200, Region.Op.XOR);
drawSample(canvas);
canvas.restore();
canvas.save();
canvas.translate(560, 1110);
canvas.clipRect(0, 0, 160, 160);
//REVERSE_DIFFERENCE:和DIFFERENCE相反,当前要剪切区域去掉之前剪切的区域
canvas.clipRect(100, 100, 200, 200, Region.Op.REVERSE_DIFFERENCE);
drawSample(canvas);
canvas.restore();
canvas.save();
canvas.translate(10, 1620);
canvas.clipRect(0, 0, 160, 160);
//INTERSECT:当前裁剪区域在之前裁剪区域的内部部分,就相当于交集
canvas.clipRect(100, 100, 200, 200, Region.Op.INTERSECT);
drawSample(canvas);
canvas.restore();
}
private void drawSample(Canvas canvas) {
canvas.clipRect(0, 0, 500, 500);
canvas.drawColor(Color.WHITE);
mPaint.setColor(Color.RED);
canvas.drawCircle(250, 250, 250, mPaint);
}
}
效果如下,可以对照着代码看,容易理解,左上角是没有经过剪切的样子