前言

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

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

android 3d渲染动画效果吗,Android如何实现3D效果相关推荐

  1. android 同根动画_[转载]Android anim动画切换效果

    关于动画的实现,Android提供了Animation,在Android SDK介绍了2种Animation模式: 1. Tween Animation:通过对场景里的对象不断做图像变换(平移.缩放. ...

  2. android 折纸动画,如何在Android中实现折纸动画

    受到iOS版的启发,在最近的项目中,我们决定在打开列表元素时实现一个类似风格的动画效果.起初,我们试图使用一个现有的实现--android-flip, 通过OpenGL渲染动画--在***的Andro ...

  3. 做7秒动画赢13W大奖?总奖池超80W、国内最火爆的3D渲染动画创作大赛开始报名!

    有人本科在读就被大厂选中?有人初露锋芒便商单接到手软?还有人一战成名全网爆火?全因为参与了这个CG赛事获得了官方流量扶持,大V媒体精准曝光!还获得了高视野的行业交流和更多的业界合作机会! 总奖池超80 ...

  4. android开发骰子动画,GitHub - jieyou/dice: 一个css3 3d动画效果的色子(或称骰子?)...

    dice -- 3d色子(或称骰子?) 一个css3 3d动画效果的色子 完全效果(完全流畅的3d动画.阴影.圆角):Chrome\Firefox\Safari\iOS Safari 6.0+\And ...

  5. android手势控制动画,轻松实现Android,iOS的一个手势动画效果

    先来看效果 这是iOS下的效果,android下完全一致.通过do_GestureView组件和do_Animation组件,deviceone能很容易实现复杂的跨平台纯原生动画效果,这个示例就是通过 ...

  6. android 图片闪光动画_剖析Android动画(图片闪烁、左右摇摆、上下晃动等效果) | 学步园...

    图片闪烁,类似这样. 2011-11-22 16:18 上传 左右摇摆: 2011-11-22 17:07 上传 一.续播  (不知道取什么名字好,就是先播放动画A, 接着播放动画B) 有两种方式. ...

  7. android 飞框动画,AndroidTV中实现飞框选中效果

    相信很多从事AndroidTV开发的朋友都对如何展示item的选中效果感到苦恼,电视端开发与移动端最大的不同是用户只能通过一个遥控器进行控制(当然如果你的电视是触屏的话除外--),在这个时候,我们需要 ...

  8. android 补间动画有停顿,Android动画原理分析(一)----补间动画

    1.基本特点 补间动画(Tween动画),是android最早的动画框架,从Android1.0开始就有. 功能:可以实现移动.旋转.缩放.渐变四种效果以及这四种效果的组合形式. 实现形式:xml和代 ...

  9. android 应用切换动画,怎么在Android应用中利用Activity对动画进行切换

    怎么在Android应用中利用Activity对动画进行切换 发布时间:2020-11-27 16:19:53 来源:亿速云 阅读:107 作者:Leah 今天就跟大家聊聊有关怎么在Android应用 ...

最新文章

  1. 读《实战 GUI 产品的自动化测试》之:第二步,构建利于维护的自动化测试系统...
  2. Jpg, Jpeg, Exif
  3. 函数计算GB镜像秒级启动:下一代软硬件架构协同优化
  4. 【转】科大校长给数学系学弟学妹的忠告本科数学参考书
  5. 罗马音平假名片假名转换器_平假名与片假名
  6. Visual studio 2022 常用快捷键
  7. android 免root 安装xposed,xposed框架免root安装|VAExposed(xposed框架免root版本)1.97最新版 - 维维软件园...
  8. Python机器学习:值得反复练习的8个项目
  9. 用Python做一个游戏辅助脚本,完整编程思路分享
  10. 质因子分解 Python
  11. 我们都是被宫崎骏爱过的孩子
  12. docker image 的sha256 digest摘要
  13. 三人易行PLC编程培训怎么样?
  14. PHP汉字转拼音第三方类库
  15. 【UE4 Download】Epic导入本地离线版本详细步骤[附图](更新至4.23版本)
  16. 前端布局 Flex(弹性)布局
  17. ibm3500服务器型号,IBM x365 M2服务器的详细配置.doc
  18. 凤凰系统安装教程(与系统盘共存)
  19. 图片标注工具LabelImg安装及使用
  20. 13年电赛综合测评——三角波

热门文章

  1. Exception: com.mchange.v2.c3p0.impl.NewProxyConnection cannot be cast to com.mysql.jdbc.Connection
  2. Android AIDL使用介绍(3) 浅说AIDL背后的Binder
  3. 网站apache环境S2-057漏洞 利用POC 远程执行命令漏洞复现
  4. Wintel物联网平台-Windows IoT新手入门指南
  5. windows自带的压缩,解压缩命令
  6. 从未加入域的计算机上使用Windows验证方式访问SQL Server
  7. .NET WebBrowser不与IE或其他进程共享cookie(WebBrowser独立cookie方法)
  8. 全局变量名为 param1 var param1Value = webBrowser1.Document.InvokeScript(eval,new String[]{ param1}).To...
  9. 字符串的长度超过了为 maxJsonLength 属性设置的值
  10. XML CDATA的作用