一起来演示Android画布

Canvas的常用操作速查表

操作类型相关API备注
绘制颜色drawColor, drawRGB, drawARGB使用单一颜色填充整个画布
绘制基本形状drawPoint, drawPoints, drawLine, drawLines, drawRect, drawRoundRect, drawOval, drawCircle, drawArc依次为 点、线、矩形、圆角矩形、椭圆、圆、圆弧
绘制图片drawBitmap, drawPicture绘制位图和图片
绘制文本drawText, drawPosText, drawTextOnPath依次为 绘制文字、绘制文字时指定每个文字位置、根据路径绘制文字
绘制路径drawPath绘制路径,绘制贝塞尔曲线时也需要用到该函数
顶点操作drawVertices, drawBitmapMesh通过对顶点操作可以使图像形变,drawVertices直接对画布作用、 drawBitmapMesh只对绘制的Bitmap作用
画布剪裁clipPath, clipRect设置画布的显示区域
画布快照save, restore, saveLayerXxx, restoreToCount, getSaveCount依次为 保存当前状态、 回滚到上一次保存的状态、 保存图层状态、 回滚到指定状态、 获取保存次数
画布变换translate, scale, rotate, skew依次为 位移、缩放、 旋转、倾斜
Matrix(矩阵)getMatrix, setMatrix, concat实际画布的位移,缩放等操作的都是图像矩阵Matrix,只不过Matrix比较难以理解和使用,故封装了一些常用的方法。

首先随意自定义一个View,重写onDraw(Canvas canvas)方法,在onDraw方法里面演示画布:

public class CustomView extends View {

    private Paint mPaint;

    public CustomView(Context context) {
    super(context);
    }

    public CustomView(Context context, @Nullable AttributeSet attrs) {
    super(context, attrs);
    }

    public CustomView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public CustomView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    super(context, attrs, defStyleAttr, defStyleRes);
    }

    @Override
    protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);

    }
}

开始初始化画笔:

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);

    //初始化画笔
    initPaint();

}

/**
 * 初始化画笔
 * @return
 */
private void initPaint(){
    if(mPaint == null){
        mPaint = new Paint();
    }
    //设置画笔的颜色
    mPaint.setColor(Color.BLUE);
    //设置画笔的大小
    mPaint.setStrokeWidth(10);
}

这里简单设置了画笔,本章目的是演示画布。
接下来拿起你的笔开始画画:

画直线:

画单条直线

//画直线,前四个参数为直线两端的坐标
canvas.drawLine(100,200,300,300, mPaint);

效果如下

《一起来演示Android画布》 图片.png

画多条直线(一):

    float[] pts1 = {//声明直线位置
            200,300,400,400,
            300,400,500,600
    };
    canvas.drawLines(pts1, mPaint);

效果如下

《一起来演示Android画布》 图片.png

画多条直线(二):

    float[] pts2 = {//数组的长度为12
            200,300,400,400,
            500,450,500,600,
            700,750,800,850
    };
    // 第一个参数传递数组
    // 第二个参数传递偏移量,也就是跳过前多少个数据
    // 第三个参数传递数量,因为每四个数据确定一条直线,所以这里的传值都是4的倍数
    canvas.drawLines(pts2,2,8,mPaint);//从数组的第三个数据开始以后取8个数据,每4个数据画一条直线

效果如下

《一起来演示Android画布》 图片.png

画弧:

drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint) 

参数详解:
oval:矩形对象
startAngle:开始绘制的角度
sweepAngle:绘制移动的角度
useCenter:是否使用矩形的中心点(true为扇形,false为非扇形)

drawArc(float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean useCenter, Paint paint) 

参数详解:
left:矩形的左边位置
top:矩形的顶部位置
right:矩形的右边位置
bottom:矩形的下部位置
startAngle:开始绘制的角度
sweepAngle:绘制幅度
useCenter:是否使用矩形的中心点(true为扇形,false为非扇形)

那么弧是怎么绘制的呢?
(1)弧绘制的过程是按照顺时针绘制的;
(2)绘制弧需要一个矩形作为参照物;
(3)弧在矩形中是内切的;
(4)找到矩形的中心点,可以想象从该点向右画一条水平直线,其角度为0,顺时针角度为正数,逆时针角度为负数,从startAngle角度开始顺时针绘制,绘制幅度为sweepAngle。

假设矩形参数为(400, 400, 600, 800),从-30度位置开始绘制,绘制的幅度分别是60度,120度,220度,为了方便演示,我们画一个矩形作为底部。

    RectF rectF = new RectF(400, 400, 600, 800);//声明矩形
    canvas.drawRect(rectF, new Paint());
    canvas.drawArc(rectF, -30, 60, true, mPaint);//60度扇形
    //canvas.drawArc(rectF, -30, 60, false, mPaint);//60度非扇形
    //canvas.drawArc(rectF, -30, 120, true, mPaint);//120度扇形
    //canvas.drawArc(rectF, -30, 120, false, mPaint);//120度非扇形
    //canvas.drawArc(rectF, -30, 220, true, mPaint);//220度扇形
    //canvas.drawArc(rectF, -30, 220, false, mPaint);//220度非扇形

60度效果如下

《一起来演示Android画布》 图片.png
《一起来演示Android画布》 图片.png

120度效果如下

《一起来演示Android画布》 图片.png

《一起来演示Android画布》 图片.png

220度效果如下

《一起来演示Android画布》 图片.png

《一起来演示Android画布》 图片.png

画矩形

方法:

public void drawRect(RectF rect, Paint paint);

public void drawRect(Rect r, Paint paint) ;

public void drawRect(float left, float top, float right, float bottom, Paint paint) ;

效果如下

《一起来演示Android画布》 图片.png

画圆

方法

drawCircle(float cx, float cy, float radius, Paint paint)

效果如下

《一起来演示Android画布》 图片.png

画椭圆

方法

public void drawOval(RectF oval, Paint paint) 

public void drawOval(float left, float top, float right, float bottom, Paint paint)

效果如下

《一起来演示Android画布》 图片.png

画矢量图

方法

public void drawPicture(Picture picture)

public void drawPicture(Picture picture, RectF dst) 

public void drawPicture(Picture picture, Rect dst)

使用之前请先关闭硬件加速Android开发如何关闭GPU硬件加速
但是关闭硬件加速会带来一些异常问题,所以到底关闭还是开启硬件加速,是否使用drawPicture看情况而定。

绘制位图有四种方法:
(1)使用Picture提供的draw方法绘制

    if(picture == null){
        picture = new Picture();
    }
    Canvas ca = picture.beginRecording(50, 50);
    ca.drawColor(Color.RED);
    ca.drawText("床前明月光, 地上鞋两双!", 200, 400, mPaint);
    picture.endRecording();
    picture.draw(canvas);

效果如下

《一起来演示Android画布》 图片.png

我们发现即使我这里设置的Picture大小只有50×50,红色背景依然覆盖了整个屏幕, 根据现象可以得知绘制Picture默认是覆盖整个当前画布的。

(2)使用Canvas提供的drawPicture方法绘制,并且没有矩形区域限制。

    Canvas ca = picture.beginRecording(50, 50);
    ca.drawColor(Color.RED);
    ca.drawText("床前明月光, 地上鞋两双!", 200, 400, mPaint);
    picture.endRecording();
    canvas.drawPicture(picture);

效果如下

《一起来演示Android画布》 图片.png

(3)使用Canvas提供的drawPicture方法绘制,并且有矩形区域限制

    if(picture == null){
        picture = new Picture();
    }
    Canvas ca = picture.beginRecording(50, 50);
    ca.drawColor(Color.RED);
    ca.drawCircle(100, 100, 100, mPaint);
    picture.endRecording();
    RectF rectF = new RectF(50, 50, 200, 200);
    canvas.drawPicture(picture, rectF);

效果如下

《一起来演示Android画布》 图片.png

(4)使用PictureDrawable绘制

    if(picture == null){
        picture = new Picture();
    }
    Canvas ca = picture.beginRecording(50, 50);
    ca.drawColor(Color.RED);
    ca.drawCircle(100, 100, 100, mPaint);
    picture.endRecording();
    PictureDrawable drawable = new PictureDrawable(picture);
    drawable.setBounds(0,0,500, 500);
    drawable.draw(canvas);

效果如下

《一起来演示Android画布》 图片.png

这个方法可以任意设置Picture在屏幕中的位置。

画位图

方法

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(int[] colors, int offset, int stride, float x, float y, int width, int height, boolean hasAlpha, Paint paint)
drawBitmap(int[] colors, int offset, int stride, int x, int y, int width, int height, boolean hasAlpha, Paint paint) 
drawBitmap(Bitmap bitmap, Matrix matrix, Paint paint) 
drawBitmapMesh(Bitmap bitmap, int meshWidth, int meshHeight, float[] verts, int vertOffset, int[] colors, int colorOffset, Paint paint)

(1) 根据图片左上角的位置绘画

canvas.drawBitmap(bitmap, 100,100, mPaint);

效果如下

《一起来演示Android画布》 图片.png

(2)取bitmap的某一块填充的制定区域

    //取bitmap某块的区域
    Rect rect = new Rect(0,0,bitmap.getWidth()/2,bitmap.getHeight()/2);
    //将要填充的区域
    RectF rectF = new RectF(200,200,800,800);
    canvas.drawBitmap(bitmap, rect,rectF,mPaint);

效果如下

《一起来演示Android画布》 图片.png

(3)已过时的方法

drawBitmap(int[] colors, int offset, int stride, float x, float y, int width, int height, boolean hasAlpha, Paint paint)
drawBitmap(int[] colors, int offset, int stride, int x, int y, int width, int height, boolean hasAlpha, Paint paint) 

这两个方法不需要在意。

(4)根据矩阵作用于将要绘制出的图片

   Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher);
    Matrix matrix = new Matrix();
    //旋转45度
    matrix.postRotate(45);
    //平移到屏幕的(300,400)位置
    matrix.postTranslate(300, 400);
    canvas.drawBitmap(bitmap, matrix, mPaint);

效果如下

《一起来演示Android画布》 图片.png

(5)扭曲图像的绘制

drawBitmapMesh(Bitmap bitmap, int meshWidth, int meshHeight, float[] verts, int vertOffset, int[] colors, int colorOffset, Paint paint);

参数说明:
bitmap    需要扭曲的源位图
meshWidth   控制在横向上把该源位图划成成多少格
meshHeight   控制在纵向上把该源位图划成成多少格
verts      长度为(meshWidth + 1) * (meshHeight + 1) * 2的数组,它记录了扭曲后的位图各顶点位置
vertOffset 控制verts数组中从第几个数组元素开始才对bitmap进行扭曲

效果如下

《一起来演示Android画布》 1535009798103.gif

代码:

public class CustomView extends View {

    private Paint mPaint;
    private Bitmap bitmap;
    private int MESHWIDTH = 5;//图片横向划分成5格
    private int MESHHEIHT = 5;//图片纵向划分成5格
    private int COUNT = (MESHWIDTH + 1) * (MESHHEIHT + 1);//图中有多少个点
    private float[] verts;//所有坐标的数据
    private float[] vertsChange;//改变之后的坐标数据

    public CustomView(Context context) {
    super(context);
    initVerts();
    }

    public CustomView(Context context, @Nullable AttributeSet attrs) {
    super(context, attrs);
    initVerts();
    }

    public CustomView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    initVerts();
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public CustomView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    super(context, attrs, defStyleAttr, defStyleRes);
    initVerts();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    int width = MeasureSpec.getSize(widthMeasureSpec);
    int height = MeasureSpec.getSize(heightMeasureSpec);
    if(widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST){
        //如果都是wrap_content的时候
        setMeasuredDimension(bitmap.getWidth(), bitmap.getHeight());
    }else{
        setMeasuredDimension(width, height);
    }
    }

    @Override
    protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);

    //初始化画笔
    initPaint();

    canvas.drawBitmapMesh(bitmap, MESHWIDTH, MESHHEIHT,verts, 0, null,0,mPaint);
    }

    private void initVerts(){
    bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.pic);
    verts = new float[COUNT * 2];//所有坐标的数据
    vertsChange = new float[COUNT * 2];//改变之后的坐标数据
    int index = 0;//数组坐标
    for (int y = 0;y <= MESHHEIHT;y++){
        float fy = y * bitmap.getHeight()/MESHHEIHT;
        for (int x = 0;x <= MESHWIDTH;x++){
        float fx = x * bitmap.getWidth()/MESHWIDTH;
        vertsChange[index * 2 + 0] = verts[index * 2 + 0] = fx;
        vertsChange[index * 2 + 1] = verts[index * 2 + 1] = fy;
        index += 1;
        }
    }
    }
    /**
     * 初始化画笔
     * @return
     */
    private void initPaint(){
    if(mPaint == null){
        mPaint = new Paint();
    }
    //设置画笔的颜色
    mPaint.setColor(Color.BLUE);
    //设置画笔的大小
    mPaint.setStrokeWidth(10);
    }

    /**
     * 触摸之后改变数组
     * @param cx
     * @param cy
     */
    private void changeVerts(float cx, float cy) {

    for(int i = 0; i < COUNT * 2; i += 2) {
        float fx = cx - vertsChange[i + 0];
        float fy = cy - vertsChange[i + 1];
        float rr = fx * fx + fy * fy;
        //计算每个坐标点与当前点(cx,cy)之间的距离
        float r = (float)Math.sqrt(rr);
        //图片坐标离当前点的距离比对角线的距离,求出变化幅度(这里的算法可以自定义)
        float fudu = (float) (r / Math.sqrt(bitmap.getWidth() * bitmap.getWidth() + bitmap.getHeight() * bitmap.getHeight()));
        verts[i + 0] = vertsChange[i + 0] + vertsChange[i + 0] * fudu;
        verts[i + 1] = vertsChange[i + 1] + vertsChange[i + 1] * fudu;
    }
    //重绘
    invalidate();
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
    changeVerts(event.getX(), event.getY());
    return super.onTouchEvent(event);
    }
}

画背景色

方法

canvas.drawColor(Color.BLUE);
canvas.drawColor(Color.BLUE, PorterDuff.Mode.LIGHTEN);
canvas.drawRGB(0, 0, 255);
drawARGB(int a, int r, int g, int b) //带透明度,第一个参数就是透明度

这四个方法的使用不难理解,唯一需要讲解的是第二个方法中的Mode:

看枚举

public static enum Mode {
    ADD,
    CLEAR,
    DARKEN,
    DST,
    DST_ATOP,
    DST_IN,
    DST_OUT,
    DST_OVER,
    LIGHTEN,
    MULTIPLY,
    OVERLAY,
    SCREEN,
    SRC,
    SRC_ATOP,
    SRC_IN,
    SRC_OUT,
    SRC_OVER,
    XOR;

    private Mode() {
    }
}

看图

《一起来演示Android画布》 1344993853_6311.JPG

1.PorterDuff.Mode.CLEAR
所绘制不会提交到画布上。

2.PorterDuff.Mode.SRC
显示上层绘制图片

3.PorterDuff.Mode.DST
显示下层绘制图片

4.PorterDuff.Mode.SRC_OVER
正常绘制显示,上下层绘制叠盖。

5.PorterDuff.Mode.DST_OVER
上下层都显示。下层居上显示。

6.PorterDuff.Mode.SRC_IN
取两层绘制交集。显示上层。

7.PorterDuff.Mode.DST_IN
取两层绘制交集。显示下层。

8.PorterDuff.Mode.SRC_OUT
取上层绘制非交集部分。

9.PorterDuff.Mode.DST_OUT
取下层绘制非交集部分。

10.PorterDuff.Mode.SRC_ATOP
取下层非交集部分与上层交集部分

11.PorterDuff.Mode.DST_ATOP
取上层非交集部分与下层交集部分

12.PorterDuff.Mode.XOR
取两层绘制非交集。两层绘制非交集。

13.PorterDuff.Mode.DARKEN
上下层都显示。变暗

14.PorterDuff.Mode.LIGHTEN
上下层都显示。变量

15.PorterDuff.Mode.MULTIPLY
取两层绘制交集

16.PorterDuff.Mode.SCREEN
上下层都显示。

如果想了解更多PorterDuff.Mode,请乘坐下方的飞机:
各个击破搞明白PorterDuff.Mode

本人刚才坐飞机刚回来,现在结合以上资料的案例一一给大家演示不同混合模式的效果:
PorterDuff.Mode详解

注意,默认情况下是给默认画布添加背景色,当有裁剪操作时,默认画布就会变成裁剪区域的画布。

画圆角矩形

方法

public void drawRoundRect(RectF rect, float rx, float ry, Paint paint)

public void drawRoundRect(float left, float top, float right, float bottom, float rx, float ry, Paint paint) 

效果如下

《一起来演示Android画布》 图片.png

画路径

方法

//矩形路径
public void addRect(RectF rect, Path.Direction dir) 
//矩形路径
public void addRect(float left, float top, float right, float bottom, Path.Direction dir) 
//椭圆路径
public void addOval(RectF oval, Path.Direction dir) 
//椭圆路径
public void addOval(float left, float top, float right, float bottom, Path.Direction dir) 
//圆路径
public void addCircle(float x, float y, float radius, Path.Direction dir)
//弧路径
public void addArc(RectF oval, float startAngle, float sweepAngle)
//弧路径
public void addArc(float left, float top, float right, float bottom, float startAngle, float sweepAngle) 
//椭圆路径
public void addRoundRect(RectF rect, float rx, float ry, Path.Direction dir)
//椭圆路径
public void addRoundRect(float left, float top, float right, float bottom, float rx, float ry, Path.Direction dir)
//椭圆路径
public void addRoundRect(RectF rect, float[] radii, Path.Direction dir) 
//椭圆路径
public void addRoundRect(float left, float top, float right, float bottom, float[] radii, Path.Direction dir) 
//椭圆路径
public void addPath(Path src, float dx, float dy) 
//椭圆路径
public void addPath(Path src)
//椭圆路径
public void addPath(Path src, Matrix matrix) 

效果如下
(1)矩形路径

《一起来演示Android画布》 图片.png

(2)圆路径

《一起来演示Android画布》 图片.png

(3)弧路径

《一起来演示Android画布》 图片.png

(4)椭圆路径

《一起来演示Android画布》 图片.png

(5)圆角矩形路径

《一起来演示Android画布》 图片.png

画文字

方法

public void drawText(char[] text, int index, int count, float x, float y, Paint paint) 

public void drawText(String text, float x, float y, Paint paint) 

public void drawText(String text, int start, int end, float x, float y, Paint paint) 

public void drawText(CharSequence text, int start, int end, float x, float y, Paint paint) 

public void drawTextOnPath(char[] text, int index, int count, Path path, float hOffset, float vOffset, Paint paint) 

public void drawTextOnPath(String text, Path path, float hOffset, float vOffset, Paint paint)

public void drawTextRun(char[] text, int index, int count, int contextIndex, int contextCount, float x, float y, boolean isRtl, Paint paint)

public void drawTextRun(CharSequence text, int start, int end, int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint paint)

代码

char[] chars = {'a','b','c','d'};
//从数组第二位开始画两个数据
canvas.drawText(chars, 1, 2,100, 100, mPaint);

效果如下

《一起来演示Android画布》 图片.png

代码

canvas.drawText("我是中国人",100, 100, mPaint);

效果如下

《一起来演示Android画布》 图片.png

代码

//从角标2画到角标4
canvas.drawText("我是中国人",2, 4, 100, 100, mPaint);

效果如下

《一起来演示Android画布》 图片.png

代码

//这个方法用于处理特殊文字,阿拉伯文字就是特殊文字之一
canvas.drawTextRun("عربىعربىعربىعربى", 0, 16, 0, 16, 100, 100, true, mPaint);

参数描述:
text:要绘制的文字
start:从那个字开始绘制
end:绘制到哪个字结束
contextStart:上下文的起始位置。contextStart 需要小于等于 start
contextEnd:上下文的结束位置。contextEnd 需要大于等于 end
x:文字左边的坐标
y:文字的基线坐标
isRtl:是否是 RTL(Right-To-Left,从右向左)
大小关系:
contextStart <= start <= end<= contextEnd

代码

    Path path=new Path();
    path.addCircle(400,400,200, Path.Direction.CW);
    //根据路径绘制文字
    canvas.drawTextOnPath("床前明月光,地上鞋两双!",path, 0, 0, mPaint);

效果如下

《一起来演示Android画布》 图片.png

画点

方法

public void drawPoint(float x, float y, Paint paint) 

public void drawPoints(float[] pts, int offset, int count, Paint paint) 

public void drawPoints(float[] pts, Paint paint) 

代码

    float[] pos = {100, 100,100,200,200,200,300,300,400,400,400,500,500,500};
    canvas.drawPoints(pos, mPaint);

效果如下

《一起来演示Android画布》 图片.png

画画笔(将画笔设置的颜色和透明度铺满画布)

画笔配置

/**
 * 初始化画笔
 * @return
 */
private void initPaint(){
    if(mPaint == null){
        mPaint = new Paint();
    }
    //设置画笔的颜色
    mPaint.setColor(Color.RED);
    //设置画笔的大小
    mPaint.setStrokeWidth(50);
    //设置文字大小
    mPaint.setTextSize(50);
    //设置画笔的样式
    mPaint.setStyle(Paint.Style.STROKE);
}

代码

canvas.drawPaint(mPaint);//将画笔设置的颜色和透明度铺满画布

效果如下

《一起来演示Android画布》 图片.png

  • 方法drawVertices待定(研究中)

裁剪画布

方法

public boolean clipRect(RectF rect, Op op) 

public boolean clipRect(Rect rect, Op op)

public boolean clipRect(RectF rect)

public boolean clipOutRect(RectF rect) 

public boolean clipRect(Rect rect) 

public boolean clipOutRect(Rect rect) 

public boolean clipRect(float left, float top, float right, float bottom, Op op)

public boolean clipRect(float left, float top, float right, float bottom) 

public boolean clipOutRect(float left, float top, float right, float bottom) 

public boolean clipRect(int left, int top, int right, int bottom) 

public boolean clipOutRect(int left, int top, int right, int bottom)

public boolean clipPath(Path path, Op op)

public boolean clipPath(Path path) 

public boolean clipOutPath(Path path) 

根据以上的方法可以看出,裁剪有两种,一种的矩形,另一种是自定义路径,两者用法是一样的。

对op参数的理解:以剪裁两次的区域分别为A,B来区别

  • Region.Op.DIFFERENCE:剪裁出差异的部分,类似 A-B 部分
  • Region.Op.REPLACE:后剪裁B的覆盖剪裁的A
  • Region.Op.REVERSE_DEFFERENCE:剪裁出差异的部分,类似 B-A 部分
  • Region.Op.INTERSECT:剪裁出相交的部分,类似 A交B 部分
  • Region.Op.UNION:剪裁出AB合并的部分,类似** AUB**
  • Region.Op.XOR:是** (AUB)-(A交B)** 刚好与** A交B** 相对

绘制两个路径,代码如下

    Path path1 = new Path();
    path1.addRect(300, 600, 600, 900, Path.Direction.CCW);
    canvas.clipPath(path1);

    Path path2 = new Path();
    path2.addCircle(300, 600, 300,Path.Direction.CCW);
    canvas.clipPath(path2);

    canvas.drawColor(Color.BLUE);

展示效果

《一起来演示Android画布》 图片.png

显然, 默认情况下, 两个路径取相交的部分,也就是说OP的默认值是Region.Op.INTERSECT。

下面分别对OP进行验证:
(1)Region.Op.DIFFERENCE和Region.Op.DIFFERENCE
代码

    Path path1 = new Path();
    path1.addRect(300, 600, 600, 900, Path.Direction.CCW);
    canvas.clipPath(path1, Region.Op.DIFFERENCE);

    Path path2 = new Path();
    path2.addCircle(300, 600, 300,Path.Direction.CCW);
    canvas.clipPath(path2, Region.Op.DIFFERENCE);

    canvas.drawColor(Color.BLUE);

效果如下

《一起来演示Android画布》 图片.png

  • 在整个画布上取非矩形路径区域,假设为A
  • 在A上取非圆形路径区域。

(2)Region.Op.DIFFERENCE和Region.Op.REPLACE
代码

    Path path1 = new Path();
    path1.addRect(300, 600, 600, 900, Path.Direction.CCW);
    canvas.clipPath(path1, Region.Op.REPLACE);

    Path path2 = new Path();
    path2.addCircle(300, 600, 300,Path.Direction.CCW);
    canvas.clipPath(path2, Region.Op.DIFFERENCE);

效果如下

《一起来演示Android画布》 图片.png

  • 矩形裁剪路径的模式是Region.Op.REPLACE,就是取其本身区域,假设为A
  • 圆形裁剪路径模式是Region.Op.DIFFERENCE,就在A上取非圆区域。

现在,我们将模式反过来。
代码

    Path path1 = new Path();
    path1.addRect(300, 600, 600, 900, Path.Direction.CCW);
    canvas.clipPath(path1, Region.Op.DIFFERENCE);

    Path path2 = new Path();
    path2.addCircle(300, 600, 300,Path.Direction.CCW);
    canvas.clipPath(path2, Region.Op.REPLACE);

    canvas.drawColor(Color.BLUE);

效果如下

《一起来演示Android画布》 图片.png

  • 矩形路径区域完全被圆形路径区域覆盖。

(3)Region.Op.DIFFERENCE和Region.Op.REVERSE_DIFFERENCE
代码

    Path path1 = new Path();
    path1.addRect(300, 600, 600, 900, Path.Direction.CCW);
    canvas.clipPath(path1, Region.Op.REVERSE_DIFFERENCE);

    Path path2 = new Path();
    path2.addCircle(300, 600, 300,Path.Direction.CCW);
    canvas.clipPath(path2, Region.Op.DIFFERENCE);

    canvas.drawColor(Color.BLUE);

效果如下

《一起来演示Android画布》 图片.png

  • 矩形区域使用的裁剪模式是Region.Op.REVERSE_DIFFERENCE,也就是说,矩形区域减去整个画布区域, 结果就是空区域。假设为A。
  • 圆形区域使用的裁剪模式是Region.Op.DIFFERENCE,因为当前路径的绘制是根据A画布而言的,A画布为空,所以当前再怎么绘制也为空。

我们将模式反过来
代码

    Path path1 = new Path();
    path1.addRect(300, 600, 600, 900, Path.Direction.CCW);
    canvas.clipPath(path1, Region.Op.DIFFERENCE);

    Path path2 = new Path();
    path2.addCircle(300, 600, 300,Path.Direction.CCW);
    canvas.clipPath(path2, Region.Op.REVERSE_DIFFERENCE);

    canvas.drawColor(Color.BLUE);

效果如下

《一起来演示Android画布》 图片.png

  • 矩形路径的裁剪模式为Region.Op.DIFFERENCE,所以先取除矩形路径区域的其他区域,假设为A。
  • 由于圆形路径的裁剪模式为Region.Op.REVERSE_DIFFERENCE,所以用圆形路径的区域减去A。

(4)Region.Op.DIFFERENCE和Region.Op.INTERSECT
代码

    Path path1 = new Path();
    path1.addRect(300, 600, 600, 900, Path.Direction.CCW);
    canvas.clipPath(path1, Region.Op.DIFFERENCE);

    Path path2 = new Path();
    path2.addCircle(300, 600, 300,Path.Direction.CCW);
    canvas.clipPath(path2, Region.Op.INTERSECT);

    canvas.drawColor(Color.BLUE);

效果如下

《一起来演示Android画布》 图片.png

  • 取矩形区域不同的地方,假设为A。
  • 取圆形区域与A相交的地方。

(5)Region.Op.INTERSECT和Region.Op.INTERSECT
代码

    Path path1 = new Path();
    path1.addRect(300, 600, 600, 900, Path.Direction.CCW);
    canvas.clipPath(path1, Region.Op.INTERSECT);

    Path path2 = new Path();
    path2.addCircle(300, 600, 300,Path.Direction.CCW);
    canvas.clipPath(path2, Region.Op.INTERSECT);

    canvas.drawColor(Color.BLUE);

效果如下

《一起来演示Android画布》 图片.png

  • 取画布和矩形区域相交的区域, 假设为A
  • 取A和圆形区域相交的地方。

(6)Region.Op.INTERSECT和Region.Op.UNION
代码

    Path path1 = new Path();
    path1.addRect(300, 600, 600, 900, Path.Direction.CCW);
    canvas.clipPath(path1, Region.Op.INTERSECT);

    Path path2 = new Path();
    path2.addCircle(300, 600, 300,Path.Direction.CCW);
    canvas.clipPath(path2, Region.Op.UNION);

    canvas.drawColor(Color.BLUE);

效果如下

《一起来演示Android画布》 图片.png

  • 取画布和矩形区域相交的区域, 假设为A
  • 取A和圆形区域合并的区域。

(7)Region.Op.INTERSECT和Region.Op.XOR
代码

    Path path1 = new Path();
    path1.addRect(300, 600, 600, 900, Path.Direction.CCW);
    canvas.clipPath(path1, Region.Op.INTERSECT);

    Path path2 = new Path();
    path2.addCircle(300, 600, 300,Path.Direction.CCW);
    canvas.clipPath(path2, Region.Op.XOR);

    canvas.drawColor(Color.BLUE);

效果如下

《一起来演示Android画布》 图片.png

  • 取画布和矩形区域相交的区域, 假设为A
  • A和圆形区域相并的区域减去A和圆形区域相交的区域。

先举这么多例子。

Canvas的保存与恢复

保存:用于保存Canvas状态。

canvas.save();

恢复:用于恢复Canvas状态。

canvas.restore();

Canvas的变幻操作

  • 平移操作

      canvas.drawRect(new Rect(0, 0, 200, 200), mPaint);
      canvas.translate(300, 300);
      canvas.drawRect(new Rect(0, 0, 400, 400), mPaint);
    

《一起来演示Android画布》 图片.png

  • 缩放操作

      mPaint.setColor(Color.CYAN);
      canvas.drawRect(new Rect(0, 0, 200, 200), mPaint);
      mPaint.setColor(Color.RED);
      canvas.scale(0.5f, 0.5f);
      canvas.drawRect(new Rect(300, 300, 500, 500), mPaint);
    

《一起来演示Android画布》 图片.png

  • 旋转操作

无基准点的情况:

    mPaint.setColor(Color.CYAN);
    canvas.drawRect(new Rect(0, 0, 200, 200), mPaint);
    mPaint.setColor(Color.RED);
    canvas.rotate(-30);
    canvas.drawRect(new Rect(300, 300, 500, 500), mPaint);

《一起来演示Android画布》 图片.png

有基准点的情况:

    mPaint.setColor(Color.CYAN);
    canvas.drawRect(new Rect(0, 0, 200, 200), mPaint);
    mPaint.setColor(Color.RED);
    canvas.rotate(30, 0, 300);
    canvas.drawRect(new Rect(0, 300, 400, 400), mPaint);

《一起来演示Android画布》 图片.png

  • 错切操作

    • sx:将画布在 x 方向上倾斜相应的角度,sx 为倾斜角度的 tan 值;
    • sy:将画布在 y 轴方向上倾斜相应的角度,sy 为倾斜角度的 tan 值;
      比如在 X 轴方向上倾斜45度,tan45=1;
    mPaint.setColor(Color.CYAN);
    canvas.drawColor(Color.RED);
    canvas.skew(1,0);
    canvas.drawRect(new Rect(0, 0, 200, 200), mPaint);

《一起来演示Android画布》 图片.png

其它方法

  • isHardwareAccelerated:判断是否有硬件加速
  • saveLayerAlpha:本身和save方法差不多,但是它单独分配了一个画布用于绘制图层。它定义了一个画布区域(可设置透明度),此方法之后的所有绘制都在此区域中绘制,直到调用canvas.restore()方法。例如:在调用saveLayerAlpha方法之前绘制了一个“圆形”,在调用saveLayerAlpha方法之后绘制了一个“圆形”此时这两个圆形并不在同一个图层。
  • isOpaque:检测是否支持透明
  • quickReject:判断是否没和某个矩形相交,避免过度绘制
  • clipOutPath、clipOutRect:这两个方法在API 28之后才出现
  • concat或setMatrix:使用矩阵来操作画布
    原文作者:NoBugException
    原文地址: https://www.jianshu.com/p/82d70697cf45
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞