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

变换

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);
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 224,467评论 6 522
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 96,079评论 3 402
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 171,600评论 0 366
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 60,835评论 1 300
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 69,848评论 6 399
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 53,348评论 1 314
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 41,735评论 3 428
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 40,705评论 0 279
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 47,238评论 1 324
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 39,268评论 3 345
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 41,395评论 1 354
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 37,012评论 5 350
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 42,704评论 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 33,177评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 34,309评论 1 275
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 49,925评论 3 381
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 46,457评论 2 365

推荐阅读更多精彩内容