Android如何实现3D效果

您所在的位置:网站首页 3d效果是怎么实现的 Android如何实现3D效果

Android如何实现3D效果

#Android如何实现3D效果| 来源: 网络整理| 查看: 265

前言

前段时间读到一篇文章,作者通过自定义View实现了一个高仿小米时钟,其中的3D效果很是吸引我,于是抽时间学习了一下,现在总结出来,和大家分享。

正文 想要在Android上实现3D效果,其实并没有想象中那么复杂,我们需要运用两样东西:Camera和Matrix,这里的Camera可不是我们平常拍照用的Camera,这里的Camera是位于android.graphics包下的Camera:

android.graphics.Camera 我们可以看到它的作用是:计算3D变换,并且生成一个Matrix,可以应用到Canvas上,这句话其实就是实现3D效果的核心原理。 Camera的坐标系是左手坐标系。当手机平整的放在桌面上,X轴是手机的水平方向,Y轴是手机的竖直方向,Z轴是垂直于手机向里的那个方向。

Camera坐标系

Camera位于坐标点(0,0),也就是视图的左上角。

我们再来了解一下Matrix,Android中的Matrix是一个3 x 3的矩阵,其内容如下:

Matrix

从字面上来看, MSCALE用于处理缩放变换,MTRANS用于处理平移变换,MSKEW用于处理错切变换。最后一行的MPERSP用于处理透视变换,关于透视变换,官方文档中并没有具体的说明,这里也就不再赘述。另外,矩阵是支持旋转变换的,旋转变换是通过同时设置MSCALE和MSKEW来实现的(这里边就是一些数学原理了,笔者也是半壶水,就不在这丢人了,感兴趣的同学可以自己研究一下)。另外有同学可能对错切变换也不是特别理解,笔者当时也是自己查了下才明白,这里简单说明一下,就免得大家再去百度了:

错切变换(skew)在数学上又称为Shear mapping(可译为“剪切变换”)或者Transvection(缩并),它是一种比较特殊的线性变换。错切变换的效果就是让所有点的x坐标(或者y坐标)保持不变,而对应的y坐标(或者x坐标)则按比例发生平移,且平移的大小和该点到x轴(或y轴)的垂直距离成正比。错切变换,属于等面积变换,即一个形状在错切变换的前后,其面积是相等的。

X轴错切变换

上图中,各点的y坐标保持不变,但其x坐标则按比例发生了平移。

Y轴错切变换

上图中,各点的x坐标保持不变,但其y坐标则按比例发生了平移。

还有一个不容易理解的地方,Matrix针对每种变换,都提供了set、pre和post三种操作方式。

pre方法表示矩阵前乘,如果变换矩阵为A,原始矩阵为M,pre方法即是 M x A

post方法表示矩阵后乘,如果变换矩阵为A,原始矩阵为M,post方法即是 A x M

之所以需要区分前乘和后乘,是因为矩阵的乘法不满足交换率,即 A x M != M x A

另外还有比较重要的一点, 在图像处理中,越靠近右边的矩阵越先执行

调用一系列set、pre、post方法时,可以理解为将这些操作插入一个队列:set是清空队列再添加,pre是在队首插入,post是在队尾插入。

举个栗子: Matrix m = new Matrix(); m.postTranslate(20, 20); m.preScale(0.2f, 0.5f); m.setScale(0.8f, 0.8f); m.postScale(3f, 3f); m.preTranslate(0.5f, 0.5f);

执行顺序为:preTranslate(0.5f, 0.5f) → setScale(0.8f, 0.8f) → postScale(3f, 3f) 因为setScale(0.8f, 0.8f)会将前面的postTranslate(20, 20)和preScale(0.2f, 0.5f)清除掉,然后再将postScale(3f, 3f)插入队尾,preTranslate(0.5f, 0.5f)插入队首。

2018.4.25补充---Canvas的几何变换: Canvas的几何变换方法包括 translate、rotate、scale、skew 几种,其原理也是运用matrix做几何变换。 我们以 canvas.rotate(float degrees) 方法举例:

canvas.rotate(float degrees).png 通过官方文档我们可以了解到,rotate方法其实就是用matrix进行前乘,然后再将matrix应用到当前画布上。 以下两段代码其实是等价的:

canvas.rotate(-degreeZ); // 1 Matrix matrix = new Matrix(); // 2 matrix.reset(); matrix.preRotate(-degreeZ); canvas.concat(matrix);

由于是前乘,所以canvas的几何变换方法是倒序的,需要把变换的代码倒着写,举个栗子:

// 如果想要让canvas先移动 (-centerX, -centerY) 距离,再移动 (centerX, centerY) 距离进行恢复 // 代码需要倒着写 canvas.translate(centerX, centerY); canvas.translate(-centerX, -centerY);

这里再补充一下 canvas.concat(matrix) 方法: 用 Canvas 当前的变换矩阵和 Matrix 相乘,即基于 Canvas 当前的变换,叠加上 Matrix 中的变换。

熟悉了基本的工具,我们就可以开工了,我们先来看一下最终的效果:

3D效果.gif

圆盘跟随手指的移动而变换角度,呈现出3D的效果,看起来还是很不错的,我们看看如何来实现这个效果吧: 首先我们需要做一些初始化的工作:

private Paint mWhitePaint; private Paint mCirclePaint; private float mCircleStrokeWidth = 2; private float mMaxRadius = 300; /* Camera旋转的最大角度 */ private float mMaxCameraRotate = 15; /* 我们今天的主角 */ private Matrix mMatrix; private Camera mCamera; /* Camera绕X轴旋转的角度 */ private float mCameraRotateX; /* Camera绕Y轴旋转的角度 */ private float mCameraRotateY; private void init(){ mMatrix = new Matrix(); mCamera = new Camera(); //白色大圆的画笔 mWhitePaint = new Paint(Paint.ANTI_ALIAS_FLAG); mWhitePaint.setStyle(Paint.Style.FILL_AND_STROKE); mWhitePaint.setStrokeWidth(mCircleStrokeWidth); mWhitePaint.setColor(Color.WHITE); //内部蓝色圆环的画笔 mCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG); mCirclePaint.setStyle(Paint.Style.STROKE); mCirclePaint.setStrokeWidth(mCircleStrokeWidth); mCirclePaint.setColor(Color.WHITE); mCirclePaint.setColor(0xff237EAD); }

这是重写的onDraw方法,做了两件事情: 1.将canvas传入setCameraRotate方法中 2.再画几个圈圈

@Override protected void onDraw(Canvas canvas) { setCameraRotate(canvas); canvas.drawCircle(getWidth() / 2, getHeight() / 2, mMaxRadius, mWhitePaint); canvas.drawCircle(getWidth() / 2, getHeight() / 2, mMaxRadius / 6 * 5, mCirclePaint); canvas.drawCircle(getWidth() / 2, getHeight() / 2, mMaxRadius / 6 * 4, mCirclePaint); canvas.drawCircle(getWidth() / 2, getHeight() / 2, mMaxRadius / 6 * 3, mCirclePaint); canvas.drawCircle(getWidth() / 2, getHeight() / 2, mMaxRadius / 6 * 2, mCirclePaint); }

接下来我们看看setCameraRotate方法里面做了什么

private void setCameraRotate(Canvas mCanvas) { mMatrix.reset(); mCamera.save(); mCamera.rotateX(mCameraRotateX);//绕x轴旋转 mCamera.rotateY(mCameraRotateY);//绕y轴旋转 mCamera.getMatrix(mMatrix);//计算对于当前变换的矩阵,并将其复制到传入的mMatrix中 mCamera.restore(); /** * Camera默认位于视图的左上角,故生成的矩阵默认也是以其左上角为旋转中心, * 所以在动作之前调用preTranslate将mMatrix向左移动getWidth()/2个长度, * 向上移动getHeight()/2个长度, * 使旋转中心位于矩阵的中心位置,动作之后再post回到原位 */ mMatrix.preTranslate(-getWidth() / 2, -getHeight() / 2); mMatrix.postTranslate(getWidth() / 2, getHeight() / 2); mCanvas.concat(mMatrix);//将mMatrix与canvas中当前的Matrix相关联 }

以上这段代码,除了旋转操作以外,其余的基本属于固定写法,这样写的原因都在注释里写清楚了,可能有点不太好理解,多看几遍或者自己试着写一下就明白了。

上面这段代码中的mCameraRotateX和mCameraRotateY这两个全局变量的值应该与此时手指触摸坐标相关联,所以我们在onTouchEvent方法中动态设置:

@Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: //根据手指坐标计算Camera应该旋转的角度 getCameraRotate(event); invalidate(); break; case MotionEvent.ACTION_MOVE: getCameraRotate(event); invalidate(); break; } return true; }

我们最后来看看getCameraRotate方法中是如何处理的:

private void getCameraRotate(MotionEvent event) { float rotateX = -(event.getY() - getHeight() / 2); float rotateY = (event.getX() - getWidth() / 2); /** *为什么旋转角度要这样计算: * 当Camera.rotateX(x)的x为正时,图像围绕X轴,上半部分向里下半部分向外,进行旋转, * 也就是手指触摸点要往上移。这个x就会与event.getY()的值有关,x越大,绕X轴旋转角度越大, * 以圆心为基准,手指往上移动,event.getY() - getHeight() / 2的值为负, * 故 float rotateX = -(event.getY() - getHeight() / 2) * 同理, * 当Camera.rotateY(y)的y为正时,图像围绕Y轴,右半部分向里左半部分向外,进行旋转, * 也就是手指触摸点要往右移。这个y就会与event.getX()的值有关,y越大,绕Y轴旋转角度越大, * 以圆心为基准,手指往右移动,event.getX() - getWidth() / 2的值为正, * 故 float rotateY = event.getX() - getWidth() / 2 */ /** * 此时得到的rotateX、rotateY 其实是以圆心为基准,手指移动的距离, * 这个值很大,不能用来作为旋转的角度, * 所以还需要继续处理 */ //求出移动距离与半径之比。mMaxRadius为白色大圆的半径 float percentX = rotateX / mMaxRadius; float percentY = rotateY / mMaxRadius; if (percentX > 1) { percentX = 1; } else if (percentX < -1) { percentX = -1; } if (percentY > 1) { percentY = 1; } else if (percentY < -1) { percentY = -1; } //将最终的旋转角度控制在一定的范围内,这里mMaxCameraRotate的值为15,效果比较好 mCameraRotateX = percentX * mMaxCameraRotate; mCameraRotateY = percentY * mMaxCameraRotate; }

到这里,我们要的3D效果就已经实现了。也是费了一番功夫。

结语

写这篇文章的起因是读到了猴菇先生的博客高仿小米时钟 - 使用Camera和Matrix实现3D效果对文中实现的3D效果产生了兴趣,但是文中主要的篇幅还是介绍如何自定义View,关于3D效果的实现只有主要代码和简单的注释,所以我又自己从Camera和Matrix的定义开始,将3D效果作为主体重新学习了一遍,便是有了这篇文章。文中的部分代码也是从猴菇先生的代码中借鉴的,将代码进行了简化,只保留了3D效果的部分,将注释和说明进行了丰富,从头开始讲解,更加易于学习。

从开始研究到写完这篇文章,断断续续加起来差不多花了2天时间,发现写文章确实很锻炼人,以前自己遇到问题,上网随便搜搜,看个大概,就完事儿了。现在想要写出来,必须要弄明白、透彻,才敢动手写,也算是对自己的一种监督吧,我可不愿意误人子弟,所以经常写到一半,发现某些地方不是特别清楚,又回过头去弄明白了再继续写,写完之后,收获也是大大的。而且之前写的文章还收到了点赞、关注还有打赏,真的特别开心。

最后,如有错误,欢迎指正。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3