Canvas的功能

Canvas的功能

一. 如何在一屏幕上绘图

三个概念:

  1. 需要有画布Canvas
  2. 需要有笔Paint
  3. 需要有坐标系,画笔默认是在左上角(0,0)位置绘制的。我们可以通过移动画布坐标原点的形式,实现在不同位置绘制。
1. 移动坐标原点
  简单绘制一个图

我们将画笔Canvas.translate(100,400)移动之后再进行绘制,这时画布的原点已经变化了。

2. 视图坐标系

理论上 Canvas 这张纸是没有边界的,但是我们的手机屏幕是有界的。我们可以理解为我们透过一个方形的洞(手机屏幕)看一张巨画(Canvas)。

而这里我们就又存在一个问题了,因为刚才的移动,我们是移动的原点,也就是说我们的画布是静止不动的,只是落笔点一直在变动,这就导致我们绘制的图对于用户来说是看不全的,所以我们需要进行移动 方形的洞 来查看这幅画。

举个例子,我们要查看最开始所说的画,可以通过移动 Screen框来查看这幅画,而这里又出现了一个坐标系,这一坐标系则为 视图坐标系,通过 scrollerToscrollerBy 进行移动该Screen框,正数则往正半轴,负数则往负半轴。

3. 小结

自定义控件中存在两个坐标系需要明确,用一句话总结如下:

  1. 绘图坐标系:决定我们的绘制的坐标
  2. 视图坐标系:决定我们所看到的画布范围

二、Canvas的剪刀手API

Canvas 中以 clip开头 的公有方法,用于裁剪画布的内容。裁剪之后画布之外将无法绘制, 我们抽取比较好玩的参数类型为Path的方法来分享,其余的都可以一一映射进来。

1、clipPath裁剪任意形状画布

public boolean clipPath(@NonNull Path path)

描述: 只留下 path内 的画布区域,而处于path范围之外的则不显示。

举个例子: 我们先准备好一个心形的路径Path,然后调用 clipPath 从画布中将此路径内的区域 “裁剪” 下来,最后为了我们观察,使用drawColor “染”上酒红色。

// 第一步:创建 心形路径 mPath
....省略,具体请移步demo(我会贴在末尾的)

// 第二步:从画布 canvas 裁剪下心形路径之内的区域
canvas.clipPath(mPath);

// 第三步:涂酒红色
canvas.drawColor(mBgColor);

【这个有一个网上的】 自带美感的贝塞尔曲线原理与实战

画心型路径基本是使用贝塞尔曲线的( 路径关系可以参考这个画心),路径关系如文章所示本章仅讲解和绘制相关的知识。

效果如图所示:

代码如下:

/**
 * 参考链接:https://blog.csdn.net/BigBoySunshine/article/details/53898806
 */
public class HartView extends View {

    public HartView(Context context) {
        super(context);
        init();
    }


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

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

    private void init() {
        mPaint.setAntiAlias(true);
        mPaint.setColor(Color.RED);
        mPaint.setStyle(Paint.Style.FILL);

    }

    private Paint mPaint = new Paint();
    private Path mPath = new Path();


    @Override
    protected void onDraw(Canvas canvas) {


        int mWidth = getWidth();
        int mHeight = getHeight();

        for (int i = 0 + 180; i < 361 + 180; i++) {

            double sit = i * 2 * Math.PI / 360d;

            double x = 20 * 16 * Math.pow(Math.sin(sit), 3);
            double y = 20 * (13 * Math.cos(sit) - 5 * Math.cos(2 * sit) - 2 * Math.cos(3 * sit) - Math.cos(4 * sit));
            if (i == 180) {
                mPath.moveTo(mWidth / 2 + (float) (x), mHeight / 2 - (float) (y));scrcpy
            }
            mPath.lineTo(mWidth / 2 + (float) (x), mHeight / 2 - (float) (y));

            if (canvas != null)
                canvas.drawPath(mPath, mPaint);
        }

    }
}
/**
 * 参考链接:https://blog.csdn.net/BigBoySunshine/article/details/53898806
 */
public class OutsideHartView extends View {

    public OutsideHartView(Context context) {
        super(context);
        init();
    }


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

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

    private void init() {
        mPaint.setAntiAlias(true);
        mPaint.setColor(Color.RED);
        mPaint.setStyle(Paint.Style.FILL);

    }

    private Paint mPaint = new Paint();
    private Path mPath = new Path();


    @Override
    protected void onDraw(Canvas canvas) {


        int mWidth = getWidth();
        int mHeight = getHeight();

        for (int i = 0 + 180; i < 361 + 180; i++) {

            double sit = i * 2 * Math.PI / 360d;

            double x = 20 * 16 * Math.pow(Math.sin(sit), 3);
            double y = 20 * (13 * Math.cos(sit) - 5 * Math.cos(2 * sit) - 2 * Math.cos(3 * sit) - Math.cos(4 * sit));
            if (i == 180) {
                mPath.moveTo(mWidth / 2 + (float) (x), mHeight / 2 - (float) (y));
            }
            mPath.lineTo(mWidth / 2 + (float) (x), mHeight / 2 - (float) (y));
 
        }
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            canvas.clipOutPath(mPath);
        }

        canvas.drawColor(getContext().getResources().getColor(R.color.color_cd6155));
    }
2. clipRect裁剪矩形
public boolean clipRect(float left, float top, float right, float bottom)

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

public boolean clipRect(@NonNull Rect rect)

public boolean clipRect(@NonNull RectF rect)
3. clipOutPath裁剪保留path之外的区域
public boolean clipOutPath(@NonNull Path path)

描述: 只留下 path外 的画布区域,而处于path范围之内的则不显示。(与clipPath的作用范围正好相反)

值得注意的是,该方法只能在API26版本以上调用。 低版本我们使用下一小节介绍的方法

举个例子:

我们先准备好一个心形的路径Path,然后调用 clipOutPath 从画布中将此路径之外的区域 “裁剪” 下来,最后为了我们观察,使用 drawColor “染”上酒红色。

// 第一步:创建 心形路径 mPath 
....省略,具体请移步github 

// 第二步:从画布 canvas 裁剪下心形路径之外的区域 
canvas.clipOutPath(mPath);

// 第三步:涂酒红色 
canvas.drawColor(mBgColor);

实现效果如下:

代码如下:

/**
 * 参考链接:https://blog.csdn.net/BigBoySunshine/article/details/53898806
 */
public class OutsideHartView extends View {

    public OutsideHartView(Context context) {
        super(context);
        init();
    }


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

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

    private void init() {
        mPaint.setAntiAlias(true);
        mPaint.setColor(Color.RED);
        mPaint.setStyle(Paint.Style.FILL);

    }

    private Paint mPaint = new Paint();
    private Path mPath = new Path();


    @Override
    protected void onDraw(Canvas canvas) {


        int mWidth = getWidth();
        int mHeight = getHeight();

        for (int i = 0 + 180; i < 361 + 180; i++) {

            double sit = i * 2 * Math.PI / 360d;

            double x = 20 * 16 * Math.pow(Math.sin(sit), 3);
            double y = 20 * (13 * Math.cos(sit) - 5 * Math.cos(2 * sit) - 2 * Math.cos(3 * sit) - Math.cos(4 * sit));
            if (i == 180) {
                mPath.moveTo(mWidth / 2 + (float) (x), mHeight / 2 - (float) (y));
            }
            mPath.lineTo(mWidth / 2 + (float) (x), mHeight / 2 - (float) (y));
 
        }
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            canvas.clipOutPath(mPath);
        }

       值得注意的是,该方法只能在API26版本以上调用。 低版本我们使用下一小节介绍的方法

举个例子:

我们先准备好一个心形的路径Path,然后调用 clipOutPath 从画布中将此路径之外的区域 “裁剪” 下来,最后为了我们观察,使用 drawColor “染”上酒红色。

 canvas.drawColor(getContext().getResources().getColor(R.color.color_cd6155));
    }

此类型的方法还有以下这几个,但他们的裁剪范围均为矩形

public boolean clipOutRect(float left, float top, float right, float bottom)
public boolean clipOutRect(int left, int top, int right, int bottom)
public boolean clipOutRect(@NonNull Rect rect)
public boolean clipOutRect(@NonNull RectF rect)
4、clipPath 裁剪保留区域

clipPathclipOutPath 一样,都是对画布的裁剪,不会保留空白像素,之所以这么说是因为笔者在实际工作过程中遇到过,因为通过混色方案实现过View的压感效果,但是如果这个View在可见区域外其实保留了空白像素,这种情况混色将会出现原本圆角位置出现四个阴影直角效果的情况。

public boolean clipPath(@NonNull Path path, @NonNull Region.Op op)

描述: 在画布上进行使用 path 路径进行操作,至于其作用由 op 决定。

描述比较抽象,我们通过例子来体会。但在上例子前,我们需要先了解下 Region.Op 这个枚举类型,具体内容代码如下

public enum Op {
    // A: 为我们先裁剪的路径
    // B: 为我们后裁剪的路径

    // A形状中不同于B的部分显示出来
    DIFFERENCE(0),
    // A和B交集的形状
    INTERSECT(1),
    // A和B的全集
    UNION(2),
    // A和B的全集形状,去除交集形状之后的部分
    XOR(3),
    // B形状中不同于A的部分显示出来
    REVERSE_DIFFERENCE(4),
    // 只显示B的形状
    REPLACE(5);

	// ...省略不相关代码
}

通过源码可以知道共有六种类型。值得一提的有以下两点:

1)clipOutPath 方法中使用的类型就是 DIFFERENCE,换而言之,我们可以使用以下代码代替,解决在API26 以下无法使用的问题clipOutPath 方法的问题

clipPath(mPath, Region.Op.DIFFERENCE)

2)clipPath 方法中使用的类型就是 INTERSECT,换而言之,我们可以使用以下代码代替

clipPath(mPath, Region.Op.INTERSECT)
复制代码

举些例子:

接下来我们一个个讲解这六种类型,两次裁剪比较能体现出 Region.Op 参数的作用,所以我们接下来的例子需要使用两个路径:

1、心形路径 (下列例子中的 A

2、圆形路径 (下列例子中的 B

(1)DIFFERENCE

描述: A形状中不同于B的部分显示出来

效果图: 红色即为最终裁剪留下区域

代码如下:

    @Override
    protected void onDraw(Canvas canvas) {


        int mWidth = getWidth();
        int mHeight = getHeight();


        for (int i = 0 + 180; i < 361 + 180; i++) {

            double sit = i * 2 * Math.PI / 360d;

            double x = 20 * 16 * Math.pow(Math.sin(sit), 3);
            double y = 20 * (13 * Math.cos(sit) - 5 * Math.cos(2 * sit) - 2 * Math.cos(3 * sit) - Math.cos(4 * sit));
            if (i == 180) {
                mPath.moveTo(mWidth / 2 + (float) (x), mHeight / 2 - (float) (y));
            }
            mPath.lineTo(mWidth / 2 + (float) (x), mHeight / 2 - (float) (y));
        }

        canvas.clipPath(mPath, Region.Op.DIFFERENCE);

        canvas.save();
        mPaint.setColor(0xaa6200EE);
        canvas.drawCircle(mWidth / 2, mHeight / 2 + 400, 400, mPaint);

    }

(2)INTERSECT

描述: A和B交集的形状

效果图: 红色和蓝色交界的部分即为最终裁剪留下区域

代码如下:

    @Override
    protected void onDraw(Canvas canvas) {


        int mWidth = getWidth();
        int mHeight = getHeight();


        for (int i = 0 + 180; i < 361 + 180; i++) {

            double sit = i * 2 * Math.PI / 360d;

            double x = 20 * 16 * Math.pow(Math.sin(sit), 3);
            double y = 20 * (13 * Math.cos(sit) - 5 * Math.cos(2 * sit) - 2 * Math.cos(3 * sit) - Math.cos(4 * sit));
            if (i == 180) {
                mPath.moveTo(mWidth / 2 + (float) (x), mHeight / 2 - (float) (y));
            }
            mPath.lineTo(mWidth / 2 + (float) (x), mHeight / 2 - (float) (y));
        }

        canvas.clipPath(mPath, Region.Op.INTERSECT);

        canvas.save();
        mPaint.setColor(0xaa6200EE);
        canvas.drawCircle(mWidth / 2, mHeight / 2 + 400, 400, mPaint);
    }

(3)UNION

描述: A和B的全集,安卓版本大于等于28不支持 ,Android.P = 28;

效果图: 红色即为最终裁剪留下区域

效果如下:

【TODO需要补齐效果】

代码如下:

    @Override
    protected void onDraw(Canvas canvas) {


        int mWidth = getWidth();
        int mHeight = getHeight();


        for (int i = 0 + 180; i < 361 + 180; i++) {

            double sit = i * 2 * Math.PI / 360d;

            double x = 20 * 16 * Math.pow(Math.sin(sit), 3);
            double y = 20 * (13 * Math.cos(sit) - 5 * Math.cos(2 * sit) - 2 * Math.cos(3 * sit) - Math.cos(4 * sit));
            if (i == 180) {
                mPath.moveTo(mWidth / 2 + (float) (x), mHeight / 2 - (float) (y));
            }
            mPath.lineTo(mWidth / 2 + (float) (x), mHeight / 2 - (float) (y));
        }

        canvas.clipPath(mPath, Region.Op.UNION);

        canvas.save();
        mPaint.setColor(0xaa6200EE);
        canvas.drawCircle(mWidth / 2, mHeight / 2 + 400, 400, mPaint);
    }

4)XOR

描述: A和B的全集形状,去除交集形状之后的部分 ,安卓版本大于等于28不支持 ,Android.P = 28;

效果图: 红色即为最终裁剪留下区域

img

效果如下:

【TODO需要补齐效果】

代码如下:

    @Override
    protected void onDraw(Canvas canvas) {


        int mWidth = getWidth();
        int mHeight = getHeight();


        for (int i = 0 + 180; i < 361 + 180; i++) {

            double sit = i * 2 * Math.PI / 360d;

            double x = 20 * 16 * Math.pow(Math.sin(sit), 3);
            double y = 20 * (13 * Math.cos(sit) - 5 * Math.cos(2 * sit) - 2 * Math.cos(3 * sit) - Math.cos(4 * sit));
            if (i == 180) {
                mPath.moveTo(mWidth / 2 + (float) (x), mHeight / 2 - (float) (y));
            }
            mPath.lineTo(mWidth / 2 + (float) (x), mHeight / 2 - (float) (y));
        }

        canvas.clipPath(mPath, Region.Op.XOR);

        canvas.save();
        mPaint.setColor(0xaa6200EE);
        canvas.drawCircle(mWidth / 2, mHeight / 2 + 400, 400, mPaint);

    }

(5)REVERSE_DIFFERENCE

描述: B形状中不同于A的部分显示出来,安卓版本大于等于28不支持 ,Android.P = 28;

效果图: 红色即为最终裁剪留下区域

效果如下:

【TODO需要补齐效果】

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sGSf8Y09-1627377521198)(https://user-gold-cdn.xitu.io/2019/4/27/16a5ce60957fdfdb?imageView2/0/w/1280/h/960/format/webp/ignore-error/1)]

代码如下:

    @Override
    protected void onDraw(Canvas canvas) {


        int mWidth = getWidth();
        int mHeight = getHeight();


        for (int i = 0 + 180; i < 361 + 180; i++) {

            double sit = i * 2 * Math.PI / 360d;

            double x = 20 * 16 * Math.pow(Math.sin(sit), 3);
            double y = 20 * (13 * Math.cos(sit) - 5 * Math.cos(2 * sit) - 2 * Math.cos(3 * sit) - Math.cos(4 * sit));
            if (i == 180) {
                mPath.moveTo(mWidth / 2 + (float) (x), mHeight / 2 - (float) (y));
            }
            mPath.lineTo(mWidth / 2 + (float) (x), mHeight / 2 - (float) (y));
        }

        canvas.clipPath(mPath, Region.Op.REVERSE_DIFFERENCE);

        canvas.save();
        mPaint.setColor(0xaa6200EE);
        canvas.drawCircle(mWidth / 2, mHeight / 2 + 400, 400, mPaint);

    }

(6)REPLACE

描述: 只显示B的形状,安卓版本大于等于28不支持 ,Android.P = 28;

效果图: 红色即为最终裁剪留下区域

效果如下:

【TODO需要补齐效果】

img 此类

代码如下:

    @Override
    protected void onDraw(Canvas canvas) {


        int mWidth = getWidth();
        int mHeight = getHeight();


        for (int i = 0 + 180; i < 361 + 180; i++) {

            double sit = i * 2 * Math.PI / 360d;

            double x = 20 * 16 * Math.pow(Math.sin(sit), 3);
            double y = 20 * (13 * Math.cos(sit) - 5 * Math.cos(2 * sit) - 2 * Math.cos(3 * sit) - Math.cos(4 * sit));
            if (i == 180) {
                mPath.moveTo(mWidth / 2 + (float) (x), mHeight / 2 - (float) (y));
            }
            mPath.lineTo(mWidth / 2 + (float) (x), mHeight / 2 - (float) (y));
        }

        canvas.clipPath(mPath, Region.Op.REPLACE);

        canvas.save();
        mPaint.setColor(0xaa6200EE);
        canvas.drawCircle(mWidth / 2, mHeight / 2 + 400, 400, mPaint);

    }
4. 代码实战

绘制心型水波纹

  1. 绘制心形
  2. 绘制波纹
  3. 组合
  4. 动画

效果如下:

通过画布裁剪去掉无关部分

三、理解Canvas.save(),Canvas.restore(),Canvas.restoreToCount()函数

API备注
**Canvas save **把Canvas 的信息保存,压入栈
**Canvas restore **恢复到最近的一个保存点。出栈。
**restoreToCount **恢复到特定的保存点

当前矩阵变换例如:平移translate(),缩放scale(),以及旋转rotate()等

Canvas的save()、restore()这两个方法字面意思就是保存、恢复,但为什么要保存和回复呢?不保存会怎么样?

其实可以理解为Canvas.store()就是将当前的Canvas压入栈做备份,中间可能会经过若干的矩阵变换,这些变化可能是平移、也可能是旋转、缩放等。在我们绘制完成之后只需要调用Canvas.restore()就可以将画布恢复到原来的位置。但是这个操作过程中是不会改变画布上已经绘制的图像内容的,也就是说Path、bitmap等是不受影响的。压栈记录的其实是对画布的操作。

下面我们来做一个简单的实验我们就可以理解了。

  1. 我们创建一个画布

    绘制如下Path

    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        mPaint.setColor(0xFF000000);
        mPaint.setAntiAlias(true);
        mPaint.setStrokeWidth(10);
        canvas.drawLine(0, 0, 100, 0, mPaint);
        canvas.drawLine(0, 100, 0, 0, mPaint);
        canvas.translate(100, 100);
        canvas.drawLine(0, 0, 100, 0, mPaint);
        canvas.drawLine(0, 100, 0, 0, mPaint);
        canvas.translate(100, 100);
        canvas.drawLine(0, 0, 100, 0, mPaint);
        canvas.drawLine(0, 100, 0, 0, mPaint);
        }
    

    效果如下:

  1. 增加Canvas.save()代码,然后继续绘制发现没有任何影响

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        mPaint.setColor(0xFF000000);
        mPaint.setAntiAlias(true);
        mPaint.setStrokeWidth(10);
        canvas.drawLine(0, 0, 100, 0, mPaint);
        canvas.drawLine(0, 100, 0, 0, mPaint);
        canvas.translate(100, 100);
        canvas.drawLine(0, 0, 100, 0, mPaint);
        canvas.drawLine(0, 100, 0, 0, mPaint);
        canvas.translate(100, 100);
        canvas.drawLine(0, 0, 100, 0, mPaint);
        canvas.drawLine(0, 100, 0, 0, mPaint);
    
        canvas.save();
    
        canvas.translate(100, 100);
        canvas.drawLine(0, 0, 100, 0, mPaint);
        canvas.drawLine(0, 100, 0, 0, mPaint);
        }
    

​ 效果如下:

  1. 使用 Canvas.restore()后,然后在执行重新执行绘制
@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    mPaint.setColor(0xFF000000);
    mPaint.setAntiAlias(true);
    mPaint.setStrokeWidth(10);
    canvas.drawLine(0, 0, 100, 0, mPaint);
    canvas.drawLine(0, 100, 0, 0, mPaint);
    canvas.translate(100, 100);
    canvas.drawLine(0, 0, 100, 0, mPaint);
    canvas.drawLine(0, 100, 0, 0, mPaint);
    canvas.translate(100, 100);
    canvas.drawLine(0, 0, 100, 0, mPaint);
    canvas.drawLine(0, 100, 0, 0, mPaint);

    canvas.save();

    canvas.translate(100, 100);
    canvas.drawLine(0, 0, 100, 0, mPaint);
    canvas.drawLine(0, 100, 0, 0, mPaint);

    canvas.restore();

    canvas.translate(100, 300);
    canvas.drawLine(0, 0, 100, 0, mPaint);
    }

绘制效果如下:

可以看到前面设置的canvas.translate(100, 100) 被还原了,可以得出结论,canvas.translate(x,y)是可以被还原的。

  1. 使用Canvas.restore()还原Canvas.rotate()Canvas.scale()
@Overridehttp://shubin.noip.cn:9000/image/image_20210723_400128987.png
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    mPaint.setColor(0xFF000000);
    mPaint.setAntiAlias(true);
    mPaint.setStrokeWidth(10);
    canvas.drawLine(0, 0, 100, 0, mPaint);
    canvas.drawLine(0, 100, 0, 0, mPaint);
    canvas.translate(100, 100);
    canvas.drawLine(0, 0, 100, 0, mPaint);
    canvas.drawLine(0, 100, 0, 0, mPaint);
    canvas.translate(100, 100);
    canvas.drawLine(0, 0, 100, 0, mPaint);
    canvas.drawLine(0, 100, 0, 0, mPaint);

    canvas.save();

    canvas.translate(100, 100);
    canvas.drawLine(0, 0, 100, 0, mPaint);
    canvas.drawLine(0, 100, 0, 0, mPaint);

    canvas.restore();

    canvas.translate(100, 300);
    canvas.drawLine(0, 0, 100, 0, mPaint);

    mPaint.setColor(0xffff00ff);
    canvas.save();
    canvas.translate(100, 100);
    canvas.rotate(90);
    canvas.drawLine(0, 0, 50, 0, mPaint);
    canvas.drawLine(0, 50, 0, 0, mPaint);
    canvas.translate(100, 100);
    canvas.scale(2, 2);
    mPaint.setColor(0xff0000ff);
    canvas.drawLine(0, 0, 50, 0, mPaint);
    canvas.drawLine(0, 50, 0, 0, mPaint);
    canvas.restore();
    canvas.drawLine(0, 0, 200, 0, mPaint);

}

绘制效果如下:

通过这个示例可以看出,Canvas.rotate()Canvas.scale()都是可以被还原的。

5.裁剪整个画布,然后执行绘制

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        mPaint.setColor(0xFF000000);
        mPaint.setAntiAlias(true);
        mPaint.setStrokeWidth(10);

        mPaint.setColor(0xffff00ff);
        canvas.save();http://shubin.noip.cn:9000/image/image_20210723_400128987.png
        canvas.drawRect(new Rect(0, 0, 600, 600), mPaint);

        canvas.translate(100, 100);
        Path path = new Path();
        path.addRect(new RectF(0, 0, 200, 200), Path.Direction.CW);
        canvas.clipPath(path);
        mPaint.setColor(0xff00ffff);
        canvas.drawRect(new Rect(0, 0, 600, 600), mPaint);
        canvas.restore();

    }

实现效果如下:

  1. 通过Canvas.restore()整个画布只后重新绘制矩形块。
@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    mPaint.setColor(0xFF000000);
    mPaint.setAntiAlias(true);
    mPaint.setStrokeWidth(10);

    mPaint.setColor(0xffff00ff);
    canvas.save();
    canvas.drawRect(new Rect(0, 0, 600, 600), mPaint);

    canvas.translate(100, 100);
    Path path = new Path();
    path.addRect(new RectF(0, 0, 200, 200), Path.Direction.CW);
    canvas.clipPath(path);
    mPaint.setColor(0xff00ffff);
    canvas.drawRect(new Rect(0, 0, 600, 600), mPaint);
    canvas.restore();
    mPaint.setColor(0xff00ffff);
    canvas.drawRect(new Rect(0, 0, 600, 600), mPaint);
}

实现效果:

Canvas的其他功能

如下图当使用 canvas.translate(100,100)方法后canvas的坐标中心就不是左上角,而是(100,100),如图左边数第一个黑色直角就是 第一次canvas.translate(50,50)后的坐标中心。我用了一个for循环对同一个canvas(注意是同一个canvas)执行了5次canvas.translate(50,50),从图中发现canvas每次都以当前坐标中心为基础移动(50,50),如上图。但是如果遇到在for循环中对canvas执行translate后不想canvas改变坐标中心怎么办?那就在canvas translate前save,后再restore。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值