王学岗高级UI4-canvas的使用

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的效果如下
在这里插入图片描述

发布了208 篇原创文章 · 获赞 15 · 访问量 7万+

猜你喜欢

转载自blog.csdn.net/qczg_wxg/article/details/103465229