基础使用
这里要介绍的是google的DrawerLayout,行为可见google官方应用如gmail;而手Q的抽屉则是根据android-undergarment项目来定制的一个控件。
官方教程:Creating a Navigation Drawer
DrawerLayout添加在主内容区的上层,作为parent,下面的第一个child是主内容区域,第二个child则可以是其他任何东西,需要作为抽屉的view则需要声明android:layout_gravity。
DrawerLayout的setScrimColor可以设置抽屉拉出时右侧主内容剩余区域上面盖的颜色(默认0x99000000)。
高级应用
DrawerLayout默认只有在边缘的一个edge能够触发抽屉拉取的动作,而这个是通过ViewDragHelper这个类来实现的。
1 | private static final int EDGE_SIZE = 20; // dp |
EDGE_SIZE是触发区域,默认20dp,而BASE_SETTLE_DURATION和MAX_SETTLE_DURATION则是控制抽屉从打开到关闭之间的这个间隔。由于是私有静态常量,可以通过
1 | public static void setDrawerLeftEdgeSize(DrawerLayout drawerLayout, float dp) { |
来反射设置左侧的触发区域,类似地可以修改右侧触发区域以及打开动画的间隔(当然你也可以直接去ViewDragHelper里面修改)。
不建议自己处理onTouch,会导致抽屉不能平滑跟手,比如stackoverflow上有给出以下这种方案的,简直坑爹:
1 | // ======================== 触摸事件处理 =================================== |
多点触摸的时候DrawerLayout抛出ArrayIndexOutOfBoundsException
这是由于多点触摸时候requestDisallowInterceptTouchEvent和DrawerLayout的innerViews问题。自己在外面继承DrawerLayout然后改一下行为。
1 | public class SafeDrawerLayout extends DrawerLayout { |
有时候手动拉出抽屉时候,抽屉会卡在那里,拉不出来
这也是极其坑爹的一个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
public void onEdgeTouched(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 checkNewEdgeDrag(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 < absODelta * 0.5f && mCallback.onEdgeLock(edge)) {
mEdgeDragsLocked[pointerId] |= edge;
return false;
}
return (mEdgeDragsInProgress[pointerId] & edge) == 0 && absDelta > mTouchSlop;
}
但这里由于mEdgeDragsInProgress[pointerId] & edge) == edge所以阻止了DrawerLayout回到STATE_DRAGGING。
解决方案是把DrawerLayout的ViewDragCallback中的mPeekRunnable进行修改,简单粗暴。
1 | private final Runnable mPeekRunnable = new Runnable() { |