Parallax-Layer-Layout源码解析

  1. 1. Parallax-Layer-Layout
  2. 2. 使用
  3. 3. 源码
    1. 3.1. SensorTranslationUpdater
    2. 3.2. ParallaxLayerLayout

Github: Parallax-Layer-Layout 分析版本:5375a3a

在 Android 上的分层视差效果的开源库。

Parallax-Layer-Layout

Parallax-Layer-Layout

使用

添加到 layout 中:

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
<com.schibsted.spain.parallaxlayerlayout.ParallaxLayerLayout
android:id="@+id/parallax"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
app:parallaxOffsetBase="20dp"
app:parallaxOffsetIncrement="10dp"
app:parallaxScaleVertical="0.5">


<View
android:id="@+id/layer_3"
android:layout_width="200dp"
android:layout_height="200dp"
android:background="@drawable/background_primary_dark"
app:layout_parallaxZIndex="3"/>


<View
android:id="@+id/layer_2"
android:layout_width="152dp"
android:layout_height="152dp"
android:background="@drawable/background_primary"/>


<View
android:id="@+id/layer_1"
android:layout_width="120dp"
android:layout_height="120dp"
android:background="@drawable/background_accent"/>


</com.schibsted.spain.parallaxlayerlayout.ParallaxLayerLayout>

注册传感器:

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
public class MainActivity extends AppCompatActivity {

private ParallaxLayerLayout parallaxLayout;
private SensorTranslationUpdater translationUpdater;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main_squares);

parallaxLayout = (ParallaxLayerLayout) findViewById(R.id.parallax);
translationUpdater = new SensorTranslationUpdater(this);
parallaxLayout.setTranslationUpdater(translationUpdater);
}

@Override
protected void onResume() {
super.onResume();
translationUpdater.registerSensorManager();
}

@Override
protected void onPause() {
super.onPause();
translationUpdater.unregisterSensorManager();
}
}

源码

SensorTranslationUpdater

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
public class SensorTranslationUpdater implements ParallaxLayerLayout.TranslationUpdater, SensorEventListener {

private static final int DEFAULT_SAMPLING_PERIOD = 100;//传感器回调间隔时间
private final SensorManager sensorManager;//传感器
private float[] mTiltVector = new float[3];
private boolean mTargeted = false;//是否第一次进入或者reset了
private float[] mTargetMatrix = new float[16];//初始矩阵
private float[] mRotationMatrix = new float[16];//当前矩阵
private float[] mOrientedRotationMatrix = new float[16];//屏幕旋转后转换回来之后的矩阵
private float[] mTruncatedRotationVector;//适配机型的传感器返回的数组
private float mTiltSensitivity = 2.0f;//灵敏度
private ParallaxLayerLayout parallax;//View

public SensorTranslationUpdater(Context context) {
this((SensorManager) context.getSystemService(Context.SENSOR_SERVICE));
}

public SensorTranslationUpdater(SensorManager sensorManager) {
this.sensorManager = sensorManager;
}

/**
* 注册传感器
* 使用的是TYPE_ROTATION_VECTOR传感器
* 在Activity的onResume中调用
*/

public void registerSensorManager() {
if (sensorManager != null) {
sensorManager.registerListener(this,sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR), DEFAULT_SAMPLING_PERIOD);
}
}

/**
* 注销传感器
* 在Activity的onPause中调用
*/

public void unregisterSensorManager() {
if (sensorManager != null) {
sensorManager.unregisterListener(this);
}
}

/**
* 传感器的回调
*
* @param event
*/

@Override
public void onSensorChanged(SensorEvent event) {
if (parallax == null) {
return;
}

//得到每个角度都是间于-1到1之间
final float[] vectors = interpretSensorEvent(parallax.getContext(), event);
if (vectors == null) {
return;
}

float roll = vectors[2];//X轴方向的值
float pitch = -vectors[1];//Y轴方向的值

//做平移动画
parallax.updateTranslations(new float[]{roll, pitch});
}

/**
* 将View设置进来
*
* @param parallaxLayerLayout
*/

@Override
public void subscribe(ParallaxLayerLayout parallaxLayerLayout) {
parallax = parallaxLayerLayout;
}

/**
* 清理掉View
*/

@Override
public void unSubscribe() {
parallax = null;
}

}

初始化 SensorTranslationUpdater 完成了,现在看一些方法的具体实现:

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
public class SensorTranslationUpdater implements ParallaxLayerLayout.TranslationUpdater, SensorEventListener {

private static final int DEFAULT_SAMPLING_PERIOD = 100;//传感器回调间隔时间
private final SensorManager sensorManager;//传感器
private float[] mTiltVector = new float[3];
private boolean mTargeted = false;//是否第一次进入或者reset了
private float[] mTargetMatrix = new float[16];//初始矩阵
private float[] mRotationMatrix = new float[16];//当前矩阵
private float[] mOrientedRotationMatrix = new float[16];//屏幕旋转后转换回来之后的矩阵
private float[] mTruncatedRotationVector;//适配机型的传感器返回的数组
private float mTiltSensitivity = 2.0f;//灵敏度
private ParallaxLayerLayout parallax;//View

//最后返回的在-1到1之间
@Nullable
@SuppressWarnings("SuspiciousNameCombination")
private float[] interpretSensorEvent(@NonNull Context context, @Nullable SensorEvent event) {
if (event == null) {
return null;
}

//适配
// Retrieves the RotationVector from SensorEvent
float[] rotationVector = getRotationVectorFromSensorEvent(event);

//第一次的时候会进到这里
//赋值mTargetMatrix,这个为最初始的matrix
//就是将传感器得到的值转换成矩阵
// Set target rotation if none has been set
if (!mTargeted) {
setTargetVector(rotationVector);
return null;
}

//转换成矩阵
// Get rotation matrix from event's values
SensorManager.getRotationMatrixFromVector(mRotationMatrix, rotationVector);

//得到当前屏幕的旋转角度
// Acquire rotation of screen
final int rotation =
((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay()
.getRotation();

//通过屏幕的选择来计算角度
// Calculate angle differential between target and current orientation
if (rotation == Surface.ROTATION_0) {
//通过当前的mRotationMatrix矩阵和最开始的mTargetMatrix矩阵,对比得到变换的角度
SensorManager.getAngleChange(mTiltVector, mRotationMatrix, mTargetMatrix);
} else {
//通过屏幕的选择角度重新映射坐标
// Adjust axes on screen orientation by remapping coordinates
switch (rotation) {
case Surface.ROTATION_90:
SensorManager.remapCoordinateSystem(mRotationMatrix, SensorManager.AXIS_Y,
SensorManager.AXIS_MINUS_X, mOrientedRotationMatrix);
break;

case Surface.ROTATION_180:
SensorManager.remapCoordinateSystem(mRotationMatrix, SensorManager.AXIS_MINUS_X,
SensorManager.AXIS_MINUS_Y, mOrientedRotationMatrix);
break;

case Surface.ROTATION_270:
SensorManager.remapCoordinateSystem(mRotationMatrix, SensorManager.AXIS_MINUS_Y,
SensorManager.AXIS_X, mOrientedRotationMatrix);
break;
default:
break;
}

SensorManager.getAngleChange(mTiltVector, mOrientedRotationMatrix, mTargetMatrix);
}

//得到的角度都是在-π到π之间,转化成-1到1之间
// Perform value scaling and clamping on value array
for (int i = 0; i < mTiltVector.length; i++) {
// Map domain of tilt vector from radian (-PI, PI) to fraction (-1, 1)
mTiltVector[i] /= Math.PI;

// Adjust for tilt sensitivity
mTiltVector[i] *= mTiltSensitivity;

// Clamp values to bounds
if (mTiltVector[i] > 1) {
mTiltVector[i] = 1f;
} else if (mTiltVector[i] < -1) {
mTiltVector[i] = -1f;
}
}

return mTiltVector;
}

/**
* 适配,只要前4个
* 一般的都只返回3个,XYZ
*
* @param event
* @return
*/

@NonNull
private float[] getRotationVectorFromSensorEvent(@NonNull SensorEvent event) {
if (event.values.length > 4) {
// On some Samsung devices SensorManager.getRotationMatrixFromVector
// appears to throw an exception if rotation vector has length > 4.
// For the purposes of this class the first 4 values of the
// rotation vector are sufficient (see crbug.com/335298 for details).
if (mTruncatedRotationVector == null) {
mTruncatedRotationVector = new float[4];
}
System.arraycopy(event.values, 0, mTruncatedRotationVector, 0, 4);
return mTruncatedRotationVector;
} else {
return event.values;
}
}
}

该俩方法就是将传感器返回的值转换成 -1 到 1 区间内的值,然后传递给 ParallaxLayerLayout,再看看 ParallaxLayerLayout

ParallaxLayerLayout

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
197
198
199
200
public class ParallaxLayerLayout extends FrameLayout {
//默认移动范围
private static final int DEFAULT_BASE_OFFSET_DP = 10;
private static final int DEFAULT_OFFSET_INCREMENT_DP = 5;

private Interpolator interpolator = new DecelerateInterpolator();
private int offsetIncrementPx;
private int baseOffsetPx;
private float scaleX = 1.0f;
private float scaleY = 1.0f;
private TranslationUpdater translationUpdater;

public ParallaxLayerLayout(Context context) {
super(context);
}

public ParallaxLayerLayout(Context context, AttributeSet attrs) {
super(context, attrs);

TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ParallaxLayerLayout);
try {
baseOffsetPx =
a.getDimensionPixelSize(R.styleable.ParallaxLayerLayout_parallaxOffsetBase, -1);
if (baseOffsetPx == -1) {
baseOffsetPx =
(int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DEFAULT_BASE_OFFSET_DP,
getResources().getDisplayMetrics());
}

offsetIncrementPx =
a.getDimensionPixelSize(R.styleable.ParallaxLayerLayout_parallaxOffsetIncrement, -1);
if (offsetIncrementPx == -1) {
offsetIncrementPx = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
DEFAULT_OFFSET_INCREMENT_DP, getResources().getDisplayMetrics());
}

scaleX = a.getFloat(R.styleable.ParallaxLayerLayout_parallaxScaleHorizontal, 1.0f);
scaleY = a.getFloat(R.styleable.ParallaxLayerLayout_parallaxScaleVertical, 1.0f);
if (scaleX > 1.0f || scaleX < 0.0f || scaleY > 1.0f || scaleY < 0.0f) {
throw new IllegalArgumentException("Parallax scale must be a value between -1.0 and 1.0");
}
} finally {
a.recycle();
}
}

//region Offset computation
@Override
protected void onFinishInflate() {
super.onFinishInflate();

computeOffsets();

//用于IDE的预览
if (isInEditMode()) {
updateTranslations(new float[]{1.0f, 1.0f});
}
}

private void computeOffsets() {
final int childCount = getChildCount();
for (int i = childCount - 1; i >= 0; i--) {
View child = getChildAt(i);
LayoutParams lp = (LayoutParams) child.getLayoutParams();
int index;
//该childView当前的索引
if (lp.customIndex == -1) {
//Reversed for parallax effect
index = childCount - 1 - i;
} else {
index = lp.customIndex;
}
lp.offsetPx = offsetPxForIndex(index, lp.incrementMultiplier);
}
}

/**
* 这里计算出不同的childView的索引不同从而移动的距离也不同
*
* @param index
* @param incrementMultiplier
* @return
*/

private float offsetPxForIndex(int index, float incrementMultiplier) {
return incrementMultiplier * baseOffsetPx + index * offsetIncrementPx;
}
//endregion

//region Translation

/**
* 移动
*
* @param translations X and Y translation percentage, with values from -1.0 to 1.0.
*/

public void updateTranslations(@Size(2) float[] translations) {
if (Math.abs(translations[0]) > 1 || Math.abs(translations[1]) > 1) {
throw new IllegalArgumentException("Translation values must be between 1.0 and -1.0");
}

final int childCount = getChildCount();//为每个子View都处理
for (int i = childCount - 1; i >= 0; i--) {
View child = getChildAt(i);
float[] translationsPx = calculateFinalTranslationPx(child, translations);//计算移动距离
child.setTranslationX(translationsPx[0]);//移动X轴
child.setTranslationY(translationsPx[1]);//移动Y轴
}
}

/**
* 设置TranslationUpdater
*
* @param translationUpdater
*/

public void setTranslationUpdater(TranslationUpdater translationUpdater) {
if (this.translationUpdater != null) {
this.translationUpdater.unSubscribe();
}
this.translationUpdater = translationUpdater;
this.translationUpdater.subscribe(this);
}

/**
* 计算移动距离
*
* @param child
* @param translations
* @return
*/

@Size(2)
private float[] calculateFinalTranslationPx(View child, @Size(2) float[] translations) {
LayoutParams lp = (LayoutParams) child.getLayoutParams();
int xSign = translations[0] > 0 ? 1 : -1;
int ySign = translations[1] > 0 ? 1 : -1;

float translationX =
xSign * lp.offsetPx * interpolator.getInterpolation(Math.abs(translations[0])) * scaleX;
float translationY =
ySign * lp.offsetPx * interpolator.getInterpolation(Math.abs(translations[1])) * scaleY;

return new float[]{translationX, translationY};
}
//endregion

//region LayoutParams
@Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
return p instanceof LayoutParams;
}

@Override
protected LayoutParams generateDefaultLayoutParams() {
return new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
}

@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new LayoutParams(getContext(), attrs);
}

@Override
protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
return new LayoutParams(p.width, p.height);
}

public interface TranslationUpdater {

void subscribe(ParallaxLayerLayout parallaxLayerLayout);

void unSubscribe();
}
//endregion

public static class LayoutParams extends FrameLayout.LayoutParams {

private float offsetPx;
private int customIndex = -1;
private float incrementMultiplier = 1.0f;

public LayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
gravity = Gravity.CENTER;
TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ParallaxLayerLayout_LayoutParams);
try {
customIndex =
a.getInt(R.styleable.ParallaxLayerLayout_LayoutParams_layout_parallaxZIndex, -1);
incrementMultiplier = a.getFloat(
R.styleable.ParallaxLayerLayout_LayoutParams_layout_parallaxIncrementMultiplier, 1.0f);
} finally {
a.recycle();
}
}

public LayoutParams(int width, int height) {
super(width, height);
gravity = Gravity.CENTER;
}
}
}

自己实现了 LayoutParams ,在其中设置了一些成员变量,这样省去了创建 entity 这种类了。通过 customIndex 的值分别去计算移动的距离,值越大移动距离越多。