詳解Android 裸眼3D效果View控件
描述:這是一個(gè)裸眼3D效果的控件View。
Tips:本項(xiàng)目代碼部分邏輯參考于其他文章(自如的3D裸眼實(shí)現(xiàn)),眾人拾柴火焰高,希望大家能多多補(bǔ)充。
項(xiàng)目代碼:https://gitee.com/jiugeishere/uidesign
控件效果如下:

實(shí)現(xiàn)功能:
- 實(shí)現(xiàn)三層圖片疊加效果(裸眼3D效果)
- 可設(shè)置每層圖片移動(dòng)速率
- 可設(shè)置每層圖片移動(dòng)的限制度數(shù)
- 可直接設(shè)置圖片或引入圖片
設(shè)計(jì)核心:
主要的設(shè)計(jì)核心是依賴于傳感器對(duì)手機(jī)晃動(dòng)的監(jiān)聽(重力感應(yīng)監(jiān)聽器),對(duì)每層圖片進(jìn)行不同的移動(dòng),實(shí)現(xiàn)仿3D效果。
核心代碼:
SensorLayout 用以監(jiān)聽傳感器
import android.content.Context;
import android.content.res.TypedArray;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.util.AttributeSet;
import android.widget.FrameLayout;
import android.widget.Scroller;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.ui.design.R;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 傳感器監(jiān)聽
* author tangxianfeng
* created 2021.8.15
**/
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;//最小偏移度數(shù) Y
private double mDegreeYMax = 50;//最大偏移度數(shù) Y
private double mDegreeXMin = -50;//最小偏移度數(shù) X
private double mDegreeXMax = 50;//最大偏移度數(shù) X
private static final double MOVE_DISTANCE_X = 50;//X軸移動(dòng)偏移量 實(shí)際偏移為MOVE_DISTANCE_X*acclerateratio
private static final double MOVE_DISTANCE_Y = 50;//Y軸移動(dòng)偏移量 實(shí)際偏移為MOVE_DISTANCE_Y*acclerateratio
private float acclerateratio = 1;//偏移加速的倍率 可以通過設(shè)置此倍率改變偏移速度
private final float[] values = new float[3];//包含 x,y,z的偏移量
private final float[] Sensororientation = new float[9];//旋轉(zhuǎn)矩陣
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);
if (attrs != null) {
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.SensorLayoutStyle);
acclerateratio = typedArray.getFloat(R.styleable.SensorLayoutStyle_AccelerateRatio, 1);
}
mSensorManager = (SensorManager) getContext().getSystemService(Context.SENSOR_SERVICE);
if (mSensorManager != null) {
Sensor accelerateSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
// 地磁場(chǎng)傳感器
Sensor magneticSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
mSensorManager.registerListener(this, accelerateSensor, SensorManager.SENSOR_DELAY_GAME);
mSensorManager.registerListener(this, magneticSensor, SensorManager.SENSOR_DELAY_GAME);
}
}
@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;
}
if (mMagneticValues != null && mAccelerateValues != null)
SensorManager.getRotationMatrix(Sensororientation, null, mAccelerateValues, mMagneticValues);
SensorManager.getOrientation(Sensororientation, values);
// x軸的偏轉(zhuǎn)角度
double degreeX = (float) Math.toDegrees(values[1]);
// y軸的偏轉(zhuǎn)角度
double degreeY = (float) Math.toDegrees(values[2]);
int scrollX = mScroller.getFinalX();
int scrollY = mScroller.getFinalY();
if (degreeY <= 0 && degreeY > mDegreeYMin) {
scrollX = (int) (degreeY / Math.abs(mDegreeYMin) * MOVE_DISTANCE_X * acclerateratio);
} else if (degreeY > 0 && degreeY < mDegreeYMax) {
scrollX = (int) (degreeY / Math.abs(mDegreeYMax) * MOVE_DISTANCE_X * acclerateratio);
}
if (degreeX <= 0 && degreeX > mDegreeXMin) {
scrollY = (int) (degreeX / Math.abs(mDegreeXMin) * MOVE_DISTANCE_Y * acclerateratio);
} else if (degreeX > 0 && degreeX < mDegreeXMax) {
scrollY = (int) (degreeX / Math.abs(mDegreeXMax) * MOVE_DISTANCE_Y * acclerateratio);
}
smoothScroll(scrollX, scrollY);
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
//移動(dòng)
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();
}
}
//解綁監(jiān)聽
public void unregister() {
mSensorManager.unregisterListener(this);
}
public void setDegree(double degreeYMin,double degreeYMax,double degreeXMin,double degreeXMax) {
mDegreeYMin = degreeYMin;
mDegreeYMax=degreeYMax;
degreeXMax=degreeYMax;
degreeXMin=degreeXMin;
}
public void setAcclerateratio(float acclerateratio) {
this.acclerateratio = acclerateratio;
}
@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;
}
Sensor3DView 三層視圖封裝
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import androidx.annotation.Nullable;
import com.bumptech.glide.Glide;
import com.ui.design.R;
/**
* author tangxianfeng
* created 2021.8.15
**/
public class Sensor3DView extends LinearLayout {
private SensorLayout sensorforeground;//最上層傳感器View
private SensorLayout sensorbackground;//最底層傳感器View
private SensorLayout sensormid;//中間層傳感器View
private ImageView foregroundimg;//最上層圖片
private ImageView backgroundimg;//底層圖片
private ImageView midimg;//中間層圖片
private Context mContext;
public Sensor3DView(Context context) {
super(context);
this.mContext = context;
}
public Sensor3DView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
View inflate = LayoutInflater.from(getContext()).inflate(R.layout.sensor3d_item, this);
this.mContext = context;
initView(inflate);
if (attrs != null) {
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.Sensor3DViewStyle);
float forgroundacclerateratio = typedArray.getFloat(R.styleable.Sensor3DViewStyle_foregroundAccelerateRatio, 1);
float backgroundacclerateratio = typedArray.getFloat(R.styleable.Sensor3DViewStyle_backgroundAccelerateRatio, 1);
float midacclerateratio = typedArray.getFloat(R.styleable.Sensor3DViewStyle_midAccelerateRatio, 1);
setAllImg(typedArray.getResourceId(R.styleable.Sensor3DViewStyle_backgrounddrawable,1),typedArray.getResourceId(R.styleable.Sensor3DViewStyle_middrawable,1),typedArray.getResourceId(R.styleable.Sensor3DViewStyle_foregrounddrawable,1));
setAllratio(backgroundacclerateratio, midacclerateratio, forgroundacclerateratio);
}
}
private void initView(View inflate) {
sensorforeground = inflate.findViewById(R.id.sensorforeground);
sensorbackground = inflate.findViewById(R.id.sensorbackground);
sensormid = inflate.findViewById(R.id.sensormid);
midimg = inflate.findViewById(R.id.midimg);
backgroundimg = inflate.findViewById(R.id.backgroundimg);
foregroundimg = inflate.findViewById(R.id.foregroundimg);
}
//加載三張圖片
public void setAllImg(Object backgroundurl, Object midurl, Object foregroundurl) {
Glide.with(mContext).load(backgroundurl).into(backgroundimg);
Glide.with(mContext).load(midurl).into(midimg);
Glide.with(mContext).load(foregroundurl).into(foregroundimg);
}
//設(shè)置移動(dòng)速度
public void setAllratio(float backgroundratio, float midratio, float foregroundratio) {
sensorbackground.setAcclerateratio(backgroundratio);
sensormid.setAcclerateratio(midratio);
sensorforeground.setAcclerateratio(foregroundratio);
}
//設(shè)置限制角度
public void setDegree(float MinX,float MinY,float MaxX,float MaxY,View3DLayer layer){
if (MinX>=MaxX||MinY>=MaxY){
return;
}
switch (layer){
case all:
setDegree(MinY,MaxY,MinX,MaxX,sensorforeground);
setDegree(MinY,MaxY,MinX,MaxX,sensormid);
setDegree(MinY,MaxY,MinX,MaxX,sensorbackground);
break;
case mid:
setDegree(MinY,MaxY,MinX,MaxX,sensormid);
break;
case background:
setDegree(MinY,MaxY,MinX,MaxX,sensorbackground);
break;
case foreground:
setDegree(MinY,MaxY,MinX,MaxX,sensorforeground);
break;
}
}
//sensorLayout 設(shè)置限制角度
private void setDegree(float MinY,float MaxY,float MinX,float MaxX,SensorLayout sensorLayout){
sensorLayout.setDegree(MinY,MaxY,MinX,MaxX);
}
@Override
public void destroyDrawingCache() {
super.destroyDrawingCache();
sensorbackground.unregister();
sensormid.unregister();
sensorforeground.unregister();
}
public enum View3DLayer{
foreground,
background,
mid,
all
}
}
styles.xml
<!--3D裸眼效果-->
<declare-styleable name="SensorLayoutStyle">
<attr name="AccelerateRatio" format="float" />
</declare-styleable>
<!--3D裸眼效果集合View-->
<declare-styleable name="Sensor3DViewStyle">
<attr name="foregroundAccelerateRatio" format="float" />
<attr name="backgroundAccelerateRatio" format="float" />
<attr name="midAccelerateRatio" format="float" />
<attr name="foregrounddrawable" format="reference" />
<attr name="backgrounddrawable" format="reference" />
<attr name="middrawable" format="reference" />
</declare-styleable>
使用示例:
直接引用到layout文件中便可,或者可通過代碼設(shè)置其他屬性。
<com.ui.design.view.sensor3D.view.Sensor3DView
android:id="@+id/sensor3Dview"
android:layout_width="match_parent"
android:layout_height="200dp"
android:layout_centerInParent="true"
app:foregrounddrawable="@drawable/forground3d"
app:backgrounddrawable="@drawable/background3d"
app:middrawable="@drawable/mid3d"
app:foregroundAccelerateRatio="4.0"
app:backgroundAccelerateRatio="-2.0"
app:midAccelerateRatio="1.0"/>
項(xiàng)目代碼倉庫 UIDesign 開源項(xiàng)目
到此這篇關(guān)于詳解Android 裸眼3D效果View控件的文章就介紹到這了,更多相關(guān)Android 裸眼3D效果內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Flutter持久化存儲(chǔ)之?dāng)?shù)據(jù)庫存儲(chǔ)(sqflite)詳解
這篇文章主要給大家介紹了關(guān)于Flutter持久化存儲(chǔ)之?dāng)?shù)據(jù)庫存儲(chǔ)的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者使用Flutter具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-03-03
Android之自定義實(shí)現(xiàn)BaseAdapter(通用適配器三)
這篇文章主要為大家詳細(xì)介紹了Android之自定義實(shí)現(xiàn)BaseAdapter通用適配器第三篇,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-12-12
Android4.1中BinderService用法實(shí)例分析
這篇文章主要介紹了Android4.1中BinderService用法,以實(shí)例形式分析了Android4.1新增BinderService類的功能、原理及使用技巧,具有一定參考借鑒價(jià)值2015-10-10
Kotlin 集合函數(shù)map 和 first 的使用場(chǎng)景分析
Kotlin 提供了許多強(qiáng)大的集合操作函數(shù),其中 map 適用于轉(zhuǎn)換集合,first 適用于獲取符合條件的第一個(gè)元素,這篇文章給大家介紹Kotlin 集合函數(shù):map 和 first 的使用場(chǎng)景,感興趣的朋友一起看看吧2025-04-04
詳細(xì)分析Android中onTouch事件傳遞機(jī)制
相信不少朋友在剛開始學(xué)習(xí)Android的時(shí)候,對(duì)于onTouch相關(guān)的事件一頭霧水。分不清onTouch(),onTouchEvent()和OnClick()之間的關(guān)系和先后順序,所以覺得有必要搞清onTouch事件傳遞的原理。經(jīng)過一段時(shí)間的琢磨以及相關(guān)博客的介紹,這篇文章就給大家詳細(xì)的分析介紹下。2016-10-10
Android中3種全屏方法及3種去掉標(biāo)題欄的方法
這篇文章主要介紹了Android中3種全屏方法及3種去掉標(biāo)題欄的方法,二個(gè)問題各給出了3種解決方法,并給出實(shí)例代碼,需要的朋友可以參考下2015-06-06
Android仿360桌面手機(jī)衛(wèi)士懸浮窗效果
這篇文章主要介紹了Android仿360手機(jī)衛(wèi)士懸浮窗效果的桌面實(shí)現(xiàn),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-05-05

