前两天,偶然看到自如大前端开源了一个裸眼3D的Banner轮播图实现方案,觉得非常有意思,于是也打算研究一下。

1,实现原理

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

1.1 分层

打开Android Stusio进行布局分析时会发现,他们的Banner使用了两层视图,对应两个Viewpager,并且这两个Viewpager还实现了联动,如下图所示。

除了Viewpager的联动,他们的Banner还支持裸眼3D效果,能够跟随陀螺进行显示上的变化。

1.2 位移

打开自如客App,当用户在不同的角度上看Banner时会看到明显的错位移动。这种错位移动其实借助的是设备本身的传感器来实现的,具体实现方式是让底部的背景始终保持不动,然后根据从设备传感器获取当前设备对应的倾斜角,计算出背景和前景的移动距离,进而执行背景和前景移动的动作,示意图如下。

相关的代码如下:

1, 传感器代码

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);

2,计算偏移角度代码

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);
// x轴的偏转角度
values[1] = (float) Math.toDegrees(values[1]);
// y轴的偏转角度
values[2] = (float) Math.toDegrees(values[2]);

3,执行相对偏移计算

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());

2,Android实现

2.1 传感器监听

其实,实现裸眼3D效果最核心的就是传感器的监听,这个自如客SensorLayout已经进行了开源,SensorLayout通过监听传感器来计算View的位移,然后通过Scroller进行滑动,首选我们添加一个传感器监听的方法,如下所示。

public SensorLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);mScroller = new Scroller(context);mSensorManager = (SensorManager) getContext().getSystemService(Context.SENSOR_SERVICE);// 重力传感器if (mSensorManager != null) {Sensor accelerateSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);// 地磁场传感器Sensor magneticSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);mSensorManager.registerListener(this, accelerateSensor, SensorManager.SENSOR_DELAY_GAME);mSensorManager.registerListener(this, magneticSensor, SensorManager.SENSOR_DELAY_GAME);}
}

然后,在传感器发生变化的时候通过Scroller来移动View,如下所示。

@Override
public void onSensorChanged(SensorEvent event) {if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {mAccelerateValues = event.values;}if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {mMagneticValues = event.values;}float[] values = new float[3];float[] R = new float[9];if (mMagneticValues != null && mAccelerateValues != null)SensorManager.getRotationMatrix(R, null, mAccelerateValues, mMagneticValues);SensorManager.getOrientation(R, values);// x轴的偏转角度values[1] = (float) Math.toDegrees(values[1]);// y轴的偏转角度values[2] = (float) Math.toDegrees(values[2]);double degreeX = values[1];double degreeY = values[2];if (degreeY <= 0 && degreeY > mDegreeYMin) {hasChangeX = true;scrollX = (int) (degreeY / Math.abs(mDegreeYMin) * mXMoveDistance * mDirection);} else if (degreeY > 0 && degreeY < mDegreeYMax) {hasChangeX = true;scrollX = (int) (degreeY / Math.abs(mDegreeYMax) * mXMoveDistance * mDirection);}if (degreeX <= 0 && degreeX > mDegreeXMin) {hasChangeY = true;scrollY = (int) (degreeX / Math.abs(mDegreeXMin) * mYMoveDistance * mDirection);} else if (degreeX > 0 && degreeX < mDegreeXMax) {hasChangeY = true;scrollY = (int) (degreeX / Math.abs(mDegreeXMax) * mYMoveDistance * mDirection);}smoothScroll(hasChangeX ? scrollX : mScroller.getFinalX(), hasChangeY ? scrollY : mScroller.getFinalY());
}

代码中的mDirection表示的是移动的方向,这个参数会开放给使用方,用来设置跟随传感器移动还是与传感器反向移动。

public void smoothScroll(int destX, int destY) {int scrollY = getScrollY();int delta = destY - scrollY;mScroller.startScroll(destX, scrollY, 0, delta, 200);invalidate();
}@Override
public void computeScroll() {if (mScroller.computeScrollOffset()) {scrollTo(mScroller.getCurrX(), mScroller.getCurrY());postInvalidate();}
}

SensorLayout完整的代码如下:

public class SensorLayout extends FrameLayout implements SensorEventListener {private final SensorManager mSensorManager;private float[] mAccelerateValues;private float[] mMagneticValues;private final Scroller mScroller;private double mDegreeYMin = -50;private double mDegreeYMax = 50;private double mDegreeXMin = -50;private double mDegreeXMax = 50;private boolean hasChangeX;private int scrollX;private boolean hasChangeY;private int scrollY;private static final double mXMoveDistance = 40;private static final double mYMoveDistance = 20;private int mDirection = 1;public SensorLayout(@NonNull Context context) {this(context, null);}public SensorLayout(@NonNull Context context, @Nullable AttributeSet attrs) {this(context, attrs, 0);}public SensorLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);mScroller = new Scroller(context);mSensorManager = (SensorManager) getContext().getSystemService(Context.SENSOR_SERVICE);// 重力传感器if (mSensorManager != null) {Sensor accelerateSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);// 地磁场传感器Sensor magneticSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);mSensorManager.registerListener(this, accelerateSensor, SensorManager.SENSOR_DELAY_GAME);mSensorManager.registerListener(this, magneticSensor, SensorManager.SENSOR_DELAY_GAME);}}@Overridepublic void onSensorChanged(SensorEvent event) {if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {mAccelerateValues = event.values;}if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {mMagneticValues = event.values;}float[] values = new float[3];float[] R = new float[9];if (mMagneticValues != null && mAccelerateValues != null)SensorManager.getRotationMatrix(R, null, mAccelerateValues, mMagneticValues);SensorManager.getOrientation(R, values);// x轴的偏转角度values[1] = (float) Math.toDegrees(values[1]);// y轴的偏转角度values[2] = (float) Math.toDegrees(values[2]);double degreeX = values[1];double degreeY = values[2];if (degreeY <= 0 && degreeY > mDegreeYMin) {hasChangeX = true;scrollX = (int) (degreeY / Math.abs(mDegreeYMin) * mXMoveDistance * mDirection);} else if (degreeY > 0 && degreeY < mDegreeYMax) {hasChangeX = true;scrollX = (int) (degreeY / Math.abs(mDegreeYMax) * mXMoveDistance * mDirection);}if (degreeX <= 0 && degreeX > mDegreeXMin) {hasChangeY = true;scrollY = (int) (degreeX / Math.abs(mDegreeXMin) * mYMoveDistance * mDirection);} else if (degreeX > 0 && degreeX < mDegreeXMax) {hasChangeY = true;scrollY = (int) (degreeX / Math.abs(mDegreeXMax) * mYMoveDistance * mDirection);}smoothScroll(hasChangeX ? scrollX : mScroller.getFinalX(), hasChangeY ? scrollY : mScroller.getFinalY());}@Overridepublic void onAccuracyChanged(Sensor sensor, int accuracy) {}public void smoothScroll(int destX, int destY) {int scrollY = getScrollY();int delta = destY - scrollY;mScroller.startScroll(destX, scrollY, 0, delta, 200);invalidate();}@Overridepublic void computeScroll() {if (mScroller.computeScrollOffset()) {scrollTo(mScroller.getCurrX(), mScroller.getCurrY());postInvalidate();}}public void unregister() {mSensorManager.unregisterListener(this);}public void setDegreeYMin(double degreeYMin) {mDegreeYMin = degreeYMin;}public void setDegreeYMax(double degreeYMax) {mDegreeYMax = degreeYMax;}public void setDegreeXMin(double degreeXMin) {mDegreeXMin = degreeXMin;}public void setDegreeXMax(double degreeXMax) {mDegreeXMax = degreeXMax;}public void setDirection(@ADirection int direction) {mDirection = direction;}@IntDef({DIRECTION_LEFT, DIRECTION_RIGHT})@Retention(RetentionPolicy.SOURCE)@Target(ElementType.PARAMETER)public @interface ADirection {}public static final int DIRECTION_LEFT = 1;public static final int DIRECTION_RIGHT = -1;
}

2.2 SensorLayout示例

其实,明白裸眼3D的原理后,我们使用SensorLayout就可以很容易实现这种效果。下面是使用SensorLayout实现单个页面的裸眼3D效果,只需要使用SensorLayout包裹对应的图片即可。

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="wrap_content"><com.xzh.vrgame.banner3d.SensorLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"android:layout_marginBottom="25dp"><ImageViewandroid:id="@+id/iv_background"android:layout_width="match_parent"android:layout_height="wrap_content"android:scaleType="centerCrop"android:scaleX="1.3"android:src="@drawable/background1"/></com.xzh.vrgame.banner3d.SensorLayout><ImageViewandroid:id="@+id/iv_mid"android:layout_width="match_parent"android:layout_height="100dp"android:layout_gravity="bottom"android:layout_marginStart="16dp"android:layout_marginEnd="16dp"android:scaleType="fitXY"android:src="@drawable/mid1"/><com.xzh.vrgame.banner3d.SensorLayoutandroid:id="@+id/sensor_layout"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_gravity="bottom"><ImageViewandroid:id="@+id/iv_foreground"android:layout_width="match_parent"android:layout_height="100dp"android:scaleType="fitXY"android:src="@drawable/foreground1"/></com.xzh.vrgame.banner3d.SensorLayout></FrameLayout>

2.3 ViewPager裸眼3D轮播图示例

通过前面的分析,自如APP的裸眼3D用到了两个ViewPager,然后让他们实现联动。其实,我们可以把背景层使用ImageView,然后前景层再使ViewPager也可以实现3D轮播的效果,通过监听前景层的ViewPager,来改变背景层使用ImageView。布局文件代码如下:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"><com.xzh.vrgame.banner3d.SensorLayoutandroid:id="@+id/sensor_layout"android:layout_width="match_parent"android:layout_height="200dp"><ImageViewandroid:id="@+id/iv_background"android:layout_width="match_parent"android:scaleType="centerCrop"android:scaleX="1.3"android:layout_height="match_parent" /></com.xzh.vrgame.banner3d.SensorLayout><com.xzh.vrgame.widget.AutoPlayViewPagerandroid:id="@+id/avp_foreground"android:layout_width="match_parent"android:layout_height="220dp" /></FrameLayout>

然后就是使用ViewPager+PageAdapter实现轮播。当然,大家也可以使用一些轮播的库减少代码,比如convenientbanner,最终效果如下图所示。

代码链接如下:https://github.com/xiangzhihong/AndroidDemo

Android仿自如客APP裸眼3D效果相关推荐

  1. Android OpenGL 仿自如 APP 裸眼 3D 效果

    概述 之前看到 自如团队 发布的 自如客APP裸眼3D效果的实现 ,非常有趣,不久后,社区内 Android 的开发者们陆续提供了 Flutter. Android 原生 .Android Jetpa ...

  2. 卷起来了!Android OpenGL仿自如APP裸眼3D效果

    /   今日科技快讯   / 近日,"乘联会"微信公众号发布消息,2021年12月新能源乘用车市场多元化发力,厂商批发销量突破万辆的企业有14家,较前期大幅增多,其中:比亚迪933 ...

  3. 设计师:裸眼 3D 效果,你们客户端实现很难吗?

    自如-黄进 | 作者 承香墨影 | 编辑 https://juejin.cn/post/6989227733410644005 | 原文 Hi,大家好,这里是承香墨影! 说到裸眼 3D 效果,最先想到 ...

  4. 怎么设置ppt页面的长度和宽度_在PPT中将照片变裸眼3D效果怎样操作?分享技巧,帮你快速实现...

    PPT的使用相信大家都不陌生,使用最多的就是制作PPT对工作进行汇报,对新项目进行展开讨论.其实在PPT中还可以设计海报,制作高逼格封面以及将照片变为3D效果等偏设计类的操作.今天将以如何把照片变为3 ...

  5. 投影仪的裸眼3D效果

    裸眼3D是指在看投影仪投出来的画面时,不需要戴3D眼镜就能看到立体效果的技术.裸眼3D投影仪通常使用两个投影仪同时投出左右眼图像,利用人眼视差原理来产生立体效果.这种方式看起来比较自然,但是画面质量通 ...

  6. 基于android的裸眼3d,午诺裸眼3D原理其实并不复杂

    午诺裸眼3D原理其实并不复杂 其实同样是裸眼3D手机,可能其中含有的技术却大不相同.午诺P8采用的是国内领先3D光学厂商康得新提供的柱状光栅技术,屏幕通过特殊处理给左右眼的图像是不一样的,消费者面对手 ...

  7. 裸眼 3D 是什么效果?

    作者:沙因,腾讯 IEG 前端开发工程师 介绍一种裸眼 3D 的实现方式,代码以 web 端为例. 平常我们都是戴着 3D 眼镜才能感受 3D 效果,那裸眼能直接看 3D 么?可以看看下面这个视频: ...

  8. 户外LED显示屏如何实现裸眼3D显示效果:创造逼真立体体验的新视界

    随着科技的不断进步,户外LED显示屏已成为现代广告和娱乐领域中不可或缺的元素.而在这个数字化时代,人们对于视觉体验的要求也越来越高.裸眼3D效果作为一种引人注目的显示技术,为户外LED显示屏注入了全新 ...

  9. 成都太古里,溢出屏幕的裸眼3d

    在成都太古里和春熙路交界处有一块裸眼3D屏,早在十月份的时候就凭借一段酷炫真实的外星飞碟裸眼3d视频登上热搜,刷爆朋友圈,让本就是打卡圣地的太古里再一次上升成为一个网红打卡必经之地. 数字平原有幸参与 ...

  10. 史帝奇文旅项目篇——穿越式裸眼3D轨道影院

    目前国内 A 级景区.文化商业街区.城市商业空间等文旅目的地急需,提质增收促进二消,提供造血功能.以投资小.见效快.精运营.品质化的新业态.新场景成为未来文旅市场的重要商机. "穿越式裸眼3 ...

最新文章

  1. OpenGL第三方库:GLFW入门篇
  2. 【STM32】中断相关函数和类型
  3. @RequestMapping 和 @GetMapping @PostMapping 区别
  4. python直方图的拟合_从一组数据python中将两个高斯拟合成直方图
  5. 2020“家”经济时代开启——中国到家服务行业研究报告
  6. matplotlin 入门
  7. Bambook 简介
  8. cms采集系统-批量文章采集支持各大CMS采集
  9. 电子设备常见的音视频接口
  10. linux删除回收站提示权限错误
  11. 关于小米手机USB传输稍大点的文件老中断的问题解决方法!
  12. Burg法求解AR(p)模型参数(一)自回归模型
  13. 福大计算机课程表,福州大学研究生院-通知公告-福州大学课程表(非全日制工程硕士研究生2017年周末班公共课3-5月份 )...
  14. python scratch 图形化_走进Scratch图形化编程
  15. 二维码原理与编码介绍
  16. c语言定义max和命令,C语言#define定义函数
  17. “作环保的程序员,从不用百度开始”(转自酷壳)
  18. 微信小程序部分面试题汇总
  19. 关于a:visited稀奇古怪直接生效或不生效的问题
  20. 命令提示符_常用命令1

热门文章

  1. 油罐清洗抽吸系统设计
  2. 新手不翻车的可乐鸡翅做法!好吃到吮指!
  3. Cluster-based Beam Search for Pointer-Generator Chatbot Grounded by Knowledge
  4. windows上未关闭135、445等危险端口引发的威胁
  5. 一文带你搞懂Vue中的Excel导入导出
  6. Echarts世界国家中英文对照
  7. C语言编程 - 推箱子小游戏源码分享 (含过关)
  8. 【实习日报】2019年4月下半月 前端开发实习工作日报汇总
  9. Java double value_Java Double doubleValue()用法及代码示例
  10. IDEA 启动本地 Flink Web UI