自如-黄进 | 作者
承香墨影 | 编辑
https://juejin.cn/post/6989227733410644005 | 原文
Hi,大家好,这里是承香墨影!
说到裸眼 3D 效果,最先想到的就是利用 MR 实现的虚拟增强技术,例如下图就流传甚广。
但这种效果,通常需要通过 AI 或者使用 MR 再加上后期特效渲染而成。
那么有没有更简单的实现裸眼 3D 的效果呢?
自然是有。裸眼 3D 最基础的原理,就是利用「视觉差」,我们只需要让大脑误认为有层次有景深的感觉,就可以模拟出 3D 的效果。
比如一些常见的 Gif 图,就是利用 2 条辅助线,制造出裸眼 3D 的效果。
比如:
比如:
比如:
既然是利用「视觉差」可以模拟出裸眼 3D 效果,那么我们可以将 UI 视图分层,然后将不同层次的元素,按照不同的速率运动,造成伪立体的感觉,就是一种裸眼 3D 的效果。
这有点像前几年知乎 App 在启动 Banner 里实现的「视差动画」,只是它利用的是 UI 的滚动来带动元素的视差移动,而要实现裸眼 3D 还需要借助传感器,利用 SensorManager 获取到的数据,来偏移某些图层上的元素。
今天推荐一个来自自如大前端研发中心-黄进的一篇文章,讲述他在自如 App 中如何将裸眼 3D 效果应用在首页 Banner 上。
按照惯例,先上效果。
背景
移动端界面设计如此火热的今天,各类大厂的设计规范和设计语言,已经非常的成熟,我们想做一些在这套成熟的设计规范之外的尝试和创新,所以有别于传统的 Banner 交互形式,成为了我们的发力点。
设计理念
由于 App 版面空间有限,除了功能导向、阅读习惯和设计美观外,自如想在既定的框下,做一下不同的设计尝试,哪怕这种尝试只能提升用户 1% 的观感。
可能租了几年自如的房子,用了几年自如客 App,你可能也不会注意到一些小的细节。但如果哪天,作为用户的你突然发现了这个隐藏的 “彩蛋”,看到了自如在这些小细节上的用心,我相信那天你将会对自如这个品牌有更深层次的认识和理解。
裸眼 3D 技术,一般都是应用在裸眼 3D 大屏、全息投影等等比较常见的场景中。在 APP 的 Banner 上应用,的确也是一次全新的尝试。
我们通过借助移动设备上的传感器、以及自身的屏显清晰度、画面呈现,将 2D 影像转化为景深效果,以呈现出不用 "3D" 眼镜就可看到的 3D 效果。
实现方式
以下以 Android 为例,介绍一下该效果的实现方式。
分层
自如客 App 的 Banner 其实一直在创新当中,有专门注意过的同学可能知道,在裸眼 3D 效果之前,自如客 App 其实就已经实现了分层。
当时为了实现更加自然和精致的切换效果:在每个 Banner 滑入滑出的时候,底部其实会在原地进行渐显渐隐,内容会跟随手势滑入滑出。
此次为了实现 3D 效果,我们在以前分层的基础上加了一层中景,将原有的前景拆分为前景和中景。

上图的 sl_bg
为背景,pv_middle
为中景,sl
为前景。
由于切换的交互,实际上 Banner 使用了两个 ViewPager 进行了联动。背景在最底层的 ViewPager 里面,中景和前景在另外一个 ViewPager 里。
跟手位移
打开自如客 App 后,用户操作设备可以明显感受到画面的错位移动,造成视觉上的景深效果。这种错位移动,其实就是借助设备本身的传感器来实现的,具体实现方式,是我们让中景始终保持不动,同时从设备传感器获取当前设备对应的倾斜角,根据倾斜角计算出背景和前景的移动距离,然后执行背景和前景移动的动作。
如下图所示:

为了使用的方便,我们封装了一个 SensorLayout,专门用于根据设备的倾斜角执行内容的位移;SensorLayout 内部的主要实现:
注册对应的传感器
mSensorManager = (SensorManager) getContext().getSystemService(Context.SENSOR_SERVICE);
mAcceleSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
mMagneticSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
mSensorManager.registerListener(this, mAcceleSensor, SensorManager.SENSOR_DELAY_GAME);
mSensorManager.registerListener(this, mMagneticSensor, SensorManager.SENSOR_DELAY_GAME);
计算偏转角度
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
mAcceleValues = event.values;
}
if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {
mMageneticValues = event.values;
}
float[] values = new float[3];
float[] R = new float[9];
SensorManager.getRotationMatrix(R, null, mAcceleValues, mMageneticValues);
SensorManager.getOrientation(R, values);
values[1] = (float) Math.toDegrees(values[1]);
values[2] = (float) Math.toDegrees(values[2]);
通过重力传感器和地磁场传感器,获取设备的偏转角度
根据偏转角度执行滑动
if (mDegreeY <= 0 && mDegreeY > mDegreeYMin) {
hasChangeX = true;
scrollX = (int) (mDegreeY / Math.abs(mDegreeYMin) * mXMoveDistance*mDirection);
} else if (mDegreeY > 0 && mDegreeY < mDegreeYMax) {
hasChangeX = true;
scrollX = (int) (mDegreeY / Math.abs(mDegreeYMax) * mXMoveDistance*mDirection);
}
if (mDegreeX <= 0 && mDegreeX > mDegreeXMin) {
hasChangeY = true;
scrollY = (int) (mDegreeX / Math.abs(mDegreeXMin) * mYMoveDistance*mDirection);
} else if (mDegreeX > 0 && mDegreeX < mDegreeXMax) {
hasChangeY = true;
scrollY = (int) (mDegreeX / Math.abs(mDegreeXMax) * mYMoveDistance*mDirection);
}
smoothScrollTo(hasChangeX ? scrollX : mScroller.getFinalX(), hasChangeY ? scrollY : mScroller.getFinalY());
mDegreeX
即为第二部中获取的偏转角度,mDegreeXMin
和 mDegreeXMax
为 X 轴可发生偏转位移的角度的最大值和最小值,mYMoveDistance
即为 Y 轴上的最大偏移距离(围绕 X 轴发生旋转,视图会沿 Y 轴上发生位移);Y 轴上的偏转同理;就算好 X 轴和 Y 轴的偏移距离后,使用 scroller
进行滑动;
实现总结
读到这里,相信大家已经基本了解了这套 Banner 的实现方案。
Android 端在布局上进行了分层,中景位置不变,借助重力传感器和地磁场传感器获取偏转角度,根据角度使背景和前景进行错位移动。iOS 端的实现原理也基本一致,不再赘述。
墨影小结
正如文章开头讲的,利用「视觉差」实现裸眼 3D 效果,最重要的就是 2 点:
UI 视图分层;
利用传感器(SensorManager)让不同层次的元素,规律移动;
关键代码已经在文内展示,有兴趣的可以自己动手实现。再给自己加一个新的封装要求就更好了。例如何封装不同图层元素显式位置的规则,以适应设计师频繁的替换元素而无需修改代码。
-- End --
本文对你有帮助吗?留言、转发、点好看是最大的支持,谢谢!
推荐阅读:
百度技术:“App 优化网络,先从 HTTPDNS 开始” | 原理到实战
Android 的 Window 如何理解?Dialog 到底是不是子窗口?
