今天我们来看看如何实现一个立方体翻转的效果,如图


Android 立方体翻转效果_子视图

看上去很麻烦,实际上实现起来还是蛮轻松的。
这里我们使用到的有两个类。

​android.graphic.Camera​

  1.  这是在图像学概念里的摄像机,这是一个​​透视摄像机​​。

​android.graphic.Matrix​

头疼的钻研路开始

我们先从摄像头上的角度分析:
正常情况下,我们是这么看画面的(那个电池一样的东西就当是摄像头吧)


Android 立方体翻转效果_子视图_02


我们要产生立方体的效果,那逻辑上应该是这么看:

Android 立方体翻转效果_三角函数_03

Camera提供了几个接口,我们这使用到的接口有两个:

  1. Rotate 旋转
  2. Translate 平移

​画布​​的!

这里我们首先要有2个View。xml结构入下:

<cn.geminiwen.canvassupport.view.SplashLayout
android:text="@string/hello_world"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#000"
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#f00">
</RelativeLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#0f0">
</RelativeLayout>
</cn.geminiwen.canvassupport.view.SplashLayout>

SplashLayout就是我的自定义布局,用来绘制立方体效果的布局。

​backgroundView​​,第二个View作为​​foregroundView​​,使得效果是从​​backgroundView​​翻转到​​foregroundView​​。

具体代码入下:

private void cube(Canvas canvas, double interpolation) {
View foregroundView = getChildAt(0);
View backgroundView = getChildAt(1);
int width = getWidth();
int height = getHeight();
long drawingTime = getDrawingTime();
float rotate;

//begin drawForeground
rotate = (float)(- sFinalDegree * interpolation);
mCamera.save();
mCamera.translate((float)(width - interpolation * width), 0, 0);
mCamera.rotateY(rotate);
mCamera.getMatrix(mMatrix);
mCamera.restore();

mMatrix.postTranslate(0, height / 2);
mMatrix.preTranslate(-width, -height / 2);
canvas.save();
canvas.concat(mMatrix);
drawChild(canvas, foregroundView, drawingTime);
canvas.restore();
//end drawForeground



//draw Background
rotate = (float)(sFinalDegree - sFinalDegree * interpolation);
mCamera.save();
mCamera.translate((float)(-width * interpolation), 0, 0);
mCamera.rotateY(rotate);
mCamera.getMatrix(mMatrix);
mCamera.restore();

mMatrix.postTranslate(width, height / 2);
mMatrix.preTranslate(0, -height / 2);
canvas.save();
canvas.concat(mMatrix);
drawChild(canvas, backgroundView, drawingTime);
canvas.restore();
//end draw Background

}


​ViewGroup​​的​​dispatchDraw​​方法里即可,因为​​ViewGroup​​只能在​​dispatchDraw​​方法中绘制子视图。

其中,​​canvas​​代表画布,​​interpolation​​代表动画从0.0 到 1.0 的过程,方便插入器的使用。

这里来解释下我们的过程。

View状态变换

​background​​是这样的:

  1. 绕Y轴正方向转90度
  2. 画布x轴移动到width的位置。

​画布2​​的状态。

终点状态是这样:

  1. 绕Y轴正方向0度。
  2. 画布x轴移动到0的位置。

可以参考上图中的画布的状态。

旋转问题

​sFinalDegree​​为90。

​interpolation​​从0到1的过程,

​background​​的rotate就变成了从​​90​​到​​0​​的过程。

平移问题

​平移摄像机​​或者​​直接平移画布​​。

这里我们说下区别,如果移动摄像机,会导致图像的投影发生变化,举个例子:
比如我们已经在投影上绕Y轴旋转90度,如果移动X轴的话,看如下图的区别:

1、未平移摄像机


Android 立方体翻转效果_android_04

2、平移摄像机


Android 立方体翻转效果_android_05


从图上我们知道,这个旋转过的画布的前端和后端我们都是可以看见的,这当然不符合我们要求,那么我们直接平移画布是什么意思呢?​​1​​一样,但是我们的画布却平移了,这就达到了我们最终的要求。

我们看代码虽然我们平移的是画布,但是我们平移的过程中确是使用移动摄像机的方式来绘制投影,这又是为什么呢?

我们从三角函数的投影来解释这个问题。

首先看见我平移摄像头的方式是线性的,也就是​​y=kt​​这种方式,斜率一定,也就是随着时间变化,我平移的距离是线性增加的。那么考虑旋转的时候的投影情况:

被旋转的角就是​​角a​​,我们的画布长为​​width​​,那么画布的投影长度为 

​width * cos(a)​

Android 立方体翻转效果_android_06


它是一个三角函数。变化趋势先快后慢,因此我们在旋转过程中会看见右边露出背景,造成视觉上的不友好,怎么解决这个问题呢? 

这时候就借助我们的摄像机平移的投影方式。


Android 立方体翻转效果_android_07

​cos(a) * width​​要长很多,因此它就可以让我们在旋转过程中不产生黑边,给人视觉上的饱满感,会让我们的视觉效果好很多。​​foregroundView​​就是一个​​backgroundView​​的逆向过程,因此使用类似的代码,然后假设​​interpolation​​是从1-0的过程即可,同时它的坐标系整体要往左移动一个屏幕。

总结

​Camera​​来进行辅助我们进行矩阵的计算。

源码

​https://github.com/geminiwen/AndroidCubeDemo​​