Google官方抽屉DrawerLayout使用心得、踩坑经验以及定制化

本文详细介绍了Google DrawerLayout的基础使用和高级应用,包括如何设置抽屉颜色、调整触发区域以及动画间隔。同时,文章揭示了在实际使用中遇到的坑,如初始化LayoutParams时的错误、多点触摸时的ArrayIndexOutOfBoundsException以及抽屉卡住无法拖动的问题,并提供了相应的解决办法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

基础使用

这里要介绍的是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();
            }
        };


评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值