最終效果預覽
解決的問題
實現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方法所以無法獲取寬高。
總結
經過以上的所有操作之後便可以實現最開始的效果啦。