Canvas简介
什么是Canvas?
字面翻译“画布”,本质其实是一个绘制图形的工具类
我们从源码中认识Canvas
我们打开ViewRootImpl类。找到performDraw();在performDraw()中调用了draw();
private boolean draw(boolean fullRedrawNeeded) {
3178 Surface surface = mSurface;
3179 if (!surface.isValid()) {
3180 return false;
3181 }
3182 ……
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
3347 scalingRequired, dirty, surfaceInsets)) {
3348 return false;
3349 }
……
Suface可以理解为一个面,在这个方法里调用这个面,我们进入这个方法看看
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
3364 boolean scalingRequired, Rect dirty, Rect surfaceInsets) {
3365
3366 // Draw with software renderer.
定义canvas
3367 final Canvas canvas;
3368
3369
3384 创建出canvas
3385 canvas = mSurface.lockCanvas(dirty);
3386
3387
3470 return true;
3471 }
在这个方法里就定义了一个Canvas,并且用Surface将它创建出来
canvas = mSurface.lockCanvas(dirty);
dirty是一个矩形React。这个矩形大小就是我们的屏幕大小
说白了就是在Surface这个面里面我们画了一个矩形,这个画出来的矩形就是Canvas,跟踪lockCanvas方法你会发现这个canvas是从native层里获取的。科学的解释下Canvas就是Surface这个面板里面的一个绘制区域。最终会将我们的View绘制在该区域。注意ViewGroup不会重写onDraw(),只有控件才会重新onDraw();
我们追踪Canvas.drawXXX();会发现,他最终会把它要绘制的东西交给native层,所以我们可以得出一个结论:canvas不是具体执行绘制的对象 ,具体执行绘制的是GPU
坐标系
在Canvas去具体绘制图像的位置被叫做为坐标系。而Canvas中,存在两个坐标系概念,一是canvas的坐标系(最外层的面板,一经确定是不会改变的),二是绘制坐标系(绘制时所要将自己想要的东西画到某个地方的坐标)
canvas默认的坐标体系是左上角为(0,0),往右表示x轴的正方向,往下表示y轴的正方向。
我们看下这以下代码
package com.example.gaojiui;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.animation.LinearInterpolator;
import android.widget.TextView;
import androidx.appcompat.widget.AppCompatTextView;
import org.w3c.dom.Text;
/**
* @author writing
* @time 2019/12/7 21:09
* @note
*/
public class TestView extends View {
private Paint paint;
public TestView(Context context) {
super(context);
init();
}
public TestView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public TestView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
paint = new Paint();
paint.setColor(Color.RED);
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(10);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
RectF rectF = new RectF(0,0,400,400);
canvas.drawRect(rectF,paint);
//绘制坐标系的偏移
canvas.translate(100,200);
paint.setColor(Color.BLUE);
//左上角依然是0,0
RectF rectF1 = new RectF(0,0,400,400);
canvas.drawRect(rectF1,paint);
}
}
大家可以发现,绘制坐标系改变了,但canvas坐标系坐标没有改变。
matrix变换矩阵
先看一段代码
package com.example.gaojiui;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
/**
* @author writing
* @time 2019/12/7 21:09
* @note
*/
public class TestView extends View {
private Paint paint;
public TestView(Context context) {
super(context);
init();
}
public TestView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public TestView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
paint = new Paint();
paint.setColor(Color.RED);
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(10);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Matrix matrix1 = canvas.getMatrix();
Log.i("zhang_xin",matrix1.toString());
//绘制矩形
RectF rectF = new RectF(0,0,400,400);
canvas.drawRect(rectF,paint);
Matrix matrix2 = canvas.getMatrix();
Log.i("zhang_xin",matrix2.toString());
//canvas移动
canvas.translate(100,100);
paint.setColor(Color.BLACK);
RectF rectF2 = new RectF(0,0,400,400);
canvas.drawRect(rectF2,paint);
Matrix matrix3 = canvas.getMatrix();
Log.i("zhang_xin",matrix3.toString());
//canvas选装
canvas.rotate(30,200,100);
paint.setColor(Color.GREEN);
RectF rectF3 = new RectF(0,0,400,400);
canvas.drawRect(rectF3,paint);
Matrix matrix4 = canvas.getMatrix();
Log.i("zhang_xin",matrix4.toString());
}
}
看下打印结果
看下运行效果
我们看下对应关系
Canvas一些API的使用
我们写一个demo,绘制一个表盘
package com.example.gaojiui;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;
import androidx.annotation.Nullable;
/**
* @author writing
* @time 2019/12/13 12:53
* @note 表盘
*/
public class WatchView extends View {
private int width;
private int height;
private Paint paintCircle;
private Paint paintDegree;
private Paint paintHour;
private Paint paintMinute;
public WatchView(Context context) {
super(context);
inint();
}
public WatchView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
inint();
}
public WatchView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
inint();
}
private void inint() {
paintCircle = new Paint();
paintCircle.setColor(Color.BLACK);
paintCircle.setAntiAlias(true);
paintCircle.setStyle(Paint.Style.STROKE);
paintCircle.setStrokeWidth(10);
paintDegree = new Paint();
paintDegree.setColor(Color.BLUE);
paintDegree.setAntiAlias(true);
paintDegree.setStrokeWidth(5);
paintDegree.setStyle(Paint.Style.STROKE);
paintHour = new Paint();
paintHour.setStrokeWidth(60);
paintHour.setColor(Color.RED);
paintMinute = new Paint();
paintMinute.setStrokeWidth(30);
paintMinute.setColor(Color.GREEN);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//父容器建议的宽高
width = getMeasuredWidth();
height = getMeasuredHeight();
}
/**
* 绘制分为三步:
* 第一:绘制外圆;
* 第二:绘制刻度线;
* 第三:绘制指针;
*/
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//画外圆
canvas.drawCircle(width/2,height/2,Math.min(width,height)/2,paintCircle);
//绘制刻度线
for(int i = 0;i<12;i++){
if(i%3==0){
paintDegree.setStrokeWidth(7);
paintDegree.setTextSize(100);
canvas.drawLine(width/2,height/2-width/2,width/2,height/2-width/2+60,paintDegree);
String degree = String.valueOf(i);
canvas.drawText(degree,width/2-paintDegree.measureText(degree)/2,height/2-width/2+160,paintDegree);
}else{
paintDegree.setStrokeWidth(5);
paintDegree.setTextSize(70);
canvas.drawLine(width/2,height/2-width/2,width/2,height/2-width/2+60,paintDegree);
String degree = String.valueOf(i);
canvas.drawText(degree,width/2-paintDegree.measureText(degree)/2,height/2-width/2+160,paintDegree);
}
//旋转要有中心点
canvas.rotate(30,width/2,height/2);
}
//画指针
canvas.translate(width/2,height/2);
canvas.drawLine(0,0,100,200,paintHour);
canvas.drawLine(0,0,-100,400,paintMinute);
}
}
我们看下save 和restore两个方法
canvas.getSaveCount()指的是当前保存的一个层级
图层
图层分为状态栈和Layer栈;
状态栈:save、 restore方法来保存和还原变换操作Matrix以及Clip剪裁, 也可以通过restoretoCount直接还原到对应栈的保存状态
Layer栈:saveLayer的时候都会新建一个透明的图层(离屏Bitmap-离屏缓冲),并且会将saveLayer之前的一些Canvas操作延续过来, 后续的绘图操作都在新建的layer上面进行,当我们调用restore 或者 restoreToCount 时 更新到对应的图层和画布上
联想我们以前写的刮刮乐。我这里就不重复贴代码了。
save与savelayer的区别就是前者是不透明的后者是透明的。
挂挂卡中,如果我们用save,它的效果如下
用savelayer的效果如下