SwipeBackLayout源码解析

  1. 1. SwipeBackLayout
  2. 2. 使用
    1. 2.1. 注意
    2. 2.2. demo
  3. 3. 源码
    1. 3.1. SwipeBackActivity
    2. 3.2. SwipeBackActivityHelper
    3. 3.3. SwipeBackLayout
    4. 3.4. Utils
  4. 4. 总结

Github: SwipeBackLayout 分析版本:e4ddae6

SwipeBackLayout 是一个仿 IOS 通过手势退出界面的开源库。

SwipeBackLayout

SwipeBackLayout

SwipeBackLayout 可以通过在左、右和下边缘来拖动整个 Activity 达到退出 Activity 的效果。

使用

添加到 Gradle :

1
compile 'me.imid.swipebacklayout.lib:library:1.0.0'

继承 SwipeBackActivity

1
2
public class DemoActivity extends SwipeBackActivity {
}
  • onCreatesetContentView() 照常使用
  • 可以通过 getSwipeBackLayout() 定制 SwipeBackLayout

styles.xml 中的主题中添加:

1
<item name="android:windowIsTranslucent">true</item>

注意

需要在项目中添加最新的 supportV4 包

demo

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
public class DemoActivity extends SwipeBackActivity implements View.OnClickListener {
private int[] mBgColors;

private static int mBgIndex = 0;

private String mKeyTrackingMode;

private RadioGroup mTrackingModeGroup;

private SwipeBackLayout mSwipeBackLayout;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_demo);
changeActionBarColor();
findViews();
mKeyTrackingMode = getString(R.string.key_tracking_mode);
mSwipeBackLayout = getSwipeBackLayout();

mTrackingModeGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
int edgeFlag;
switch (checkedId) {
case R.id.mode_left:
edgeFlag = SwipeBackLayout.EDGE_LEFT;
break;
case R.id.mode_right:
edgeFlag = SwipeBackLayout.EDGE_RIGHT;
break;
case R.id.mode_bottom:
edgeFlag = SwipeBackLayout.EDGE_BOTTOM;
break;
default:
edgeFlag = SwipeBackLayout.EDGE_ALL;
}
mSwipeBackLayout.setEdgeTrackingEnabled(edgeFlag);
saveTrackingMode(edgeFlag);
}
});
}
...

源码

SwipeBackActivity

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
public class SwipeBackActivity extends AppCompatActivity implements SwipeBackActivityBase {
private SwipeBackActivityHelper mHelper;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mHelper = new SwipeBackActivityHelper(this);
mHelper.onActivityCreate();
}

@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
mHelper.onPostCreate();
}

@Override
public View findViewById(int id) {
View v = super.findViewById(id);
if (v == null && mHelper != null)
return mHelper.findViewById(id);
return v;
}

@Override
public SwipeBackLayout getSwipeBackLayout() {//SwipeBackActivityBase接口中的方法
return mHelper.getSwipeBackLayout();
}

@Override
public void setSwipeBackEnable(boolean enable) {//SwipeBackActivityBase接口中的方法
getSwipeBackLayout().setEnableGesture(enable);
}

@Override
public void scrollToFinishActivity() {//SwipeBackActivityBase接口中的方法
Utils.convertActivityToTranslucent(this);
getSwipeBackLayout().scrollToFinishActivity();
}
}

SwipeBackActivity 中实现了 SwipeBackActivityBase 接口,在 Activity 的生命周期函数 onCreate() 中创建了 SwipeBackActivityHelper 对象,该类的作用是设置 Activity 的透明和在 DecorView 中替换 SwipeBackLayoutonPostCreate() 是在 Activity 完全运行起来之后才会被调用。其中 findViewById() 方法进行了判断,首先在 Activity 的 contentView 中获取,获取不到再到 SwipeBackLayout 中获取。

SwipeBackActivityHelper

SwipeBackActivityonCreate() 中的调用方法:

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
public class SwipeBackActivityHelper {

private SwipeBackLayout mSwipeBackLayout;

public SwipeBackActivityHelper(Activity activity) {
mActivity = activity;
}

@SuppressWarnings("deprecation")
public void onActivityCreate() {
//设置Window的background为透明
mActivity.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
//设置decorView没有background
mActivity.getWindow().getDecorView().setBackgroundDrawable(null);
//inflate一个SwipeBackLayout出来
mSwipeBackLayout = (SwipeBackLayout) LayoutInflater.from(mActivity).inflate(
me.imid.swipebacklayout.lib.R.layout.swipeback_layout, null);
//设置手势滑动监听器
mSwipeBackLayout.addSwipeListener(new SwipeBackLayout.SwipeListener() {
@Override
public void onScrollStateChange(int state, float scrollPercent) {
}

@Override
public void onEdgeTouch(int edgeFlag) {
//当有边界触摸的时候设置成透明的
Utils.convertActivityToTranslucent(mActivity);
}

@Override
public void onScrollOverThreshold() {

}
});
}
}

onActivityCreate 中主要就是将 window 、 decorView 的背景设置为透明的。

SwipeBackActivityonPostCreate() 中的调用方法:

1
2
3
4
5
public class SwipeBackActivityHelper {
public void onPostCreate() {
mSwipeBackLayout.attachToActivity(mActivity);
}
}

attachToActivity 中的操作就是将 decorView 中的 childView 换成 SwipeBackLayout ,然后将 childView 添加到 SwipeBackLayout 中。

其他的方法:

1
2
3
4
5
6
7
8
9
10
11
12
public class SwipeBackActivityHelper {
public View findViewById(int id) {
if (mSwipeBackLayout != null) {
return mSwipeBackLayout.findViewById(id);
}
return null;
}

public SwipeBackLayout getSwipeBackLayout() {
return mSwipeBackLayout;
}
}

SwipeBackLayout

SwipeBackLayout 是一个 View ,可以从构造函数开始看:

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
public class SwipeBackLayout extends FrameLayout {

/**
* Minimum velocity that will be detected as a fling
*/

private static final int MIN_FLING_VELOCITY = 400; // dips per second

private static final int[] EDGE_FLAGS = {
EDGE_LEFT, EDGE_RIGHT, EDGE_BOTTOM, EDGE_ALL
};
private int mEdgeFlag;

private ViewDragHelper mDragHelper;

public SwipeBackLayout(Context context) {
this(context, null);
}

public SwipeBackLayout(Context context, AttributeSet attrs) {
this(context, attrs, R.attr.SwipeBackLayoutStyle);
}

public SwipeBackLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs);
mDragHelper = ViewDragHelper.create(this, new ViewDragCallback());

TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SwipeBackLayout, defStyle,
R.style.SwipeBackLayout);

//与边缘可拖动的距离
int edgeSize = a.getDimensionPixelSize(R.styleable.SwipeBackLayout_edge_size, -1);
if (edgeSize > 0) {
//设置给ViewDragHelper
setEdgeSize(edgeSize);
}
//边缘模式,分为EDGE_LEFT, EDGE_RIGHT, EDGE_BOTTOM, EDGE_ALL
int mode = EDGE_FLAGS[a.getInt(R.styleable.SwipeBackLayout_edge_flag, 0)];
//设置给ViewDragHelper
setEdgeTrackingEnabled(mode);
//边缘滑动的时候的阴影
int shadowLeft = a.getResourceId(R.styleable.SwipeBackLayout_shadow_left,
R.drawable.shadow_left);
int shadowRight = a.getResourceId(R.styleable.SwipeBackLayout_shadow_right,
R.drawable.shadow_right);
int shadowBottom = a.getResourceId(R.styleable.SwipeBackLayout_shadow_bottom,
R.drawable.shadow_bottom);
setShadow(shadowLeft, EDGE_LEFT);
setShadow(shadowRight, EDGE_RIGHT);
setShadow(shadowBottom, EDGE_BOTTOM);
a.recycle();
//得到密度
final float density = getResources().getDisplayMetrics().density;
//手势滑动最小速度
final float minVel = MIN_FLING_VELOCITY * density;
//设置给ViewDragHelper
mDragHelper.setMinVelocity(minVel);
mDragHelper.setMaxVelocity(minVel * 2f);
}

/**
* Set the size of an edge. This is the range in pixels along the edges of
* this view that will actively detect edge touches or drags if edge
* tracking is enabled.
*
* @param size The size of an edge in pixels
*/

public void setEdgeSize(int size) {
mDragHelper.setEdgeSize(size);
}

/**
* Enable edge tracking for the selected edges of the parent view. The
* callback's
* {@link me.imid.swipebacklayout.lib.ViewDragHelper.Callback#onEdgeTouched(int, int)}
* and
* {@link me.imid.swipebacklayout.lib.ViewDragHelper.Callback#onEdgeDragStarted(int, int)}
* methods will only be invoked for edges for which edge tracking has been
* enabled.
*
* @param edgeFlags Combination of edge flags describing the edges to watch
* @see #EDGE_LEFT
* @see #EDGE_RIGHT
* @see #EDGE_BOTTOM
*/

public void setEdgeTrackingEnabled(int edgeFlags) {
mEdgeFlag = edgeFlags;
mDragHelper.setEdgeTrackingEnabled(mEdgeFlag);
}

public void setShadow(int resId, int edgeFlag) {
setShadow(getResources().getDrawable(resId), edgeFlag);
}

/**
* Set a drawable used for edge shadow.
*
* @param shadow Drawable to use
* @param edgeFlag Combination of edge flags describing the edge to set
* @see #EDGE_LEFT
* @see #EDGE_RIGHT
* @see #EDGE_BOTTOM
*/

public void setShadow(Drawable shadow, int edgeFlag) {
if ((edgeFlag & EDGE_LEFT) != 0) {
mShadowLeft = shadow;
} else if ((edgeFlag & EDGE_RIGHT) != 0) {
mShadowRight = shadow;
} else if ((edgeFlag & EDGE_BOTTOM) != 0) {
mShadowBottom = shadow;
}
invalidate();
}

//处理ViewDragHelper
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
if (!mEnable) {
return false;
}
try {
return mDragHelper.shouldInterceptTouchEvent(event);
} catch (ArrayIndexOutOfBoundsException e) {
// FIXME: handle exception
// issues #9
return false;
}
}

//处理ViewDragHelper
@Override
public boolean onTouchEvent(MotionEvent event) {
if (!mEnable) {
return false;
}
mDragHelper.processTouchEvent(event);
return true;
}
}

SwipeBackLayout 继承自 FrameLayout ,其中手势的操作是通过 ViewDragHelper 来实现的。在构造函数中一些必要的参数设置给 ViewDragHelper

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
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
public class SwipeBackLayout extends FrameLayout {
/**
* Edge flag indicating that the left edge should be affected.
*/

public static final int EDGE_LEFT = ViewDragHelper.EDGE_LEFT;

/**
* Edge flag indicating that the right edge should be affected.
*/

public static final int EDGE_RIGHT = ViewDragHelper.EDGE_RIGHT;

/**
* Edge flag indicating that the bottom edge should be affected.
*/

public static final int EDGE_BOTTOM = ViewDragHelper.EDGE_BOTTOM;

/**
* Edge flag set indicating all edges should be affected.
*/

public static final int EDGE_ALL = EDGE_LEFT | EDGE_RIGHT | EDGE_BOTTOM;
/**
* Default threshold of scroll
* 超过0.3f的屏幕比例的距离之后可以滑动出去了,临界值是0.3f
*/

private static final float DEFAULT_SCROLL_THRESHOLD = 0.3f;

private static final int OVERSCROLL_DISTANCE = 10;

private float mScrimOpacity;

/**
* Edge being dragged
*/

private int mTrackingEdge;

//滑动了距离和整个屏幕的的百分比
private float mScrollPercent;

private int mContentLeft;

private int mContentTop;

/**
* Threshold of scroll, we will close the activity, when scrollPercent over
* this value;
*/

private float mScrollThreshold = DEFAULT_SCROLL_THRESHOLD;

private class ViewDragCallback extends ViewDragHelper.Callback {
private boolean mIsScrollOverValid;

//如果可拖动则返回true 否则为false
@Override
public boolean tryCaptureView(View view, int i) {//i是pointerId
//是否touch到了边缘
boolean ret = mDragHelper.isEdgeTouched(mEdgeFlag, i);
//哪个边缘被touch了
if (ret) {
if (mDragHelper.isEdgeTouched(EDGE_LEFT, i)) {
mTrackingEdge = EDGE_LEFT;
} else if (mDragHelper.isEdgeTouched(EDGE_RIGHT, i)) {
mTrackingEdge = EDGE_RIGHT;
} else if (mDragHelper.isEdgeTouched(EDGE_BOTTOM, i)) {
mTrackingEdge = EDGE_BOTTOM;
}
//回调出去
if (mListeners != null && !mListeners.isEmpty()) {
for (SwipeListener listener : mListeners) {
listener.onEdgeTouch(mTrackingEdge);
}
}
mIsScrollOverValid = true;
}
boolean directionCheck = false;
//是否达到了滑动的门槛
if (mEdgeFlag == EDGE_LEFT || mEdgeFlag == EDGE_RIGHT) {
directionCheck = !mDragHelper.checkTouchSlop(ViewDragHelper.DIRECTION_VERTICAL, i);
} else if (mEdgeFlag == EDGE_BOTTOM) {
directionCheck = !mDragHelper
.checkTouchSlop(ViewDragHelper.DIRECTION_HORIZONTAL, i);
} else if (mEdgeFlag == EDGE_ALL) {
directionCheck = true;
}
return ret & directionCheck;
}

//返回指定View在横向上能滑动的最大距离
@Override
public int getViewHorizontalDragRange(View child) {
return mEdgeFlag & (EDGE_LEFT | EDGE_RIGHT);
}

//返回指定View在纵向上能滑动的最大距离
@Override
public int getViewVerticalDragRange(View child) {
return mEdgeFlag & EDGE_BOTTOM;
}

//当子视图位置变化时,会回调这个函数
@Override
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
super.onViewPositionChanged(changedView, left, top, dx, dy);
//计算当前滑动比例
if ((mTrackingEdge & EDGE_LEFT) != 0) {
mScrollPercent = Math.abs((float) left
/ (mContentView.getWidth() + mShadowLeft.getIntrinsicWidth()));
} else if ((mTrackingEdge & EDGE_RIGHT) != 0) {
mScrollPercent = Math.abs((float) left
/ (mContentView.getWidth() + mShadowRight.getIntrinsicWidth()));
} else if ((mTrackingEdge & EDGE_BOTTOM) != 0) {
mScrollPercent = Math.abs((float) top
/ (mContentView.getHeight() + mShadowBottom.getIntrinsicHeight()));
}
mContentLeft = left;
mContentTop = top;
invalidate();
//当滑动比例小于可滑动出去的时候,且mIsScrollOverValid已经为false的时候
if (mScrollPercent < mScrollThreshold && !mIsScrollOverValid) {
mIsScrollOverValid = true;
}
if (mListeners != null && !mListeners.isEmpty()
&& mDragHelper.getViewDragState() == STATE_DRAGGING
&& mScrollPercent >= mScrollThreshold && mIsScrollOverValid) {
mIsScrollOverValid = false;
//回调出去,已经达到可以滑出结束Activity的标准了
for (SwipeListener listener : mListeners) {
listener.onScrollOverThreshold();
}
}
//当比例大于等于1的时候,就可以关闭掉Activity了
if (mScrollPercent >= 1) {
if (!mActivity.isFinishing()) {
mActivity.finish();
mActivity.overridePendingTransition(0, 0);
}
}
}

//当手指从子视图松开时,会调用这个函数,同时返回在x轴和y轴上当前的速度
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
final int childWidth = releasedChild.getWidth();
final int childHeight = releasedChild.getHeight();

int left = 0, top = 0;
if ((mTrackingEdge & EDGE_LEFT) != 0) {//左边边缘
//速度满足>=0且已经滑过了临界点0.3f,滑到最右边,不然滑到0的位置
left = xvel > 0 || xvel == 0 && mScrollPercent > mScrollThreshold ? childWidth + mShadowLeft.getIntrinsicWidth() + OVERSCROLL_DISTANCE : 0;
} else if ((mTrackingEdge & EDGE_RIGHT) != 0) {//右边边缘
//速度满足>=0且已经滑过了临界点0.3f,滑到最左边,不然滑到0的位置
left = xvel < 0 || xvel == 0 && mScrollPercent > mScrollThreshold ? -(childWidth + mShadowLeft.getIntrinsicWidth() + OVERSCROLL_DISTANCE) : 0;
} else if ((mTrackingEdge & EDGE_BOTTOM) != 0) {//上边边缘
//速度满足>=0且已经滑过了临界点0.3f,滑到最下边,不然滑到0的位置
top = yvel < 0 || yvel == 0 && mScrollPercent > mScrollThreshold ? -(childHeight + mShadowBottom.getIntrinsicHeight() + OVERSCROLL_DISTANCE) : 0;
}
//移动View
mDragHelper.settleCapturedViewAt(left, top);
//刷新View
invalidate();
}

//返回一个值,告诉Helper,这个view能滑动的最大(或者负向最大)的横向坐标
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
int ret = 0;
if ((mTrackingEdge & EDGE_LEFT) != 0) {
ret = Math.min(child.getWidth(), Math.max(left, 0));
} else if ((mTrackingEdge & EDGE_RIGHT) != 0) {
ret = Math.min(0, Math.max(left, -child.getWidth()));
}
return ret;
}

//返回一个值,告诉Helper,这个view能滑动的最大(或者负向最大)的纵向坐标
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
int ret = 0;
if ((mTrackingEdge & EDGE_BOTTOM) != 0) {
ret = Math.min(0, Math.max(top, -child.getHeight()));
}
return ret;
}

//当边缘开始拖动的时候,会调用这个回调
@Override
public void onViewDragStateChanged(int state) {
super.onViewDragStateChanged(state);
if (mListeners != null && !mListeners.isEmpty()) {
for (SwipeListener listener : mListeners) {
listener.onScrollStateChange(state, mScrollPercent);
}
}
}
}

@Override
public void computeScroll() {
//调用mDragHelper.settleCapturedViewAt(left, top)之后会进到这里
mScrimOpacity = 1 - mScrollPercent;
if (mDragHelper.continueSettling(true)) {
ViewCompat.postInvalidateOnAnimation(this);
}
}

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
mInLayout = true;
if (mContentView != null) {
mContentView.layout(mContentLeft, mContentTop,
mContentLeft + mContentView.getMeasuredWidth(),
mContentTop + mContentView.getMeasuredHeight());
}
mInLayout = false;
}

@Override
public void requestLayout() {
if (!mInLayout) {
super.requestLayout();
}
}
}

ViewDragHelper.Callback 的手势判断中,处理的主要逻辑主要在 tryCaptureViewonViewPositionChangedonViewReleased 三个方法中,分别是在准备滑动、滑动时、和放手的时候的逻辑。

tryCaptureView 中主要进行了边缘的判断,以及是否满足滑动条件;在 onViewPositionChanged 中计算了当前滑动距离与整个 ContentView 的距离的比例,是否超越临界值等;在 onViewReleased 中处理了手抬起之后的操作,比如将 View 滑归位或者滑出去等。

现在基本上了解了滑动的机制了,那么回过头来看看 attachToActivity

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
public class SwipeBackLayout extends FrameLayout {

private View mContentView;

public void attachToActivity(Activity activity) {
mActivity = activity;
TypedArray a = activity.getTheme().obtainStyledAttributes(new int[]{
android.R.attr.windowBackground
});
int background = a.getResourceId(0, 0);
a.recycle();

ViewGroup decor = (ViewGroup) activity.getWindow().getDecorView();
// 拿到decorView的第一个子view
ViewGroup decorChild = (ViewGroup) decor.getChildAt(0);
decorChild.setBackgroundResource(background);
//把这个decorChild从decorView删除掉
decor.removeView(decorChild);
//将decorView添加到SwipeBackLayout中
addView(decorChild);
//将decorChild赋值给成员变量mContentView
setContentView(decorChild);
// 在DecorView下增加SwipeBackLayout
decor.addView(this);
}

/**
* Set up contentView which will be moved by user gesture
*
* @param view
*/

private void setContentView(View view) {
mContentView = view;
}
}

通过 attachToActivity 将 decorView 中的 contentView 换成了 SwipeBackLayout ,而 contentView 则被添加到了 SwipeBackLayout 中。与正常的相比,之间多了一个 SwipeBackLayout

在滑动的时候哪些阴影是怎么出现的呢:

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
public class SwipeBackLayout extends FrameLayout {

private static final int DEFAULT_SCRIM_COLOR = 0x99000000;

private float mScrimOpacity;

private int mScrimColor = DEFAULT_SCRIM_COLOR;

private float mScrollPercent;

private Drawable mShadowLeft;
private Drawable mShadowRight;
private Drawable mShadowBottom;

private Rect mTmpRect = new Rect();

@Override
public void computeScroll() {
mScrimOpacity = 1 - mScrollPercent;
if (mDragHelper.continueSettling(true)) {
ViewCompat.postInvalidateOnAnimation(this);
}
}

@Override
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
final boolean drawContent = child == mContentView;

boolean ret = super.drawChild(canvas, child, drawingTime);
if (mScrimOpacity > 0 && drawContent
&& mDragHelper.getViewDragState() != ViewDragHelper.STATE_IDLE) {
drawShadow(canvas, child);
drawScrim(canvas, child);
}
return ret;
}

private void drawScrim(Canvas canvas, View child) {
//得到alpha值
final int baseAlpha = (mScrimColor & 0xff000000) >>> 24;
//得到新的alpha值
final int alpha = (int) (baseAlpha * mScrimOpacity);
//得到新的color
final int color = alpha << 24 | (mScrimColor & 0xffffff);
//绘制
if ((mTrackingEdge & EDGE_LEFT) != 0) {
canvas.clipRect(0, 0, child.getLeft(), getHeight());
} else if ((mTrackingEdge & EDGE_RIGHT) != 0) {
canvas.clipRect(child.getRight(), 0, getRight(), getHeight());
} else if ((mTrackingEdge & EDGE_BOTTOM) != 0) {
canvas.clipRect(child.getLeft(), child.getBottom(), getRight(), getHeight());
}
canvas.drawColor(color);
}


private void drawShadow(Canvas canvas, View child) {
final Rect childRect = mTmpRect;
//得到当前View的位置
child.getHitRect(childRect);

if ((mEdgeFlag & EDGE_LEFT) != 0) {
//给drawable设置位置
mShadowLeft.setBounds(childRect.left - mShadowLeft.getIntrinsicWidth(), childRect.top, childRect.left, childRect.bottom);
//设置透明度
mShadowLeft.setAlpha((int) (mScrimOpacity * FULL_ALPHA));
//画到canvas上
mShadowLeft.draw(canvas);
}
//给drawable设置位置、设置透明度、画到canvas上
if ((mEdgeFlag & EDGE_RIGHT) != 0) {
mShadowRight.setBounds(childRect.right, childRect.top, childRect.right + mShadowRight.getIntrinsicWidth(), childRect.bottom);
mShadowRight.setAlpha((int) (mScrimOpacity * FULL_ALPHA));
mShadowRight.draw(canvas);
}
//给drawable设置位置、设置透明度、画到canvas上
if ((mEdgeFlag & EDGE_BOTTOM) != 0) {
mShadowBottom.setBounds(childRect.left, childRect.bottom, childRect.right, childRect.bottom + mShadowBottom.getIntrinsicHeight());
mShadowBottom.setAlpha((int) (mScrimOpacity * FULL_ALPHA));
mShadowBottom.draw(canvas);
}
}
}

就这样,阴影就绘制出来了。

再看看 scrollToFinishActivity

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
public class SwipeBackLayout extends FrameLayout {
/**
* Scroll out contentView and finish the activity
*/

public void scrollToFinishActivity() {
//得到contentView的宽高
final int childWidth = mContentView.getWidth();
final int childHeight = mContentView.getHeight();
//要移动到的位置
int left = 0, top = 0;
if ((mEdgeFlag & EDGE_LEFT) != 0) {
left = childWidth + mShadowLeft.getIntrinsicWidth() + OVERSCROLL_DISTANCE;
mTrackingEdge = EDGE_LEFT;
} else if ((mEdgeFlag & EDGE_RIGHT) != 0) {
left = -childWidth - mShadowRight.getIntrinsicWidth() - OVERSCROLL_DISTANCE;
mTrackingEdge = EDGE_RIGHT;
} else if ((mEdgeFlag & EDGE_BOTTOM) != 0) {
top = -childHeight - mShadowBottom.getIntrinsicHeight() - OVERSCROLL_DISTANCE;
mTrackingEdge = EDGE_BOTTOM;
}

mDragHelper.smoothSlideViewTo(mContentView, left, top);
invalidate();
}

@Override
public void computeScroll() {
//调用mDragHelper.smoothSlideViewTo(mContentView, left, top);之后进到这里
mScrimOpacity = 1 - mScrollPercent;
if (mDragHelper.continueSettling(true)) {
ViewCompat.postInvalidateOnAnimation(this);
}
}
}

Utils

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
public class Utils {
private Utils() {
}

/**
* Convert a translucent themed Activity
* {@link android.R.attr#windowIsTranslucent} back from opaque to
* translucent following a call to
* {@link #convertActivityFromTranslucent(android.app.Activity)} .
* <p>
* Calling this allows the Activity behind this one to be seen again. Once
* all such Activities have been redrawn
* <p>
* This call has no effect on non-translucent activities or on activities
* with the {@link android.R.attr#windowIsFloating} attribute.
*/

public static void convertActivityToTranslucent(Activity activity) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
convertActivityToTranslucentAfterL(activity);
} else {
convertActivityToTranslucentBeforeL(activity);
}
}

/**
* Calling the convertToTranslucent method on platforms before Android 5.0
*/

public static void convertActivityToTranslucentBeforeL(Activity activity) {
try {
Class<?>[] classes = Activity.class.getDeclaredClasses();
Class<?> translucentConversionListenerClazz = null;
for (Class clazz : classes) {
if (clazz.getSimpleName().contains("TranslucentConversionListener")) {
translucentConversionListenerClazz = clazz;
}
}
Method method = Activity.class.getDeclaredMethod("convertToTranslucent",
translucentConversionListenerClazz);
method.setAccessible(true);
method.invoke(activity, new Object[] {
null
});
} catch (Throwable t) {
}
}

/**
* Calling the convertToTranslucent method on platforms after Android 5.0
*/

private static void convertActivityToTranslucentAfterL(Activity activity) {
try {
Method getActivityOptions = Activity.class.getDeclaredMethod("getActivityOptions");
getActivityOptions.setAccessible(true);
Object options = getActivityOptions.invoke(activity);

Class<?>[] classes = Activity.class.getDeclaredClasses();
Class<?> translucentConversionListenerClazz = null;
for (Class clazz : classes) {
if (clazz.getSimpleName().contains("TranslucentConversionListener")) {
translucentConversionListenerClazz = clazz;
}
}
Method convertToTranslucent = Activity.class.getDeclaredMethod("convertToTranslucent",
translucentConversionListenerClazz, ActivityOptions.class);
convertToTranslucent.setAccessible(true);
convertToTranslucent.invoke(activity, null, options);
} catch (Throwable t) {
}
}
}

通过反射改变 Activity 的属性值。

总结

  • SwipeBackLayout 的手势操作用到的是 Support V4 的 ViewDragHelper
  • 能达到 Activity 背景透明的效果主要是通过 Utils 和 style.xml 中设置的 android:windowIsTranslucent
  • 正是因为 windowIsTranslucent 参数,可能在某些机器上直接显示桌面了,我以前遇到过的就有 sony 的 ROM 。