基础使用
这里要介绍的是google的DrawerLayout,行为可见google官方应用如gmail,看手Q的抽屉,应该是根据android-undergarment项目来定制的一个控件。
Google Desgin GuildLines里面有介绍:http://www.google.com/design/spec/layout/structure.html#structure-system-bars
官方教程:Creating a Navigation Drawer
DrawerLayout添加在主内容区的上层,作为parent,下面的第一个child是主内容区域,第二个child则可以是其他任何东西,需要作为抽屉的view则需要声明android:layout_gravity。
DrawerLayout的setScrimColor可以设置抽屉拉出时右侧主内容剩余区域上面盖的颜色(默认0x99000000)。
高级应用
DrawerLayout默认只有在边缘的一个edge能够触发抽屉拉取的动作,而这个是通过ViewDragHelper这个类来实现的。
private 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则是控制抽屉从打开到关闭之间的这个间隔。由于是私有静态常量,可以通过
public static void setDrawerLeftEdgeSize(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上有给出以下这种方案的,简直坑爹:
// ======================== 触摸事件处理 =================================== 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时可能出错
@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然后改一下行为。
public class SafeDrawerLayout extends DrawerLayout { public SafeDrawerLayout(Context context) { super(context); } public SafeDrawerLayout(Context context, AttributeSet attrs) { super(context, attrs); } public SafeDrawerLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } private boolean mIsDisallowIntercept = false; @Override public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { // keep the info about if the innerViews do requestDisallowInterceptTouchEvent mIsDisallowIntercept = disallowIntercept; super.requestDisallowInterceptTouchEvent(disallowIntercept); } @Override public boolean dispatchTouchEvent(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的时候,事件触发到抽屉出现有一个延时
/** * Length of time to delay before peeking the drawer. */ private static final int PEEK_DELAY = 160; // ms
@Override public void onEdgeTouched(int edgeFlags, int pointerId) { postDelayed(mPeekRunnable, PEEK_DELAY); }
抽屉有STATE_IDLE, STATE_DRAGGING和STATE_SETTLING三种状态,而这个偶然状况下,已经处于STATE_DRAGGING,而这个动作打开了抽屉20dp并试图再次置回STATE_DRAGGING,
private 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进行修改,简单粗暴。
private final Runnable mPeekRunnable = new Runnable() { @Override public void run() { //peekDrawer(); } };
版权声明:本文为博主原创文章,未经博主允许不得转载。