基础使用
这里要介绍的是google的DrawerLayout,行为可见google官方应用如gmail;而手Q的抽屉则是根据android-undergarment项目来定制的一个控件。
DrawerLayout添加在主内容区的上层,作为parent,下面的第一个child是主内容区域,第二个child则可以是其他任何东西,需要作为抽屉的view则需要声明android:layout_gravity。
DrawerLayout的setScrimColor可以设置抽屉拉出时右侧主内容剩余区域上面盖的颜色(默认0x99000000)。
高级应用
DrawerLayout默认只有在边缘的一个edge能够触发抽屉拉取的动作,而这个是通过ViewDragHelper这个类来实现的。
1
2
3
4private static final int EDGE_SIZE = 20; // dp
private static final int BASE_SETTLE_DURATION = 256; // ms
private static final int MAX_SETTLE_DURATION = 600; // ms
EDGE_SIZE是触发区域,默认20dp,而BASE_SETTLE_DURATION和MAX_SETTLE_DURATION则是控制抽屉从打开到关闭之间的这个间隔。由于是私有静态常量,可以通过
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23public static void (DrawerLayout drawerLayout, float dp) {
if (drawerLayout == null) {
return;
}
try {
// find ViewDragHelper and set it accessible
Field leftDraggerField = drawerLayout.getClass().getDeclaredField("mLeftDragger");
leftDraggerField.setAccessible(true);
ViewDragHelper leftDragger = (ViewDragHelper) leftDraggerField.get(drawerLayout);
// find edgesize and set is accessible
Field edgeSizeField = leftDragger.getClass().getDeclaredField("mEdgeSize");
edgeSizeField.setAccessible(true);
int edgeSize = edgeSizeField.getInt(leftDragger);
edgeSizeField.setInt(leftDragger, Math.max(edgeSize, ViewUtils.dpToPx(dp)));
} catch (NoSuchFieldException e) {
// ignore
} catch (IllegalArgumentException e) {
// ignore
} catch (IllegalAccessException e) {
// ignore
}
}
来反射设置左侧的触发区域,类似地可以修改右侧触发区域以及打开动画的间隔(当然你也可以直接去ViewDragHelper里面修改)。
不建议自己处理onTouch,会导致抽屉不能平滑跟手,比如stackoverflow上有给出以下这种方案的,简直坑爹:
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// ======================== 触摸事件处理 ===================================
private float startX, startY;
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = false;
int action = ev.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
startX = ev.getX();
startY = ev.getY();
break;
case MotionEvent.ACTION_MOVE:
case MotionEvent.ACTION_UP:
float endX = ev.getX();
float endY = ev.getY();
if (startX > HOT_FIELD || Math.abs(endY - startY) > SENSIBILITY_Y) {
break;
}
// From left to right
if (endX - startX >= SENSIBILITY_X) {
handled = openDrawer();
}
// From right to left
if (startX - endX >= SENSIBILITY_X) {
handled = closeDrawer();
}
break;
}
if (handled) {
mDrawerLayout.cancelChildViewTouch();
}
return handled;
}
## 坑爹的bug们
### 初始化LayoutParam时可能出错
```java
@Override
public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
LayoutParams layoutParams = null;
try {
// 出现异常时,用默认值
layoutParams = new LayoutParams(getContext(), attrs);
} catch (Throwable e) {
layoutParams = null;
}
if (layoutParams == null) {
layoutParams = new LayoutParams(-1, -1);
layoutParams.gravity = Gravity.NO_GRAVITY;
}
return layoutParams;
}
多点触摸的时候DrawerLayout抛出ArrayIndexOutOfBoundsException
这是由于多点触摸时候requestDisallowInterceptTouchEvent和DrawerLayout的innerViews问题。自己在外面继承DrawerLayout然后改一下行为。
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
36public class extends {
public (Context context) {
super(context);
}
public (Context context, AttributeSet attrs) {
super(context, attrs);
}
public (Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
private boolean mIsDisallowIntercept = false;
@Override
public void (boolean disallowIntercept) {
// keep the info about if the innerViews do requestDisallowInterceptTouchEvent
mIsDisallowIntercept = disallowIntercept;
super.requestDisallowInterceptTouchEvent(disallowIntercept);
}
@Override
public boolean (MotionEvent ev) {
// the incorrect array size will only happen in the multi-touch scenario.
if (ev.getPointerCount() > 1 && mIsDisallowIntercept) {
requestDisallowInterceptTouchEvent(false);
boolean handled = super.dispatchTouchEvent(ev);
requestDisallowInterceptTouchEvent(true);
return handled;
} else {
return super.dispatchTouchEvent(ev);
}
}
}
有时候手动拉出抽屉时候,抽屉会卡在那里,拉不出来
这也是极其坑爹的一个bug,原因是触摸EDGE的时候,事件触发到抽屉出现有一个延时:1
2
3
4
5
6
7
8/**
* Length of time to delay before peeking the drawer.
*/
private static final int PEEK_DELAY = 160; // ms
@Override
public void (int edgeFlags, int pointerId) {
postDelayed(mPeekRunnable, PEEK_DELAY);
}
抽屉有STATE_IDLE, STATE_DRAGGING和STATE_SETTLING三种状态,而这个偶然状况下,已经处于STATE_DRAGGING,而这个动作打开了抽屉20dp并试图再次置回STATE_DRAGGING,1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16private boolean (float delta, float odelta, int pointerId, int edge) {
final float absDelta = Math.abs(delta);
final float absODelta = Math.abs(odelta);
if ((mInitialEdgesTouched[pointerId] & edge) != edge || (mTrackingEdges & edge) == 0 ||
(mEdgeDragsLocked[pointerId] & edge) == edge ||
(mEdgeDragsInProgress[pointerId] & edge) == edge ||
(absDelta <= mTouchSlop && absODelta <= mTouchSlop)) {
return false;
}
if (absDelta
mEdgeDragsLocked[pointerId] |= edge;
return false;
}
return (mEdgeDragsInProgress[pointerId] & edge) == 0 && absDelta > mTouchSlop;
}
但这里由于mEdgeDragsInProgress[pointerId] & edge) == edge所以阻止了DrawerLayout回到STATE_DRAGGING。
解决方案是把DrawerLayout的ViewDragCallback中的mPeekRunnable进行修改,简单粗暴。
1
2
3
4
5private final Runnable mPeekRunnable = new Runnable() {
@Override public void () {
//peekDrawer();
}
};