查看原文
其他

自如App裸眼3D效果最近火爆了,各个版本齐了~

付十一 鸿洋 2021-10-12

本文作者


作者:付十一

链接:

https://juejin.cn/post/6992169168938205191

本文由作者授权发布。


稳住,今天是周末。


前段时间自如团队实现了App裸眼3D效果,确实让人眼前一亮。

https://juejin.cn/post/6989227733410644005 [app 版本]


昨天Nayuta 大佬使用Flutter也实现了该功能,那Jetpack compose版本怎么能落下。

https://juejin.cn/post/6991409083765129229  [flutter 版本]


前人栽树后人乘凉,首先在这里感谢自如大前端团队 和Nayuta ,下文所用的素材也有一部分来自Nayuta ,再次感谢。


本文为 compose 版本,算是各个版本都齐活了。


效果图:图很大,稍等...


1思路


从自如团队所提供的思路来看,裸眼3D效果是将整个图片结构分为3层:上层、中层、以及底层。在手机左右上下旋转时,上层和底层的图片呈相反的方向进行移动,中层则不动,在视觉上给人一种3D的效果。


至于使用Jetpack Compose来实现,主要想法如下:


1、使用Compose 的Canvas对三层图片进行绘制,且使用translate对上层和底层图片进行平移;


2、注册手机陀螺仪传感器的监听,拿到手机旋转时,xyz轴的旋转角度;


3、根据旋转角度计算图片平移的距离,期间做好最大平移距离的控制;


4、得到平移距离后,将距离设置给标记了mutableStateOf的平移距离变量,使得UI刷新,呈平移效果。


2实现


根据上面的思路,我们首先使用compose绘制出静态的三张图片,compose绘制图片的方式有多种,Image、Canvas等,因为考虑到后面图片需要进行移动,这里就选用Canvas进行绘制。


val imageBack = ImageBitmap.imageResource(id = R.drawable.back)
val imageMid = ImageBitmap.imageResource(id = R.drawable.mid)
val imageFore = ImageBitmap.imageResource(id = R.drawable.fore)

Canvas(
    modifier = Modifier
        .fillMaxSize()) {
        //底层
    drawImage(imageBack)
    //中层
    drawImage(imageMid)
    //s
    drawImage(imageFore)

}

生成静态的效果图如下:



静态图片加载是件简单的事情,那如何让图片动起来?


Compose的Canvas中有一个translate方法,作平移效果用,也就是分别在x和y坐标中通过给定的像素增量对坐标空间进行平移。参数传入x轴上平移的距离以及y轴上平移的距离。这里分别定义为xDistance,yDistance。因为只有上层和底层的图片会进行移动,所以在Canvas中,对上层和底层图片的绘制加上translate,如下:


translate(-xDistance, -yDistance) {
        drawImage(imageBack)
    }

    drawImage(imageMid)
    translate(xDistance, yDistance) {
        drawImage(imageFore)
    }

传入xDistance,yDistance参数值,这里需要注意的是,上层与底层图片为互为相反移动,所以对上层图片传入的是xDistance的相反值。到这里,图片就会根据xDistance以及yDistance的距离进行平移。


那xDistance和yDistance的值该如何动态改变呢?


Compose其实提供了一个状态mutableStateOf, 标记了mutableStateOf的data后,该data就表明是有状态的,如果后续状态发生了改变,那么所有引用这个状态的控件都会重新绘制。也就是说,将xDistance和yDistance设置成该状态,因为Canvas引用了xDistance值,所有当xDistance值发生改变时,图片也就会重新绘制,也就是做平移的效果。


如下:


var xDistance by remember { mutableStateOf(0f) }
var yDistance by remember { mutableStateOf(0f) }复制代码


xDistance和yDistance已经动态标记。下面就需要依据手机陀螺仪移动,来动态设置xDistance和yDistance的值。在开始说传感器之前,这里还存在一个问题,当图片进行平移上下或者作用平移时,会存在左右或者上下两侧屏幕露出的情况,这个时候就需要将图片做放大处理, 给图片设置边界,让图片在最大平移距离中移动,防止图片平移露出屏幕背景,将Canvas设置为原来的1.3倍。


Canvas(
   modifier = Modifier
            .fillMaxSize()
            .scale(1.3f)) {}


最后的效果也就是如下所示: 

3手机陀螺仪传感器


通过手机的旋转,图片进行移动的操作归功于传感器,



如图所示,传感器坐标系一共分为x,y,z三轴,当手机左右翻转时,则是围绕Y轴运动,当手机上下翻转时,则是围绕x轴运动,当手机平放在桌面,左右画圆时,则是围绕z轴运动。


当手机旋转时,传感器则会通知我们三个方向的移动角速度,也就根据这移动角度来确定图片的平移距离。


首先我们先看看传感器该如何监听?Android其实已经为我们封装好了API,SensorManager,直接按照说明创建就好。


val context = LocalContext.current
val sensorManager: SensorManager? = getSystemService(context, SensorManager::class.java)
val sensor = sensorManager?.getDefaultSensor(Sensor.TYPE_GYROSCOPE)

通过getSystemService获取到SensorManager后,设置sensor的种类为TYPE_GYROSCOPE,也就是陀螺仪传感器。并且监听xyz三个方向旋转角速度。


sensorManager?.registerListener(object : SensorEventListener {
    override fun onSensorChanged(event: SensorEvent?) {
    //Y轴角速度
   speedY = event?.values?.get(1)!!
   //X轴角速度
   speedX = event?.values?.get(0)!!
   //Z轴角速度
   speedZ = event?.values?.get(2)!!
}
     override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {

    }
}

通过SensorEventListener监听到手机三个方向的角速度,因为陀螺仪读出的是角速度,大家都知道,角速度乘以时间,就是转过的角度,直接计算旋转的角度值。


// 将手机在各个轴上的旋转角度相加
angularX += (event.values[0] * dT).toLong()
angularY += (event.values[1] * dT).toLong()
angularZ += (event.values[2] * dT).toLong()

//设置x轴y轴最大边界值,
if (angularY > mMaxAnular) {
    angularY = mMaxAnular.toFloat()
else if (angularY < -mMaxAnular) {
    angularY = -mMaxAnular.toFloat()
}

if (angularX > mMaxAnular) {
    angularX = mMaxAnular.toFloat()
else if (angularX < -mMaxAnular) {
    angularX = -mMaxAnular.toFloat()
}

角度计算完成后,因为图片移动是需要移动距离的,那接下来就需要知道图片的平移距离。其实在上面就提出为图片设置了最大平移边界,这里也设置了最大旋转角度,那么就可以依据角度比例来到推出平移距离。


依据公式旋转角度/最大角度 = 平移距离/最大平移距离 反推出 平移距离= 旋转角度/最大角度*最大平移距离。


val xRadio: Float = (angularY / mMaxAnular).toFloat()
val yRadio: Float = (angularX / mMaxAnular).toFloat()
xDistance = xRadio * maxOffset
yDistance = yRadio * maxOffset

图片距离计算完成,基本上随手机移动,图片会呈平移效果,但是发现还有一个问题,onSensorChanged的回调刷新很快,当围绕Y轴左右运动时,图片也会上下平移,这就导致图片会不规则跳动,绕Y轴左右运动其实只需要左右平移即可,同样的,围绕x轴运动,图片只需要上下移动即可。这里针对x,y轴运动,设置了旋转条件控制。


x = Math.abs(event.values[0])
y = Math.abs(event.values[1])
z = Math.abs(event.values[2])

if (x > y + z) {
  xDistance = 0f
  yDistance = yRadio * maxOffset
else if (y > x + z) {
  xDistance = xRadio * maxOffset
  yDistance = 0f}

好了,功能完成,我们来看看最后的效果:



4最后


市面上的App的设计基本上是千篇一律,一个有意思的idea总是会让人多看一眼,再次感谢自如团队提供了这个创意。对了,今天在蚂蚁森林收能量时,发现树木也有点此效果的味道,你不妨去瞅一眼。




因为代码并不复杂,本文没有提供源码。


不过我给大家找了个可以参考的 Android原生版本代码:


标题:用BVP一比一还原自如客APP裸眼3D效果(Android原生)

文章链接:https://juejin.cn/post/6991840263362576421

源码:https://github.com/zhpanvip/AndroidSample/tree/master/app/src/main/java/com/zhpan/sample/banner3d


参考资料:

自如客APP裸眼3D效果的实现

https://juejin.cn/post/6989227733410644005#comment

拿去吧你!Flutter 仿自如 App 裸眼 3D 效果| 8月更文挑战

https://juejin.cn/post/6991409083765129229?from=main_page




最后推荐一下我做的网站,玩Android: wanandroid.com ,包含详尽的知识体系、好用的工具,还有本公众号文章合集,欢迎体验和收藏!


推荐阅读


当Retrofit 遇到协程,如何自动支持?
嗯?Android 消息机制还能难住我?告辞!
关于 Bitmap 你要知道的一切


点击 关注我的公众号

如果你想要跟大家分享你的文章,欢迎投稿~


┏(^0^)┛明天见!

: . Video Mini Program Like ,轻点两下取消赞 Wow ,轻点两下取消在看

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存