Android VR效果实现

最终效果预览

解决的问题

实现iOS锁屏界面壁纸随传感器运动效果
全景看房图片随传感器运动效果
VR效果的实现

以下效果是跟随手机晃动进行的

在这里插入图片描述

思路分析

需要实现的点:

1.图片的左右上下移动
2.旋转矢量传感器TYPE_ROTATION_VECTOR的使用

思路分析:
在这里插入图片描述
我们可以只让图片的一部分给View进行展示,剩下的部分则在View外且不展示。当传感器进行移动时对图片展示的区域进行一个改变便可以达到我们的效果。那就从简单到复杂,来一个一个解决问题。

1.图片的左右上下移动

移动图片有很多办法,我这里提出一个非常简单的思路,使用margin。众所周知,margin为正值时可以调整View之间的距离,为负值时就可以达到我们的目的。下面贴一段代码和一张图,大家就可以简单理解。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:fresco="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".QJActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="200dp">

        <com.facebook.drawee.view.SimpleDraweeView
            android:id="@+id/sdv_qj"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            fresco:actualImageScaleType="centerCrop"
            fresco:placeholderImage="@drawable/ic_launcher_foreground" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="200dp">

        <com.facebook.drawee.view.SimpleDraweeView
            android:id="@+id/sdv_qj1"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_marginLeft="50dp"
            android:layout_marginRight="50dp"
            fresco:actualImageScaleType="centerCrop"
            fresco:placeholderImage="@drawable/ic_launcher_foreground" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="200dp">

        <com.facebook.drawee.view.SimpleDraweeView
            android:id="@+id/sdv_qj2"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_marginLeft="-50dp"
            android:layout_marginRight="-50dp"
            fresco:actualImageScaleType="centerCrop"
            fresco:placeholderImage="@drawable/ic_launcher_foreground" />
    </LinearLayout>

</LinearLayout>

对应的图片如下:
在这里插入图片描述
使用负值的margin即可实现图片的移动,是不是很简单?

注:XML使用了SimpleDraweeView是来自fresco图片框架,如果不会使用可以参考网站Fresco官网或者使用ImageView替代都是可以的

了解了图片的移动,接下来就要知道在代码里如何动态修改Margin,动态修改Margin可以使用LinearLayout的LayoutParams,因为XML的图片父ViewGroup就是LinearLayout,使用LinearLayout的LayoutParams可以直接修改各个方向的Margin。这个非常简单大家可以自己尝试。
这里我不使用LinearLayout的LayoutParams,而是使用ViewGroup的MarginLayoutParams,MarginLayoutParams顾名思义就是来改变Margin的,这里贴上动态修改Margin的代码

ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) mSimpleDraweeView.getLayoutParams();
lp.rightMargin = -1 * outViewX;
lp.leftMargin = -1 * outViewX;
lp.topMargin = -1 * outViewY;
lp.bottomMargin = -1 * outViewY;
mSimpleDraweeView.setLayoutParams(lp);

不用在意outViewX和outViewY,只要了解如何使用MarginLayoutParams动态修改Margin即可。到这已经完成了40%了,我们只要把修改和传感器绑定即可完成上述效果。

2.旋转矢量传感器TYPE_ROTATION_VECTOR的使用

使用传感器一般不要看网上的文章,直接去找Google官方文档即可,这里附上官方文档,以便以后有改变的时候查看:Google文档(搜索关键字即可找到)
下面是官方文档的个人翻译和理解:

旋转矢量传感器TYPE_ROTATION_VECTOR

在这里插入图片描述
(使用数学论证的方法来说明这个传感器)
已知旋转矢量传感器传回来的值是<x,y,z>
假设设备已围绕轴<x,y,z>旋转了角度θ
那么有旋转矢量的三个元素是<x * sin(θ/ 2),y * sin(θ/ 2),z * sin(θ/ 2)>
这样旋转矢量的大小等于sin(θ/ 2),且旋转矢量的方向等于旋转轴的方向。
values[0]: x * sin(θ/2)
values[1]: y * sin(θ/2)
values[2]: z * sin(θ/2)
values[3]: cos(θ/2)
values[4]: estimated heading Accuracy (in radians) (-1 if unavailable)
这样说太过于抽象,为了直观易懂,直接打日志。

以下日志打印顺序为values[0],values[1],values[2]
观察手机右边擡起后的数值变化

1.手机放在桌子上水平数值:

2.8667164,-0.0044089565,0.0014074062

2.手机右边擡起后数值

2.9297764,0.0037661395,-0.3877924

可以看到values[0]和values[1]数值变化不大,变化最大的是values[2]
可以得出结论,手机右侧擡起时values[2]的值变小

观察手机左边擡起后的数值变化

1.手机放在桌子上水平数值:

2.8851805,-0.0047002435,0.0012531724

2.手机左边擡起后数值

2.888664,0.004347841,0.41663048

可以看到values[0]和values[1]数值变化不大,变化最大的是values[2]
可以得出结论,手机右侧擡起时values[2]的值变大

观察手机下边擡起后的数值变化

1.手机放在桌子上水平数值:

2.8837113,-0.004837698,0.0014064246

2.手机下边擡起后数值

2.869607,0.35679248,0.020910122

可以看到values[0]和values[2]数值变化不大,变化最大的是values[1]
可以得出结论,手机下边擡起时values[1]的值变大

观察手机下边擡起后的数值变化

1.手机放在桌子上水平数值:

2.8837113,-0.004837698,0.0014064246

2.手机下边擡起后数值

2.869607,-0.37679248,0.020910122

可以看到values[0]和values[2]数值变化不大,变化最大的是values[1]
可以得出结论,手机下边擡起时values[1]的值变小

可以得出以下表格

手机状态 values[0] values[1] values[2]
左边擡起 - - 变大(正数)
右边擡起 - - 变小(负数)
下边擡起 - 变小(负数) -
上边擡起 - 变大(正数) -
有了这个表格我们便可以得到以下结论(X为偏移系数,通过偏移系数可以决定晃动的幅度)

1.当手机左边擡起/右边擡起时,画面应该向右/向左,则有:
leftMargin = leftMargin - X * values[2] ;
rightMargin = rightMargin + X * values[2]

2.当手机下边擡起/上边擡起时,画面应该向上/向下,则有:
topMargin = topMargin + X * values[1] ;
bottomMargin = bottomMargin - X * values[1]

有了这个公式,我们便可以把传感器的数值和之前的Margin结合起来来实现我们的效果。代码在文末

踩坑大全

1.边界判断

通过上述的结论我们可以得到偏移的值,但是需要注意的一点就是,这个偏移的值不能大于图片隐藏部分的,在偏移过程中需要进行偏移量的判断,方法如下:

private int checkBounds(int val) {
        if (val >= 0) {
        	//margin是不能大于0的,否则会出现白边
            return 0;
        } else if (val <= -1 * outViewX) { 
            //控制边界,不然会出现放大效果^-^
            return -1 * outViewX;
        }
        return val;
    }

2.使用ViewTreeObserver().addOnGlobalLayoutListener来获取正确的图片宽高,然后初始化缩放边界信息

这里主要是监听layout过程来正确的获取组件的宽高,因为在onCreate的时候组件并没有经过measure和layout方法所以无法获取宽高。

总结

经过以上的所有操作之后便可以实现最开始的效果啦。

发布了28 篇原创文章 · 获赞 12 · 访问量 1万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章