Last time, the article introduced the use of sensors to achieve 3D effect, according to the acceleration and gravity sensor, calculate xy offset value, and then move the view.

1. Use MotionLayout

At first, I thought I could do the same with MotionLayout, but finally I found that I was wrong. The view path set by MotionLayout was fixed and could not move freely on the XY axis.

A simple effect:

2. Wrap the View

Using a custom ViewGroup implementation, you can reference it directly in the layout

The main code

Sensor initialization:

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].todegrees () //val x = math.todegrees (values[1].todegrees ()).tofloat () //val y = toDegrees(values[1].todegrees ()).tofloat () //val y = toDegrees() 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) {}} // Sensor initialization 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 }Copy the code

Calculate the moving distance:

private fun calculateScroll(x: Int, y: Int, z: Int) {var deltaY = 0 var deltaX = 0 / / / / to remove a particular view of the if (abs (x) = = 180 | | 90 abs (x) = = | | abs (y) = = 180 | | abs (y) = = 90) return //if (x ! In-90 until 45) return if (abs(z) > 90){if (y in-180 until 0){// From right to left 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) deltaY=0 if (abs(deltaY) < 5) deltaY=0 log("onSensorChanged,scrollX:${deltaX},scrollY:${deltaY},x:${x} y:${y}") }Copy the code

Slide:

 scroller.startScroll(deltaX,deltaY, (this.x+deltaX).toInt(),(this.y+deltaY).toInt(),1000)
Copy the code
{super.com puteScroll override fun computeScroll () ()/if/whether Scroller has been completed (scroller.com puteScrollOffset ()) {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+middleSli deY) topImageView?.layout(topLeft+topSlideX,topTop+topSlideY,topRight+topSlideX,topBottom+topSlideY) /*(bottomImageView As View).scrollto (scroll.currx, scroll.curry)Copy the code

Attribute configuration:

<declare-styleable name="layout3d"> <! <attr name="TopLayer" format="reference" /> <attr name="MiddleLayer" format="reference" /> <attr name="MiddleLayer" 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="MiddleSlidingEnable" format=" Boolean "/  name="BottomSlidingEnable" format="boolean"/> <! <attr name="MiddleLayerTop" format="dimension"/> <attr name="MiddleLayerLeft" format="dimension"/> <attr name="MiddleLayerRight" format="dimension"/> <! <attr name="TopLayerTop" format="dimension"/> <attr name="TopLayerBottom" format=" Dimension "/> <attr name="TopLayerBottom" format="dimension"/ name="TopLayerLeft" format="dimension"/> <attr name="TopLayerRight" format="dimension"/> </declare-styleable>Copy the code

Introduce:

<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" />
Copy the code

End result:

Problem 4.

Do not know why sometimes inexplicable convulsions, movement is not very smooth…

The implementation effect is not very good, there are many small problems, do not recommend using 😥😥

Ask big guy for advice

5. The project address