/
GravityRotationImageView.kt
196 lines (174 loc) · 6.29 KB
/
GravityRotationImageView.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
package com.ziwenl.library
import android.content.Context
import android.hardware.SensorManager
import android.util.AttributeSet
import android.widget.Scroller
import androidx.appcompat.widget.AppCompatImageView
import kotlin.math.abs
/**
* PackageName : com.ziwenl.library
* Author : Ziwen Lan
* Date : 2021/9/18
* Time : 14:13
* Introduction : 重力感应 ImageView
* 通过从 GravityRotationHelper 处得到的磁场及加速度传感器数据,计算设备倾斜角度进行移动当前 view
*/
class GravityRotationImageView : AppCompatImageView {
companion object {
/**
* 最大旋转角度
*/
private const val MAX_ROTATION_ANGLE = 60
private const val MAX_ROTATION_ANGLE_Y = 100
/**
* 默认可移动范围 20dp
*/
internal const val MOVING_RANGE_DEFAULT = 20f
/**
* 角度变化响应值范围,通过该范围来过滤抖动与跳动
*/
private const val RESPONSE_ANGLE_CHANGE_MIN = 8
private const val RESPONSE_ANGLE_CHANGE_MAX = 40
private const val DIRECTION_BACK = 1
private const val DIRECTION_FRONT = -1
}
/**
* 最大可移动范围(前景后景的可移动范围)
*/
private var mMaxMovingRange = 0
/**
* 移动方向
*/
private var mDirection = DIRECTION_FRONT
/**
* 分别记录沿 X、Y轴旋转角度
* mRotationAngleX :沿 X 轴旋转角度,手机正面往上竖起来时,值越小
* mRotationAngleY :沿 Y 轴旋转角度,手机正面往右竖起来时,值越小
*/
internal var rotationAngleX = 0
internal var rotationAngleY = 0
/**
* 滚动辅助对象
*/
private lateinit var mScroller: Scroller
constructor(context: Context) : super(context) {
initView(context, null, 0)
}
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
initView(context, attrs, 0)
}
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
context,
attrs,
defStyleAttr
) {
initView(context, attrs, defStyleAttr)
}
private fun initView(context: Context, attrs: AttributeSet?, defStyleAttr: Int) {
mScroller = Scroller(context)
mMaxMovingRange = dip2px(context, MOVING_RANGE_DEFAULT)
val typedArray =
context.obtainStyledAttributes(
attrs,
R.styleable.GravityRotationImageView,
defStyleAttr,
0
)
val isBack = typedArray.getBoolean(R.styleable.GravityRotationImageView_isBack, false)
isBack(isBack)
typedArray.recycle()
}
override fun computeScroll() {
super.computeScroll()
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.currX, mScroller.currY)
postInvalidate()
}
}
/**
* 平滑移动 view
*/
private fun smoothScroll(dx: Int, dy: Int) {
mScroller.startScroll(scrollX, scrollY, dx, dy, 650)
invalidate()
}
/**
* 根据手机的分辨率从 dp 的单位 转成为 px(像素)
*/
private fun dip2px(context: Context, dpValue: Float): Int {
val scale = context.resources.displayMetrics.density
return (dpValue * scale + 0.5f).toInt()
}
/**
* 设置当前 view 为前景或后景
* @param isBack true 后景 ; false 前景
*/
fun isBack(isBack: Boolean) {
/**
* 判断该 view 用作前景还是后景
* 后景则需调整放大倍数使内容滚动时不会出现白边
* 并根据前后景记录对应的滚动方向
*/
if (isBack) {
mDirection = DIRECTION_BACK
scaleType = ScaleType.CENTER_CROP
scaleX = 1.1f
scaleY = 1.2f
} else {
mDirection = DIRECTION_FRONT
}
}
/**
* 处理传感器得到的数据,过滤后再根据倾斜角度移动当前 view
* 旋转移动过程中,前景后景随旋转角度偏移
*/
internal fun handleSensorChangedValues(
gravity: FloatArray,
geomagnetic: FloatArray,
maxMovingRange: Float = MOVING_RANGE_DEFAULT
) {
if (maxMovingRange != MOVING_RANGE_DEFAULT) {
mMaxMovingRange = dip2px(this.context, maxMovingRange)
}
//旋转角度值集
val orientationValues = FloatArray(3)
//旋转矩阵
val rotationMatrix = FloatArray(9)
SensorManager.getRotationMatrix(
rotationMatrix,
null,
gravity,
geomagnetic
)
SensorManager.getOrientation(rotationMatrix, orientationValues)
// z 轴的偏转角度
orientationValues[0] = Math.toDegrees(orientationValues[0].toDouble()).toFloat()
// x 轴的偏转角度
orientationValues[1] = Math.toDegrees(orientationValues[1].toDouble()).toFloat()
// y 轴的偏转角度
orientationValues[2] = Math.toDegrees(orientationValues[2].toDouble()).toFloat()
val newAngleX = orientationValues[1].toInt()
val newAngleY = orientationValues[2].toInt()
// x 、 y 轴角度变化值
val rotationAngleXChangeValue = abs(newAngleX - rotationAngleX)
val rotationAngleYChangeValue = abs(newAngleY - rotationAngleY)
var targetX = mScroller.finalX
var targetY = mScroller.finalY
if (rotationAngleYChangeValue in (RESPONSE_ANGLE_CHANGE_MIN + 1) until RESPONSE_ANGLE_CHANGE_MAX
|| rotationAngleXChangeValue in (RESPONSE_ANGLE_CHANGE_MIN + 1) until RESPONSE_ANGLE_CHANGE_MAX
) {
if (newAngleX <= 0 && newAngleX > -MAX_ROTATION_ANGLE || newAngleX in 1 until MAX_ROTATION_ANGLE) {
targetY = mMaxMovingRange * -mDirection * newAngleX / MAX_ROTATION_ANGLE_Y
}
if (newAngleY <= 0 && newAngleY > -MAX_ROTATION_ANGLE || newAngleY in 1 until MAX_ROTATION_ANGLE) {
targetX = mMaxMovingRange * mDirection * newAngleY / MAX_ROTATION_ANGLE
}
val dx = targetX - scrollX
val dy = targetY - scrollY
smoothScroll(dx, dy)
//更新角度
rotationAngleX = newAngleX
rotationAngleY = newAngleY
}
}
}