View实现3D效果

上次有文章介绍了利用传感器实现3D效果,根据加速度和重力传感器,计算xy偏移值,然后在移动view。

1. 利用MotionLayout实现

最开始想到的是用motionlayout也可以同样实现,但是最后发现我错了,motionlayout设置的view路径是固定的,无法在xy轴上自由移动。

一个简单的效果:
1.gif

2. 封装View

利用自定义ViewGroup实现,直接在布局中引用就行了

主要代码

传感器初始化:

//获取传感器XYZ值
private var mAcceleValues : FloatArray ?= null
private var mMageneticValues : FloatArray ?= null
private val listener = object : SensorEventListener {
override fun onSensorChanged(event: SensorEvent?) {
if (event?.sensor?.type == Sensor.TYPE_ACCELEROMETER) {
mAcceleValues = event.values
//log("x:${event.values[0]},y:${event.values[1]},z:${event.values[2]}")
}
if (event?.sensor?.type == Sensor.TYPE_MAGNETIC_FIELD) {
mMageneticValues = event.values
}
if (mAcceleValues==null || mMageneticValues==null) return

val values = FloatArray(3)
val R = FloatArray(9)
SensorManager.getRotationMatrix(R, null, mAcceleValues, mMageneticValues);
SensorManager.getOrientation(R, values);

//val z = values[0].toDouble()
// x轴的偏转角度
//val x = Math.toDegrees(values[1].toDouble()).toFloat()
// y轴的偏转角度
//val y = Math.toDegrees(values[2].toDouble()).toFloat()

val degreeZ = Math.toDegrees(values[0].toDouble()).toInt()
val degreeX = Math.toDegrees(values[1].toDouble()).toInt()
val degreeY = Math.toDegrees(values[2].toDouble()).toInt()
log("x:${degreeX},y:${degreeY},z:${degreeZ}")
calculateScroll(degreeX, degreeY, degreeZ)
}

override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {
}
}



//传感器初始化
private var hasInit = false
private fun initSensor(){
if (hasInit) return
log("initSensor")
val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager

// 重力传感器
val acceleSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
sensorManager.registerListener(listener, acceleSensor, SensorManager.SENSOR_DELAY_GAME)

// 地磁场传感器
val magneticSensor = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD)
sensorManager.registerListener(listener, magneticSensor, SensorManager.SENSOR_DELAY_GAME)

hasInit = true
}

计算移动距离:

private fun calculateScroll(x: Int, y: Int, z: Int) {
var deltaY = 0
var deltaX = 0

//除去一下特殊的角度
//if (abs(x)==180 || abs(x)==90 || abs(y)==180 || abs(y)==90) return
//if (x !in -90 until 45) return

if (abs(z) > 90){
if (y in -180 until 0){
//从右向左旋转
//x值增加
deltaX = abs(((y / 180.0) * slideDistance).toInt())
}else if (y in 0 until 180){
//从左向右旋转
deltaX = -abs(((y / 180.0) * slideDistance).toInt())
}
}

if (x in -90 until 0){
deltaY = abs((((x) / 180.0) * slideDistance).toInt())
}else if (x in 0 until 90){
deltaY = -abs((((x) / 180.0) * slideDistance).toInt())
}

if (abs(deltaX) < 5) deltaX=0
if (abs(deltaY) < 5) deltaY=0

log("onSensorChanged,scrollX:${deltaX},scrollY:${deltaY},x:${x} y:${y}")
}

滑动:

 scroller.startScroll(deltaX,deltaY, (this.x+deltaX).toInt(),(this.y+deltaY).toInt(),1000)
override fun computeScroll() {
super.computeScroll()
//判断Scroller是否执行完毕
if (scroller.computeScrollOffset()) {
val slideX = scroller.currX
val slideY = scroller.currY

val bottomSlideX = slideX/3
val bottomSlideY = slideY/3

val middleSlideX = slideX/2
val middleSlideY = slideY/2

val topSlideX = -slideX
val topSlideY = -slideY

bottomImageView?.layout(0+bottomSlideX,0+bottomSlideY,measuredWidth+bottomSlideX,measuredHeight+bottomSlideY)
//middleImageView?.layout(middleLeft+middleSlideX,middleTop+middleSlideY,middleRight+middleSlideX,middleBottom+middleSlideY)
topImageView?.layout(topLeft+topSlideX,topTop+topSlideY,topRight+topSlideX,topBottom+topSlideY)

/*(bottomImageView as View).scrollTo(
scroller.currX,
scroller.currY
)*/
//通过重绘来不断调用computeScroll
invalidate()
}
}


属性配置:

<declare-styleable name="layout3d">
<!--上中下层资源文件-->
<attr name="TopLayer" format="reference" />
<attr name="MiddleLayer" format="reference" />
<attr name="BottomLayer" format="reference" />

<!--上中下层滑动距离-->
<attr name="SlideDistance" format="dimension"/>

<!--上中下层是否滑动-->
<attr name="TopSlidingEnable" format="boolean"/>
<attr name="MiddleSlidingEnable" format="boolean"/>
<attr name="BottomSlidingEnable" format="boolean"/>


<!--中层图片资源坐标设置-->
<attr name="MiddleLayerTop" format="dimension"/>
<attr name="MiddleLayerBottom" format="dimension"/>
<attr name="MiddleLayerLeft" format="dimension"/>
<attr name="MiddleLayerRight" format="dimension"/>

<!--上层图片资源坐标设置-->
<attr name="TopLayerTop" format="dimension"/>
<attr name="TopLayerBottom" format="dimension"/>
<attr name="TopLayerLeft" format="dimension"/>
<attr name="TopLayerRight" format="dimension"/>

</declare-styleable>
```

在布局中引入:

<com.example.montionlayout3d.view.Layout3D
android:id="@+id/vg3D"
android:layout_width="match_parent"
android:layout_height="300dp"
android:background="@color/titi"
app:TopLayer="@drawable/circle"
app:MiddleLayer="@drawable/star1"
app:BottomLayer="@drawable/back"
layout3d:MiddleLayerTop="30dp"
layout3d:MiddleLayerBottom="70dp"
layout3d:MiddleLayerLeft="300dp"
layout3d:MiddleLayerRight="340dp"
layout3d:TopLayerTop="50dp"
layout3d:TopLayerBottom="200dp"
layout3d:TopLayerLeft="20dp"
layout3d:TopLayerRight="170dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

最终效果:

2.gif

3.gif


0 个评论

要回复文章请先登录注册