3D环形旋转效果的实现

随着电视盒和VR设备的推广,Android应用开始越来越多的使用3D效果,比如电视墙频道轮转这样的,而这种场景并不适合去引入3D引擎这种重量级的Lib,纯算法实现可能才是最佳实践,当然比起普通的自定义View会麻烦一些。

示例图

本篇将要实现的效果和上篇的RecyclerView有些相像,但却是完全不同的实现,RecyclerView的主要特性是item的复用,适用于展现大量数据集合的场景,而本篇的这个环形组件则是基于ViewGroup的自定义View,并没有涉及子view的复用,适用于对布局调整依赖比较大的场景。 所以不管是从底层实现还是应用场景上讲,这两种组件都是有区别的。

起初在设计这个控件的时候是打算用椭圆方程式来进行item的位置计算的

x^2/a^2 + y^2/b^2 = 1

配合一个经过坐标原点的直线方程算出交点的坐标

y=x*tan(30)

通过控制直线方程的旋转角度来计算不同item的位置。 这样看上去貌似还不错?那就大错特错了,做到一半就会发现这种方式计算出来的位置会和近大远小的效果完全相反:视野中心的item布局得密集,两端的稀疏。 尝试用算法处理Y轴坐标和夹角,总会显示得不自然,只能放弃这个方案。

在椭圆方程的长轴在x轴上时,交点会对直线方程偏向x轴的夹角的变化越来越敏感,造成x坐标在两端变得稀疏。那么以此可以推断出长轴在y轴上可以实现我们想要的效果吗?其实不然。

—-华丽的分割线—-

正片开始

上面这个方案之所以会失败,是有一定原因的。我们的目标是绘制出3D的效果,既然是3D,那么就应该以最原始的形状开始计算,然后通过偏移算法计算出3D后的坐标,而不是一开始就计算3D情况下所看到形状。

既然如此,我们就从最原始的圆形方程来进行计算子view的坐标,考虑到圆形有固定的半径,而子view的布局是根据角度分散的,所以直接使用角度和半径计算坐标即可。

x = cos(angle)*size
y = sin(angle)*size

公式中的size是指用于布局子view区域的半径。

通过这个公式我们可以计算出所有子view的坐标,并且可以非常均匀的以圆形的形状进行布局。

其实到现在为止,上面的计算一直是以平面坐标系作为计算基础,既然是要做3D效果,少了Z轴肯定是不可行的。

以x轴为轴心旋转

这里以Z轴作为视角的所在的方向,也就是屏幕向外的方向为Z轴正方向。 有了三个坐标之后,需要确认组件可以沿哪几个轴旋转,在我们的应用场景中,其实只需要组建以Y轴旋转,全局Z轴旋转,就可以满足所有的需求。

下面是圆形以x轴向外旋转45度得到的侧视图

注意:虚线的圆形并不是正视图的中的圆形布局,而是这个园的X轴切面沿X轴旋转的轨迹。 注意:横向的坐标轴是Z轴。 从上图可以看出,x轴坐标不变,根据旋转的角度可以算出沿x轴旋转后的y坐标映射到xy坐标系的新y轴坐标,也就是

y = cos(45) * r

z轴坐标

z = sin(45) * r

上面公式中的r是指沿x轴旋转的旋转半径,也就是这个点在xy坐标系的y坐标。 这个地方理解起来可能有些困难,需要有三视图基础。

按照上面几个公式,对所有子view的坐标进行计算处理,可以得到一个非常自然的椭圆行布局。当然现在的子view都是同样大小,下面需要设计一个算法来调整子view的缩放,来达到近大远小的视觉效果。

注意:这是俯视图 注意:纵向坐标轴为z轴,横向为x轴

上图描述了近大远小的原理和数据关系,我们这里不考虑人眼的左右成像,以一只眼为原型。 图中“看到的位置和尺寸”是指以z轴0点坐标为参照物的情况,当然也可以以其他位置为参照物,不过我们的组件是以0点为中心旋转的,为了方便计算则以0点最佳。

尺寸比例的计算其实就是计算出两个相似三角形的比例 :物距/视距

scale = (dist - z) / dist

而想要调整3D视角的深度只需要dist的大小即可。 这样在三维坐标系中,以X轴为轴心旋转后的坐标和缩放比例的计算公式都已经推导出来了。

以z轴为轴心旋转

以z轴旋转就比较简单了,因为z轴不同深度的图像和在z轴0点切面的图像是等比例的,所以以z轴旋转就相当于在xy平面坐标系以0点坐标作为圆心旋转。

从上面拿到的x,y坐标以0,0点为圆心做旋转某个角度,得到新的坐标值,这个公式是一个数学公式,不用推导了。

NewX =  x * cos(angle) - y * sin(angle);
NewY =  x * sin(angle) + y * cos(angle);

计算得到的新的xy即是旋转后的新坐标。


剩下的就是将上面这些算法以代码形式写入自定义View中,这里不再赘述。

完整代码:https://github.com/NightFarmer/SpinningView

文章目录
,