美文网首页Android UI绘制
UI绘制 - canvas变换、状态保存和恢复

UI绘制 - canvas变换、状态保存和恢复

作者: tingtingtina | 来源:发表于2019-07-14 20:26 被阅读0次

变换

API 功能说明
translate(dx, dy) 平移操作
scale(sx, sy) 缩放操作
rotate(degress) 旋转操作
skew(sx, sy) 倾斜操作
clipXXX() 切割操作,参数指定区域内可以继续绘制
clipOutXXX() 反向切割操作,参数指定区域内不可以绘制
setMatrix(matrix) 通过Matrix实现平移、缩放、旋转等操作

平移

将画布平移,后面绘制的图像都是以平移后为原点

/**
     * Preconcat the current matrix with the specified translation
     * 平移操作
     * @param dx The distance to translate in X
     * @param dy The distance to translate in Y
    */
    public void translate(float dx, float dy) 
/**************************************************/
// eg:平移
    mPaint.setColor(Color.RED);
    canvas.drawRect(0, 0, 500, 500, mPaint);

    canvas.translate(200, 200);
    mPaint.setColor(Color.BLUE);
    canvas.drawRect(0, 0, 500, 500, mPaint);
    mPaint.setColor(Color.GRAY);
    canvas.drawLine(0, 0, 600, 600, mPaint);

蓝色为移动画布前,在移动画布后,绘制的矩形和直线都是从(200,200)开始绘制的


缩放

将画布进行倍数缩放

/**
     * Preconcat the current matrix with the specified scale.
     *
     * @param sx The amount to scale in X
     * @param sy The amount to scale in Y
     */
    public void scale(float sx, float sy)

    mPaint.setColor(Color.BLUE);
    canvas.drawRect(200, 200, 700, 700, mPaint);
    canvas.scale(0.5f, 0.5f);
    mPaint.setColor(Color.RED);
    canvas.drawRect(200, 200, 700, 700, mPaint);

有重载方法如下,该方法会先平移,再缩放,再反向平移

     /**
     * Preconcat the current matrix with the specified scale.
     *
     * @param sx The amount to scale in X
     * @param sy The amount to scale in Y
     * @param px The x-coord for the pivot point (unchanged by the scale)
     * @param py The y-coord for the pivot point (unchanged by the scale)
     */
    public final void scale(float sx, float sy, float px, float py) {
        if (sx == 1.0f && sy == 1.0f) return;
        translate(px, py);
        scale(sx, sy);
        translate(-px, -py);
    }

// eg:
    mPaint.setColor(Color.BLUE);
    canvas.drawRect(200, 200, 700, 700, mPaint);
    //先translate(px,py),在scale(sx, sy), 再反向translate
    canvas.scale(0.5f, 0.5f, 200, 200);
    mPaint.setColor(Color.RED);
    canvas.drawRect(200, 200, 700, 700, mPaint);

上方scale方法等同于

    canvas.translate(200,200);
    canvas.scale(0.5f, 0.5f);
    canvas.translate(-200, -200);
  • 左图为基本缩放:可以理解为画布缩放后,所以的左边值都进行缩放后再绘制,比如例子中,缩放0.5以后,绘制的200,就是100,因此看起来不仅矩形变小,也向原点移动了
  • 右图为带坐标缩放,做的是先移动到指定位置,缩放后再平移回来,可以理解成在在指定位置开始缩放。


旋转

将画布参照原点(默认0, 0)顺时针旋转,旋转后绘制
如果画布平移,那么原点就是平移后的原点

    /**
     * Preconcat the current matrix with the specified rotation.
     *
     * @param degrees The amount to rotate, in degrees
     */
    public void rotate(float degrees) 

    //eg
    mPaint.setColor(Color.BLUE);
    canvas.drawRect(0, 0, 500, 500, mPaint);
    canvas.rotate(30);
    mPaint.setColor(Color.RED);
    canvas.drawRect(0, 0, 500, 500, mPaint);

有重载方法,可设定旋转中心(px, py)

    /**
     * Preconcat the current matrix with the specified rotation.
     *
     * @param degrees The amount to rotate, in degrees
     * @param px The x-coord for the pivot point (unchanged by the rotation)
     * @param py The y-coord for the pivot point (unchanged by the rotation)
     */
    public final void rotate(float degrees, float px, float py) {
        if (degrees == 0.0f) return;
        translate(px, py);
        rotate(degrees);
        translate(-px, -py);
    }

    //eg:设定旋转中心为矩形中心
    mPaint.setColor(Color.BLUE);
    canvas.translate(300, 300);
    canvas.drawRect(0, 0, 500, 500, mPaint);
    canvas.rotate(45, 250, 250); // px, py 旋转中心坐标
    mPaint.setColor(Color.RED);
    canvas.drawRect(0, 0, 500, 500, mPaint);
  • 左图效果是绕着原点旋转
  • 右图为重载函数,设置旋转旋转坐标为矩形中心,旋转45°


倾斜(错切)

sx和sy分别表示将画布在x和y方向上倾斜相应的角度对应的tan值
设置sx 将y逆时针旋转相应角度
设置sy 将x顺时针旋转相应角度

    /**
     * Preconcat the current matrix with the specified skew.
     *
     * @param sx The amount to skew in X
     * 将画布在x方向上倾斜相应的角度,sx倾斜角度的tan值,其实就是将y逆时针旋转相应的角度
     * @param sy The amount to skew in Y
     * 将画布在y方向上倾斜相应的角度,sx倾斜角度的tan值,其实就是将x顺时针旋转相应的角度
     */
    public void skew(float sx, float sy)

    //eg
    canvas.translate(200, 200);
    mPaint.setColor(Color.BLUE);
    mPaint.setTextSize(26);
    canvas.drawLine(0, 0, 800, 0, mPaint);//x
    canvas.drawText("X", 800, 0, mPaint);
    canvas.drawLine(0, 0, 0, 800, mPaint);//y
    canvas.drawText("Y", 0, 800, mPaint);

    canvas.drawRect(0, 0, 400, 400, mPaint);
//  canvas.skew(1, 0);//将画布在x方向上旋转45度,其实就是x轴保持方向不变,y轴逆时针旋转45度
//  canvas.skew(0, 1);//将画布在y方向上旋转45度,其实就是y轴保持方向不变,x轴顺时针旋转45度
    canvas.skew(1, (float) Math.sqrt(3));// 将画布x方向旋转45,y方向旋转60,可见,x轴顺时针旋转了60度,y逆时针旋转了45度。
    mPaint.setColor(Color.DKGRAY);
    canvas.drawLine(0, 0, 600, 0, mPaint);//x
    canvas.drawText("X", 600, 0, mPaint);
    canvas.drawLine(0, 0, 0, 600, mPaint);//y
    canvas.drawText("Y", 0, 600, mPaint);
    mPaint.setColor(Color.RED);
    canvas.drawRect(0, 0, 400, 400, mPaint);
  • 左图为 canvas.skew(1, 0) 将画布在x方向上旋转45度,其实就是x轴保持方向不变,y轴逆时针旋转45度
  • 右图为 canvas.skew(0, 1) 将画布在y方向上旋转45度,其实就是y轴保持方向不变,x轴顺时针旋转45度


  • canvas.skew(1, (float) Math.sqrt(3)) 将画布x方向旋转45,y方向旋转60,可见,x轴顺时针旋转了60度,y逆时针旋转了45度。


切割

clipRect: 将画布切割,后面绘制的在切割范围内可绘制,超出部分不绘制
clipOutRect: 将画布切割,后面绘制的部分在切割范围外可绘制,超出部分不绘制

    /**
     * Intersect the current clip with the specified rectangle, which is  expressed in local coordinates.
     *
     * @param left   The left side of the rectangle to intersect with the current clip
     * @param top    The top of the rectangle to intersect with the current clip
     * @param right  The right side of the rectangle to intersect with the current clip
     * @param bottom The bottom of the rectangle to intersect with the current clip
     * @return       true if the resulting clip is non-empty
     */
    public boolean clipRect(int left, int top, int right, int bottom) 


   /**
     * Set the clip to the difference of the current clip and the specified rectangle, which is
     * expressed in local coordinates.
     *
     * @param left   The left side of the rectangle used in the difference operation
     * @param top    The top of the rectangle used in the difference operation
     * @param right  The right side of the rectangle used in the difference operation
     * @param bottom The bottom of the rectangle used in the difference operation
     * @return       true if the resulting clip is non-empty
     */
    public boolean clipOutRect(int left, int top, int right, int bottom)


    //eg: clipRect切割后,没有显示出完整的圆形,仅在蓝色区域有效,
    //clipOutRect:在蓝色矩形外部的部分可显示
    mPaint.setColor(Color.BLUE);
    canvas.drawRect(100, 100, 500, 500, mPaint);
    canvas.clipRect(100, 100, 500, 500);// 画布被裁剪,切割只在该范围有效
//  canvas.clipOutRect(100, 100, 500, 500);// 画布裁剪外的区域有效
    mPaint.setColor(Color.RED);
    canvas.drawCircle(300, 300, 100, mPaint);
  • 左图 canvas.clipRect(100, 100, 500, 500); 画布被裁剪,切割只在该范围有效
  • 右图 canvas.clipOutRect(100, 100, 500, 500); 画布裁剪外的区域有效


矩阵Matrix

除了上面的一些变换方法,可以使用Matrix对画布进行变化操作

canvas.setMatrix(matrix);

在Android中Matrix是个3*3的矩阵
\left[ \begin{matrix} MSCALE_X & MSKEW_X & MTRANS_X\\ MSKEW_Y & MSCALE_Y & MTRAN_Y \\ MPERSP_0 & MSPERSP_1& MPERSP_2 \\ \end{matrix} \right]

通过矩阵也可以实现上述平移旋转等操作,默认单位矩阵为
\left[ \begin{matrix} 1 & 0& 0\\ 0 & 1 & 0 \\ 0 & 0 & 1 \\ \end{matrix} \right]

Matrix 的三种动作:preXXX、postXXX、setXXX

pre/set/postTranslate/Scale/Rotate/Skew

setXXX

setXXX 方法首先会将该Matrix重置为单位矩阵,即相当于首先会调用reset()方法,然后再设置该Matrix中对应功能的值。

    mPaint.setColor(Color.BLUE);
    canvas.drawRect(100, 100, 500, 500, mPaint);
    mPaint.setColor(Color.RED);
    Matrix matrix = new Matrix();
    Log.d("tina", "matrix:" + matrix.toShortString());
    matrix.preScale(0.5f, 0.5f);
    Log.d("tina", "matrix scale: " + matrix.toShortString());
    matrix.setTranslate(50,50);
    Log.d("tina", "matrix translate: " + matrix.toShortString());
    canvas.setMatrix(matrix);
    canvas.drawRect(100, 100, 500, 500, mPaint);

从示例可看出,矩形并没有缩小,只是平移了,打印的结果如下,也只是改变了负责平移的部分

matrix:[1.0, 0.0, 0.0][0.0, 1.0, 0.0][0.0, 0.0, 1.0]
matrix scale: [0.5, 0.0, 0.0][0.0, 0.5, 0.0][0.0, 0.0, 1.0]
matrix translate: [1.0, 0.0, 50.0][0.0, 1.0, 50.0][0.0, 0.0, 1.0]

preXXX:

不会重置Matrix,相当于当前操作矩阵(A)左乘参数矩阵(B),即AB。例:

    mPaint.setColor(Color.BLUE);
    canvas.drawRect(100, 100, 500, 500, mPaint);
    mPaint.setColor(Color.RED);
    Matrix matrix = new Matrix();
    Log.d("tina", "matrix:" + matrix.toShortString());
    matrix.preScale(0.5f, 0.5f);
    Log.d("tina", "matrix scale: " + matrix.toShortString());
    canvas.setMatrix(matrix);
    canvas.drawRect(100, 100, 500, 500, mPaint);
matrix:[1.0, 0.0, 0.0][0.0, 1.0, 0.0][0.0, 0.0, 1.0]
matrix scale: [0.5, 0.0, 0.0][0.0, 0.5, 0.0][0.0, 0.0, 1.0]

可以看到 矩阵的ScaleX和ScaleY 变为了0.5,其实是这么算的,采用的是左乘
\left[ \begin{matrix} 0.5 & 0& 0\\ 0 & 0.5 & 0 \\ 0 & 0& 1 \\ \end{matrix} \right] \left[ \begin{matrix} 1 & 0& 0\\ 0 & 1 & 0 \\ 0 & 0 & 1 \\ \end{matrix} \right]= \left[ \begin{matrix} 0.5 & 0& 0\\ 0 & 0.5 & 0 \\ 0 & 0 & 1 \\ \end{matrix} \right]

postXXX:

不会重置Matrix,相当于当前操作矩阵(A)右乘参数矩阵(B),即BA

mPaint.setColor(Color.BLUE);
canvas.drawRect(100, 100, 500, 500, mPaint);
mPaint.setColor(Color.RED);
Matrix matrix = new Matrix();
Log.d("tina", "matrix init:" + matrix.toShortString());
matrix.setValues(new float[]{4.0f, 0.0f, 100.0f,
        0.0f, 4.0f, 100.0f,
        0.0f, 0.0f, 1.0f});
Log.d("tina", "matrix set:" + matrix.toShortString());
matrix.postScale(0.5f, 0.5f);
Log.d("tina", "matrix scale: " + matrix.toShortString());
canvas.setMatrix(matrix);
canvas.drawRect(100, 100, 500, 500, mPaint);

我们改变了下单位矩阵,设为如下矩阵,若设置到画布上,表示放大4倍,向x轴平移100,y轴平移100

\left[ \begin{matrix} 4 & 0& 100 \\ 0 & 4& 100 \\ 0 & 0& 1 \\ \end{matrix} \right]
在此单位矩阵下右乘变换矩阵,得到下方矩阵,在图三表示的就是平移了150(含初始),放大了2倍(其实是初始放大4倍,然后缩放了0.5)
\left[ \begin{matrix} 4 & 0& 100 \\ 0 & 4& 100 \\ 0 & 0& 1 \\ \end{matrix} \right] \left[ \begin{matrix} 0.5 & 0& 0\\ 0 & 0.5 & 0 \\ 0 & 0 & 1 \\ \end{matrix} \right]= \left[ \begin{matrix} 2 & 0& 50\\ 0 & 2 & 50 \\ 0 & 0 & 1 \\ \end{matrix} \right]

image.png
Post:(右图)
matrix init:[1.0, 0.0, 0.0][0.0, 1.0, 0.0][0.0, 0.0, 1.0]
matrix set:[4.0, 0.0, 100.0][0.0, 4.0, 100.0][0.0, 0.0, 1.0]
matrix scale: [2.0, 0.0, 50.0][0.0, 2.0, 50.0][0.0, 0.0, 1.0]

Set:(左图)
matrix init:[1.0, 0.0, 0.0][0.0, 1.0, 0.0][0.0, 0.0, 1.0]
matrix set:[4.0, 0.0, 100.0][0.0, 4.0, 100.0][0.0, 0.0, 1.0]
matrix scale: [0.5, 0.0, 0.0][0.0, 0.5, 0.0][0.0, 0.0, 1.0]

Pre:(中图)
matrix init:[1.0, 0.0, 0.0][0.0, 1.0, 0.0][0.0, 0.0, 1.0]
matrix set:[4.0, 0.0, 100.0][0.0, 4.0, 100.0][0.0, 0.0, 1.0]
matrix scale: [2.0, 0.0, 100.0][0.0, 2.0, 100.0][0.0, 0.0, 1.0]

状态保存和恢复

save & restore

  • save: 保存当前画布状态
  • restore: 恢复画布状态
  • restoreToCount save 的返回值是一个整形,可以用getSaveCount获取保存的次数,也可以用restoreToCount 恢复某一状态下的画布
int state = canvas.save(); // save和restore可多次使用,每次使用时都是保存/恢复上一次状态 这里会围护一个状态栈
Log.d("tina", "state: " + state); //D/tina: state: 1
mPaint.setColor(Color.BLUE);
canvas.drawRect(0, 0, 400, 400, mPaint);
canvas.translate(200,200);
mPaint.setColor(Color.GRAY);
canvas.drawRect(0, 0, 400, 400, mPaint);
canvas.restore();
mPaint.setColor(Color.RED);
canvas.drawLine(0, 0, 300, 300, mPaint);

canvas.getSaveCount(); // 不进行任何操作默认为1 每save一次 加1 没restore一次 减1
canvas.restoreToCount(state); // 恢复到某一次的save内容

saveLayer

saveLayer 创建指定大小的图层,创建图层绘制的形状超过图层到小会被截取

    mPaint.setColor(Color.BLUE);
    canvas.drawRect(100, 100, 500, 500, mPaint);
    int layerId = canvas.saveLayer(0, 0, 500, 500, mPaint); // 创建图层,由于创建图层绘制的形状超过图层到小因此被截取
    mPaint.setColor(Color.GRAY);
    canvas.translate(100, 100);
    canvas.drawRect(100, 100, 600, 600, mPaint);
    canvas.restoreToCount(layerId);

    mPaint.setColor(Color.RED);
    canvas.drawRect(200, 200, 400, 400, mPaint);

相关文章

网友评论

    本文标题:UI绘制 - canvas变换、状态保存和恢复

    本文链接:https://www.haomeiwen.com/subject/ogzgxctx.html