美文网首页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