天天看點

Canvas的功能Canvas的功能

Canvas的功能

Canvas的功能Canvas的功能

一. 如何在一螢幕上繪圖

三個概念:

  1. 需要有畫布Canvas
  2. 需要有筆Paint
  3. 需要有坐标系,畫筆預設是在左上角(0,0)位置繪制的。我們可以通過移動畫布坐标原點的形式,實作在不同位置繪制。

1. 移動坐标原點

簡單繪制一個圖
           
Canvas的功能Canvas的功能

我們将畫筆Canvas.translate(100,400)移動之後再進行繪制,這時畫布的原點已經變化了。

Canvas的功能Canvas的功能

2. 視圖坐标系

理論上 Canvas 這張紙是沒有邊界的,但是我們的手機螢幕是有界的。我們可以了解為我們透過一個方形的洞(手機螢幕)看一張巨畫(Canvas)。

而這裡我們就又存在一個問題了,因為剛才的移動,我們是移動的原點,也就是說我們的畫布是靜止不動的,隻是落筆點一直在變動,這就導緻我們繪制的圖對于使用者來說是看不全的,是以我們需要進行移動 方形的洞 來檢視這幅畫。

舉個例子,我們要檢視最開始所說的畫,可以通過移動 Screen框來檢視這幅畫,而這裡又出現了一個坐标系,這一坐标系則為 視圖坐标系,通過

scrollerTo

scrollerBy

進行移動該Screen框,正數則往正半軸,負數則往負半軸。

Canvas的功能Canvas的功能

3. 小結

自定義控件中存在兩個坐标系需要明确,用一句話總結如下:

  1. 繪圖坐标系:決定我們的繪制的坐标
  2. 視圖坐标系:決定我們所看到的畫布範圍

二、Canvas的剪刀手API

Canvas 中以 clip開頭 的公有方法,用于裁剪畫布的内容。裁剪之後畫布之外将無法繪制, 我們抽取比較好玩的參數類型為Path的方法來分享,其餘的都可以一一映射進來。

1、clipPath裁剪任意形狀畫布

描述: 隻留下 path内 的畫布區域,而處于path範圍之外的則不顯示。

舉個例子: 我們先準備好一個心形的路徑Path,然後調用

clipPath

從畫布中将此路徑内的區域 “裁剪” 下來,最後為了我們觀察,使用

drawColor

“染”上酒紅色。

// 第一步:建立 心形路徑 mPath
....省略,具體請移步demo(我會貼在末尾的)

// 第二步:從畫布 canvas 裁剪下心形路徑之内的區域
canvas.clipPath(mPath);

// 第三步:塗酒紅色
canvas.drawColor(mBgColor);
           

【這個有一個網上的】 自帶美感的貝塞爾曲線原理與實戰

畫心型路徑基本是使用貝塞爾曲線的( 路徑關系可以參考這個畫心),路徑關系如文章所示本章僅講解和繪制相關的知識。

效果如圖所示:

Canvas的功能Canvas的功能

代碼如下:

/**
 * 參考連結: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之外的區域

描述: 隻留下 path外 的畫布區域,而處于path範圍之内的則不顯示。(與clipPath的作用範圍正好相反)

值得注意的是,該方法隻能在API26版本以上調用。 低版本我們使用下一小節介紹的方法

舉個例子:

我們先準備好一個心形的路徑Path,然後調用

clipOutPath

從畫布中将此路徑之外的區域 “裁剪” 下來,最後為了我們觀察,使用

drawColor

“染”上酒紅色。

// 第一步:建立 心形路徑 mPath 
....省略,具體請移步github 

// 第二步:從畫布 canvas 裁剪下心形路徑之外的區域 
canvas.clipOutPath(mPath);

// 第三步:塗酒紅色 
canvas.drawColor(mBgColor);
           

實作效果如下:

Canvas的功能Canvas的功能

代碼如下:

/**
 * 參考連結: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 裁剪保留區域

clipPath

clipOutPath

一樣,都是對畫布的裁剪,不會保留白白像素,之是以這麼說是因為筆者在實際工作過程中遇到過,因為通過混色方案實作過View的壓感效果,但是如果這個View在可見區域外其實保留了空白像素,這種情況混色将會出現原本圓角位置出現四個陰影直角效果的情況。

描述: 在畫布上進行使用 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

方法的問題

2)

clipPath

方法中使用的類型就是

INTERSECT

,換而言之,我們可以使用以下代碼代替

clipPath(mPath, Region.Op.INTERSECT)
複制代碼
           

舉些例子:

接下來我們一個個講解這六種類型,兩次裁剪比較能展現出

Region.Op

參數的作用,是以我們接下來的例子需要使用兩個路徑:

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

Canvas的功能Canvas的功能

2、圓形路徑 (下列例子中的 B)

Canvas的功能Canvas的功能

(1)DIFFERENCE

描述: A形狀中不同于B的部分顯示出來

效果圖: 紅色即為最終裁剪留下區域

Canvas的功能Canvas的功能

代碼如下:

@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交集的形狀

效果圖: 紅色和藍色交界的部分即為最終裁剪留下區域

Canvas的功能Canvas的功能

代碼如下:

@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;

效果圖: 紅色即為最終裁剪留下區域

Canvas的功能Canvas的功能

效果如下:

【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需要補齊效果】

Canvas的功能Canvas的功能

此類

代碼如下:

@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的功能Canvas的功能

通過畫布裁剪去掉無關部分

Canvas的功能Canvas的功能

三、了解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);
        }
               
    效果如下:
Canvas的功能Canvas的功能
  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);
        }
               

​ 效果如下:

Canvas的功能Canvas的功能
  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的功能Canvas的功能

可以看到前面設定的

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的功能Canvas的功能

通過這個示例可以看出,

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();

    }
           

實作效果如下:

Canvas的功能Canvas的功能
  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的功能

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。