当前位置:   article > 正文

Android 高级UI解密 (三) :Canvas裁剪 与 二维、三维Camera几何变换(图层Layer原理)_cavans android clippath

cavans android clippath

Android的绘图机制是核心内容之一,无论是什么样的功能最终都是以图像的形式呈现给用户。因此掌握Android的绘图技巧,有助于Android理解层次的提高,在面对产品经理提出的idea时也更有底气~

系统通过提供的Canvas对象来绘图,此类拥有各种绘制图像的API,例如 drawRect(矩形)、drawCircle(圆)、drawLine(线)、drawArc(弧)、drawPoint(点)、drawVertices(多边形)等等,通过这些API名字也可了解大致作用。但是,Canvas背后的宝藏在更深处的地方,各种基础、绚丽的效果都与之脱不了干系,两大法宝就是Canvas的裁剪合集,二维、三维Camera几何变换。

更重要的是要理解Android系统中绘制Canvas画布的概念,画布上层层叠加的纸Bitmap,图层叠加组合而成的UI呈现,这些概念运用不同于生活中的认知,需谨慎鉴别。

(此系列文章知识点相对独立,可分开阅读,不过笔者建议按照顺序阅读,理解更加深入清晰)

Android 高级UI解密 (二) :Paint滤镜 与 颜色过滤(矩阵变换)
Android 高级UI解密 (一) :Paint图形文字绘制 与 高级渲染

此篇涉及到的知识点如下:

  • Canvas裁剪合集
  • Canvas图层概念及Layer原理
  • Canvas二维、三维Camera几何变换

一. Canvas基础API和裁剪合集

1. 基础API

这里写图片描述

上图展示的是最基本的Canvas绘制图形API,简单理解即可知其作用含义,此处不再举例讲解,后续将挑出重点部分学习。


2. 剪切合集

Canvas中有关裁剪的API可分为三类: clipPathclipRectclipRegion,以上几乎可以实现任意形状的裁剪。通过三类API的名字可以想到其中的区别:

  • Rect:矩形区域。
  • Path:开放或闭合的曲线,线构成的复杂的集合图形。
  • Region:组合区域,例如取两个区域部分并起来、或重叠部分等。

注意

在学习以下API使用之前,应当明确裁剪的概念,生活中认为裁剪是对存在的图形进行切割,但在Android系统中并非如此,裁剪的主要对象是画布canvas而非图形!裁剪后的画布区域,在此上面绘制图像才会显示出来,并且每次调用canvas.drawXXX都会创建一个新的视图层。

绘制图像是需要基于画布canvas的基础上,因此需要注意调用API顺序:先裁剪画布区域,再在画布上面绘制图像!(若顺序弄反,调用draw的图像已经绘制出来,后续再调用clip,并没有对之前的图像裁剪生效,只对下一次draw生效)

(1)clipRect

这里写图片描述

  • API作用: clipRect相关的重载方法有8个,作用都是根据参数给出的Rect矩阵搭配裁剪形式,或者上下左右坐标来进行裁剪,最终得出裁剪后的矩形。
  • 参数说明:
    • rect:Rect对象,用于定义裁剪区的范围,Rect和RectF功能类似,精度和提供的方法不同而已;
    • left、top、right、bottom:矩形裁剪区的左、上、右、下边位置;
    • op:裁剪区域的组合方式。

代码示例:

    canvas.clipRect(350+width()/4,20+height()/4,350+width*3/4,20+height()*3/4);
    canvas.drawBitmap(bitmap, 350, 20, mPaint);
  • 1
  • 2

图片效果示例:

这里写图片描述

注意

在裁剪指定区域绘制完毕后,注意是否需要恢复绘制范围,否则后续的所有绘制都会被裁切!使用方法如下:

//保存视图层
canvas.save();
canvas.clipRect(350+width()/4,20+height()/4,350+width*3/4,20+height()*3/4);
canvas.drawBitmap(bitmap, 350, 20, mPaint);
//恢复原有试图层
canvas.restore();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

(2)clipPath

这里写图片描述

  • API作用: clipPath相关的重载方法只有2个,根据传入的曲线形式参数进行裁剪。它与clipRect最大的不同是裁剪后的区域十分灵活,可以是任意曲线而并非局限于矩形。
  • 参数说明:
    • path:曲线
    • op:裁剪区域的组合方式。

代码示例:

    Path path=new Path();
    path.addCircle(350+width/2, 20+height/2,width/2,Path.Direction.CCW);

    canvas.save();
    canvas.clipPath(path);
    canvas.drawBitmap(bitmap, 350, 20, paint);
    canvas.restore(); 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

图片效果示例:

这里写图片描述


(3)clipRegion

前两个方法可能很常见,但是Region区域的使用比较少见。方便理解从微积分的角度分析,任意绘制的一块区域都是由一块块小区域拼接的。例如一张图片放大很多倍后,你会发现它也是一块块很小的像素块组成的。

这里写图片描述

  • API作用: clipRegion相关的重载方法只有2个,Region区域定义了一个矩形区域,结合不同拼接方式形成区域。(刚才看了一下源码,API25竟然都已经不支持了,了解即可吧)
  • 参数说明:
    • Region:区域
    • op:裁剪区域的组合方式。

Region.Op是个枚举类,定义了Region支持的区域间运算种类,各个种类如下:

  • DIFFERENCE:A和B的差集范围,即A - B,只有在此范围内的绘制内容才会被显示;
  • INTERSECT:即A和B的交集范围;
  • UNION:即A和B的并集范围;
  • XOR:A和B的补集范围,此例中即A除去B以外的范围;
  • REVERSE_DIFFERENCE:B和A的差集范围,即B - A;
  • REPLACE:不论A和B的集合状况,B的范围将全部进行显示,如果和A有交集,则将覆盖A的交集范围;



二. 图层的保存与恢复

这一部分“Canvas的变换”都在强调Canvas画布这个概念,以上API也都是针对画布进行变换,因为很容易将Canvas绘制的画布区域和屏幕区域混淆,但两者是有差别的。这里需要引入“视图层Layer”的概念,每次canvas执行drawXXX的时候就会新建一个新的画布图层。

在对画布canvas进行裁剪或几何变换时需要知道这是一个不可逆的过程,除非搭配使用save()restore()使用保存恢复图层状态。

1. 图层概念

这里写图片描述

阅读自此,你可能对“图层”的概念还是有些模糊,举个例子,如上图是笔者在SketchBookPro绘画工具上绘制的一张图片(真是煞费苦心啊,笔者献出了自己拙劣的绘画功底,见谅~)比较正规点的绘画工具它都会都有“图层”的概念,便于修改,例如上图中图层1的内容就是绿色的大树和底下一条波浪线,也就是草原啦;图层二就是绿树上的一颗红苹果;图层三就是树下的小人儿,牛顿是也。

咋一看,这就是一幅平面2D图画,但你要从“图层”的角度来看,这可是由3层图层依次叠加而形成的,例如下图所示。

“图层”在真正的实践中最大作用就是便于修改,例如PM过来一看俗,都是苹果砸牛顿,我要换成梨子,在Android的绘制过程中可没有什么“橡皮擦”给你修改,就算此处用橡皮擦修改,一旦遇到重叠的部分,那就全擦去了。如今引入了“图层”的概念,苹果位于图层2,可以很轻易的删去图层2,新建图层绘制梨子即可。

对于Android绘制而言,“图层”概念的引用,使得各个图层之间互相独立,可以轻易对单个指定图层进行修改、删去,绘制修改起来更加简单。

这里写图片描述

Canvas为开发者提供了图层(Layer)的支持,而这些Layer(图层)是按”栈结构“来进行管理的,如下图:

这里写图片描述


2. 图层使用API

这里写图片描述
这里写图片描述

(1)save() 和 restore()

  • int save():保存当前的matrix 矩阵和剪切状态到一个私人堆栈,也就是说保存当前Canvas的状态然后作为一个Layer(图层)添加到Canvas栈中,即入栈操作。
  • void restore():恢复之前Canvas的状态,即出栈操作。

需要注意的是Layer 并不是一个实际的类,连同“视图层”只是一个抽象的相对概念。由于是栈结构,调用restore()的次数不应大于调用save()的次数。

实例测试

这里写图片描述

如上图所示,先绘制一个红色矩形,再绘制一个蓝色矩形。现在的需求是要将红色矩形旋转。在绘制红色矩形之间调用canvas.rotate(10)

emmm,说对了一半,如果只是简单的调用,你会发现连带着蓝色矩形也旋转了。此处需要用到“图层”的概念,在旋转之前先调用save保存图层,在旋转且绘制蓝色矩形过后,调用restore恢复图层,再去绘制蓝色矩形即可。

以下是正确代码和示例图:

        canvas.save();
        // 保存并旋转画布
        canvas.rotate(10);

        //绘制一个红色矩形
        mPaint.setColor(getResources().getColor(R.color.morePink));
        canvas.drawRect(mViewWidth / 2F - 200, 200, mViewWidth / 2F + 200, 600, mPaint);
        canvas.restore();


        //绘制一个蓝色的矩形
        mPaint.setColor(getResources().getColor(R.color.moreBlue));
        canvas.drawRect(mViewWidth / 2F - 100, 300, mViewWidth / 2F + 100, 500, mPaint);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

这里写图片描述

在了解以上内容后再来总结一下CanvasBitmap的关系:

在此之前我们称呼Canvas为画布,更准确的说法应该是是一个容器。如果把Canvas理解成画板,那所谓的“图层”就像是画板上的一张张透明“纸”,而这些“纸”对应到Android就是封装在Canvas中的Bitmap

(2)saveLayerXXX

int saveLayer(float left, float top, float right, float bottom, Paint paint, int saveFlags)
  • 1

查看此API,可发现它与save方法相比多了好些参数,前四个则是位置参数,接着Paint画笔参数,和标识符参数(后续讲)。作用则是指定保存的模式和保存到区域。

实例测试

上述的例子完全可以将save()方法替换成如下代码,效果相同。

canvas.saveLayer(0, 0, mViewWidth, mViewHeight, null, Canvas.ALL_SAVE_FLAG);  
  • 1

saveLayer的特点就是可以自行设定需要保存的区域,如上代码是将区域设置成全屏幕,再举个例子,将保存的区域分别设置为红色矩形、蓝色矩形,最终显示效果会是如何呢?代码如下:

        //测试实例1(只需要修改save方法)
        canvas.saveLayer(mViewWidth / 2F - 200, 200, mViewWidth / 2F + 200,600, null, Canvas.ALL_SAVE_FLAG);

        //测试实例2(只需要修改save方法)
        canvas.saveLayer(mViewWidth / 2F - 100, 300, mViewWidth / 2F + 100, 500, null, Canvas.ALL_SAVE_FLAG);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

这里写图片描述

哟呵,吓一跳!我只是修改了一下保存区域,这两种测试实例都不是理想的效果,一个个来解释:(注意黑框是指定的保存区域,笔者为了便于理解后期加上的)

  • 修改区域为旋转前的红色矩形大小:这个其实比较好理解,一开始设定的保存区域是黑框,当旋转后再绘制红色矩形,红色矩形超出黑框外的范围不会显示!此处恢复状态,再绘制蓝色矩形,蓝色矩形位置在指定范围内,因此完整绘制。
  • 修改区域为旋转前的蓝色矩形大小:这个就非常奇妙了,由于保存的区域只有蓝色矩形那么大,在绘制蓝色矩形后,它完全就把红色矩形的部分挡住了,而超出黑框范围的蓝色矩形也不会显示!

由此可见,saveLayerXXXsave方法的区别在于:

  • saveLayerXXX方法会将所有的操作存到一个新的Bitmap中而不影响当前Canvas的Bitmap;
  • save方法则是在当前的Bitmap中进行操作,并且只能针对Bitmap的形变裁剪进行操作;

也许你会疑惑saveLayerXXX为啥会有指定保存范围这个设定呢?

上述区别已解释saveLayerXXX会将操作存在一个新的Bitmap中,而对于Android而言,使用Bitmap稍有不当就会内存溢出,甚至OOM。因此根据参数尽可能创建一个小的Bitmap。

int saveLayer(float left, float top, float right, float bottom, int alpha, int saveFlags)
  • 1

除以上讲解的saveLayer方法之外,还提供有saveLayerAlpha,顾名思义就是保存画布时设置画布的透明度,不再举例实践。

(3)标识符saveFlags

saveLayersaveLayerAlpha方法中都见识过saveFlags参数,不仅如此,save重载的参数方法中也有这个参数,如下表查看其详细含义:

这里写图片描述

ALL_SAVE_FLAGCLIP_SAVE_FLAGMATRIX_SAVE_FLAG是有关保存方法通用的,意义分别为保存所有标示位、裁剪标识位、变换标识位。大多数情况下使用第一种标识符。

CLIP_TO_LAYER_SAVE_FLAG、FULL_COLOR_LAYER_SAVE_FLAG和HAS_ALPHA_LAYER_SAVE_FLAG专门用于saveLayersaveLayerAlpha方法,,意义分别为:对当前图层执行裁剪操作需要对齐图层边界,当前图层的色彩模式至少需要是8位色,在当前图层中将需要使用逐像素Alpha混合模式。(此处只做简单介绍,并不常用,与本篇主题差距稍大,有兴趣者可查阅以下资料自行了解)

色彩深度和Alpha混合的资料

(4)restoreToCount(int saveCount)

恢复图层有两个方法,restore在一开始就讲解过,相当于一次出栈操作,而调用此方法则可以恢复到指定的图层。这是如何做到的呢?

所有的savesaveLayersaveLayerAlpha方法都有一个int型的返回值,该返回值相当于一个标识,确定当前保存操作的唯一ID编号。因此可以利用restoreToCount(int saveCount)方法来指定在还原的时候还原到指定图层修改操作。

如下图所示每一个Canvas都有这样的一个Stack栈。每次调用save方法会保存一个图层ID入栈,若没有人为调用save方法,则所有的操作会默认保存到Default Stack ID中。因此若此时已创建3个图层,即位于saveID3了,若想要回到saveID1的状态,则需要调用两次restore方法,不过只要在调用save方法时获得返回值ID,则可以直接调用restoreToCount(int saveCount)回溯到理想状态!

这里写图片描述

实例测试

这里写图片描述

实践一个简单的例子来体会体会,首先看上图左侧,很明显笔者建立了三个图层,依次是红色、蓝色、橙色矩形,此处从图层二也就是蓝色矩形绘制之前调用了Canvas的rotate方法,因此后续绘制图层三的橙色矩形也受约制一起旋转了。此时要求绘制的图形是上图右侧,给出红色矩形中间位置绘制天蓝色矩形。

如果直接在图层三上根据给出的坐标绘制天蓝色矩形,那么此部分肯定会受到图层二旋转的约制,因此在绘制之前必须要回滚到图层1,此时你要两个选择:多次调用restore()方法和直接使用restoreToCount(int saveCount)回溯到指定图层。毫无疑问选择后者,代码如下:

        //绘制一个红色矩形
        int saveID1 = canvas.save(Canvas.ALL_SAVE_FLAG);
        mPaint.setColor(getResources().getColor(R.color.morePink));
        canvas.drawRect(mViewWidth / 2F - 200, 200, mViewWidth / 2F + 200, 600, mPaint);

        //绘制一个蓝色的矩形
        int saveID2 = canvas.save(Canvas.ALL_SAVE_FLAG);
        // 旋转画布
        canvas.rotate(10);
        mPaint.setColor(getResources().getColor(R.color.moreBlue));
        canvas.drawRect(mViewWidth / 2F - 100, 300, mViewWidth / 2F + 100, 500, mPaint);

        int saveID3 = canvas.save(Canvas.ALL_SAVE_FLAG);
        //绘制一个淡黄色矩形
        mPaint.setColor(getResources().getColor(R.color.littlePink));
        canvas.drawRect(mViewWidth / 2F - 50, 350, mViewWidth / 2F + 50, 450, mPaint);

        canvas.restoreToCount(saveID1);
//        canvas.restore();canvas.restore();
        mPaint.setColor(getResources().getColor(R.color.littleBlue));
        canvas.drawRect(mViewWidth / 2F - 170, 370, mViewWidth / 2F + 170, 430, mPaint);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22



三. Canvas几何变换技巧

  • Canvas的translaterotatescaleskew变换方法;
  • Matrix的pre/postTranslateRotateScaleSkew变换方法及自定义;
  • Camera的rotatesetLocation三维变换方法;

这里写图片描述

1. Canvas变换

注意

在学习Canvas的裁剪API时,因为每次调用drawXXX方法都会创建一个新的视图层,故而使用API的顺序是先裁剪画布再调用drawXXX方法绘制图形。此处几何变换的4个API也是相对于画布Canvas作变化,但是想要对图形进行有序性的连续变换时,例如先移动再旋转,一定要先调用旋转API再调用移动API,因为这是反序性的!同时在实际操作中也需要搭配save()restore()方法使用。

Canvas的几何变化内部原理使用的是Matrix类,Matrix矩阵是几何变换背后的代数原理。

(1)平移Tanslate

void translate (float dx, float dy)
  • 1

API作用:用指定的转换对当前matrix 进行预处理。
参数说明: x、y轴移动的距离。

注意:此API的功能就是移动画布位置,再再次强调的是每次canvas执行drawXXX的时候就会新建一个新的画布图层

实例测试

来证明以上理论,做一个简单的测试如下,首先在(100, 100)的位置用蓝色画笔绘制一个矩形,接着调用此API方法移动画布x、y轴各50像素,用红色画笔绘制一个矩形。代码如下,查看显示效果:

        mPaint.setColor(getResources().getColor(R.color.moreBlue));
        RectF r = new RectF(100, 100, 250, 250);
        canvas.drawRect(r, mPaint);

        mPaint.setColor(getResources().getColor(R.color.morePink));
        canvas.translate(50,50);
        canvas.drawRect(r, mPaint);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

这里写图片描述

如图很显然,在只移动画布位置的情况下,矩阵的位置也随之改变,说明第二个红色矩形是在新的画布图层上绘制的。另外需要强调的是移动画布是一个不可逆的过程,除非使用save()restore()来搭配使用,代码示例如下:

        //保存此刻画布设置状态
        canvas.save();

        canvas.translate(50,50);
        canvas.drawRect(r, mPaint);  

        //恢复画布设置状态
        canvas.restore();  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

(2)缩放Scale

void scale (float sx, float sy)
  • 1

API作用:用指定的比例预先缩放当前矩阵matrix 。
参数说明: x、y轴缩放的比例。

注意:设置的参数是对x、y轴的缩放系数,即“画布”会被缩放,同时意味着画布里面所有的绘制的东西都会被缩放。

实例测试

查看以下示例,设置的图片位置是举例top、left各为50像素,第一张是原图展示;第二张调用此API,将x、y轴各压缩0.5,这里意味着这个“画布”缩小,因此不仅是画布里的内容缩小,其间隔距离也会被缩小;第三张图同理。

        canvas.drawBitmap(bitmap, 50, 50, mPaint);

        canvas.save();
        canvas.scale(1.5f, 0.5f);
        canvas.drawBitmap(bitmap, 50, 50, mPaint);
        canvas.restore(); 

        canvas.save();
        canvas.scale(1.5f, 1.5f);
        canvas.drawBitmap(bitmap, 600, 50, mPaint);
        canvas.restore(); 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

这里写图片描述


(3)旋转Rotate

rotate(float degrees) 
rotate(float degrees, float px, float py)
  • 1
  • 2

API作用:用指定的旋转预先缩放当前矩阵。围绕坐标原点旋转degrees度,值为正顺时针。
参数说明: degrees为旋转角度,px和py为指定旋转的中心点坐标(px,py)。

注意:此处需要再三强调的是旋转是指整个“画布”进行旋转,因此其绘制内容具体坐标位置是根据旋转后的“画布”而定。

实例测试

        //旋转Rotate
        canvas.drawBitmap(bitmap, 200, 20, mPaint);

        canvas.save();
        canvas.rotate(30);
        canvas.drawBitmap(bitmap, 200, 20, mPaint);
        canvas.restore(); 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

这里写图片描述


(4)斜拉画布Skew

skew(float sx, float sy)
  • 1

API作用:使用指定的偏斜预处理当前矩阵。
参数说明: sx为x轴方向上倾斜的对应角度,sy为y轴方向上倾斜的对应角度。

注意:参数的类型虽然为float ,两个值都是tan值!比如要在x轴方向上倾斜60度,那么小数值对应:tan 60 = 根号3 = 1.732,在设置时应填写1.732。

实例测试

        //斜拉画布Skew
        canvas.drawBitmap(bitmap, 200, 20, mPaint);

        canvas.save();
        canvas.skew(1.73f, 0);//X轴方向上倾斜60度,tan60=根号3
        canvas.drawBitmap(bitmap, 200, 20, mPaint);
        canvas.restore(); 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

这里写图片描述



2. Matrix变换

其实在上一篇博客 Android 高级UI解密 (二) :滤镜 与 颜色过滤(矩阵变换)中讲解过颜色矩阵ColorMatrix,其中说到

“美颜相机中的图片美白原理就是将红色、绿色、蓝色进行位移,可以获得不同的效果,而其中的计算则可以借助矩阵完成。”

再结合上一点中介绍Canvas的几何变换内部原理就是Matrix矩阵,可知Matrix的强大性,重要的是除了Canvas的4个API几何变换方法,我们可以直接使用Matrix的方法代替之。

(1)Matrix优势

  • Matrix除了四种基本的缩放、移动、旋转、斜拉几何变换效果外,还可以自定义效果,拥有更大的灵活性。
  • Canvas的几何变换操作是反序性的!Matrix对四种基本操作提供了preXXXpostXXX方法,正序反序随你心意~

(2)使用Matrix的步骤

  1. 创建Matrix对象;
  2. 调用Matrix的pre/postTranslateRotateScaleSkew变换方法;
  3. 调用Canvas的concat(matrix)方法运用Matrix。
Matrix matrix = new Matrix();

......

matrix.reset();  
matrix.postTranslate();  
matrix.postRotate();

canvas.save();  
canvas.concat(matrix);  
//canvas.setMatrix(matrix)
canvas.drawBitmap(bitmap, x, y, paint);  
canvas.restore(); 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

在Canvas中使用Matrix矩阵有如下两个API方法:

  • Canvas.concat(matrix):用 Canvas 当前的变换矩阵和 Matrix 相乘,即基于 Canvas 当前的变换,叠加上 Matrix 中的变换;
  • Canvas.setMatrix(matrix):用 Matrix 直接替换 Canvas 当前的变换矩阵,即抛弃 Canvas 当前的变换,改用 Matrix 的变换。(注意:此方法在不同的手机系统中显示效果不一致,建议使用concat方法)


3. Camera三维变换

Caves虽然是个二维图形变换工具,但是也可以通过几何变换去实现三维效果。值得庆幸的是官方已为开发者提供了Camera类,其内部会把这些变换转化成Matrix,用二维来模拟三维。

void applyToCanvas(Canvas canvas)
  • 1

调用Camera的applyToCanvas方法即可运用到Canvas上。Camera 的三维变换有三类:旋转、平移、移动相机。

(1)旋转Rotate

这里写图片描述

API作用:在指定的轴上旋转角度;
参数说明: X、Y、Z轴旋转的量;

实例测试

        //Camera
        Camera camera = new Camera();
        canvas.save();

        camera.rotateX(30); // 旋转 Camera 的三维空间
        camera.applyToCanvas(canvas); // 把旋转投影到 Canvas

        canvas.drawBitmap(bitmap, 200, 20, mPaint);
        canvas.restore();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

这里写图片描述

如上展示效果指定X轴旋转30度,图片倒是有点三维feel了,可是发现图片显然不对称啊。因为Camera 的旋转是以左上角原点为轴心进行X轴旋转,按照正常的逻辑应当以图形中点为轴心进行旋转,可是Camera并没有提供设置轴心的方法,因此我们只能手动移动画布Canvas。具体做法就是:

  1. 在调用Camera旋转API之前,调用Canvas的translat移动画布将原点移动到需要绘制的bitmap中点;
  2. 使用Camera旋转API;
  3. 再调用Canvas的translat移动画布将需要绘制的bitmap中点移动到原点;
  4. 调用Canvas的drawBitmap绘制bitmap。

emmmm,其中这个移动来移动去的感觉有点坑爹,参考代码如下,之后的效果图片就对称了:

        //Camera
        Camera camera = new Camera();
        canvas.save();
        canvas.translate(bitmap.getWidth()/2+200,bitmap.getHeight()/2+20);

        camera.save();
        camera.rotateX(30); // 旋转 Camera 的三维空间
        camera.applyToCanvas(canvas); // 把旋转投影到 Canvas
        camera.restore();

        canvas.translate(-bitmap.getWidth()/2-200,-bitmap.getHeight()/2-20);

        canvas.drawBitmap(bitmap, 200, 20, mPaint);
        canvas.restore();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

这里写图片描述


(2)移动相机setLocation

void setLocation(float x, float y, float z)
  • 1

API作用:修改相机位置;
参数说明: X、Y、Z轴旋转的角度;

此处参数单位不是像素,而是 inch英寸。这种设计源自 Android 底层的图像引擎 Skia 。在 Skia 中,Camera 的位置单位是英寸,英寸和像素的换算单位在 Skia 中被写死为了 72 像素,而 Android 中把这个换算单位照搬了过来

查看这个API的作用可能会有点懵,waht?相机?确实如此,Camera不同于Canvas二维坐标系,本身代表的是三维,即XYZ轴。在此之上,工作模型还包括了一个虚拟的Camera对象,它的默认位置在Z轴负方向,也就是坐标原点的正前方的位置。

其工作原理就是以Camera为出发点,把三维模型往View上做一个投影,投影就是实际显示的图像。在默认情况下,投影与图像是一样的,但在使用API修改基本参数后,投影就改变了。

这里写图片描述这里写图片描述

这里写图片描述

这里写图片描述

上面三张图即可明白Camera投影的工作原理,Camera 的坐标系如下:

这里写图片描述

以下GIF动图演示的是使用Camera完成旋转的过程,其中包括使用Canvas的translate方法将图片中点移动到轴心,再旋转X轴,旋转完后再移动回来,再绘制的整个过程,配合此GIF更易了解。

这里写图片描述

wait, wait, wait,笔者刚才检查的时候发现此节都在啵得啵得Camera的概念,还没细讲这个API呢,其实了解此概念之后,即可了解这个照射光速的发起点,也就是Camera,可以通过此API设置它的位置。

一般可以运用在例如旋转时导致图片呈现过大,可以设置Z轴位置,增大其值相当于挪远照相机,图像成像自然就会变小。在此不再举例,详细可查看扔物线一文。


(3)平移Translate

void translate(float x, float y, float z)
  • 1

API作用:在指定的轴上移动角度;
参数说明: X、Y、Z轴旋转的角度;

至于此API,在了解第二点后可以很容易的理解使用此API会对XYZ移动,例如增大Z轴,相当于将照相机挪远,图像自然变小。例如如下例子演示:

        //原图
        canvas.drawBitmap(bitmap, 0, 0, mPaint);

        Camera camera = new Camera();
        canvas.save();

        camera.save();
        camera.translate(0,0,300); // 旋转 Camera 的三维空间
        camera.applyToCanvas(canvas); // 把旋转投影到 Canvas
        camera.restore();

        //设置Camera之后图像变小
        canvas.drawBitmap(bitmap, 0, 0, mPaint);
        canvas.restore();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

这里写图片描述

在演示以上效果后,但是笔者并不打算详细讲解,因为Canvas自己本身也有translate方法,其拉远拉近效果可以使用canvas的缩放API完成,改变图像可以完成的事情,没有必要去改变三维坐标轴,因此了解即可。




【声明:“Camera三维变换”部分引用并参考了扔物线一文,以上黑色截图来自于扔物线讲解的视频,若有不妥,请联系笔者删去。这一部分笔者想要使用简单的例子讲解很难说明白,但扔物线写的这篇文章中配合了一个小视频动画演示,简直是醍醐灌顶,一点就透,因此笔者把重点制作成图片、GIF粘贴过来,墙裂推荐~】

hhha,想不到吧,去年开始写的系列写到一半笔者又去写热修复,时隔多月还是回来填坑了。事出有因,Android UI这一块其实说来说去就是Paint、Canvas、Path、PathMeasure、动画、绘制顺序、自定义View/Layout,如今网络上类似的讲解文章多如牛毛,笔者一开始的打算是通过此系列总结通透这块内容,但是发现有的内容太基础了,有的点详细讲解内容太多了,想要写出精华实在难以下笔。推荐多逛逛帖子,观摩学习别人的博客文章,你可以发现每个人写文章的详略点不同,讲解逻辑能力,有的文章简直让笔者欲罢不能,不由得朝天大喊,精辟!

最后笔者心里有了大概的谱,在跟自己妥协的同时保证文章的质量,卷土重来:)

若有错误,虚心指教~

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/IT小白/article/detail/254528
推荐阅读
相关标签
  

闽ICP备14008679号