完美开启DrawerLayout全屏手势侧滑

DrawerLayout是安卓官方的一个非常好用的组件,使用ViewDragHelper实现。主要方便大家写由侧滑菜单的界面。但是这个东西可定制性其实不强,侧滑手势必须在屏幕边缘才可以,在现在手机屏幕越来越大的情况下,其实不利于单手操作。那么怎么才可以让DrawerLayout可以全屏手势侧滑出菜单呢?
注:下面的描述默认侧滑菜单都是在左侧的,右侧同理,但很少用到右侧的。

一、按照网上可以查找到的内容,主要由两种做法:

1、在Activity里重写事件分发,判断是右滑的话就drawer.openDrawer(GravityCompat.START);
但这种体验并不好,也得在手指滑动离开屏幕后才行,没有跟随手势的动画。也容易和内部可以垂直滚动的控件有一点点滑动冲突。
2、就是利用反射,重新设置edgeSize,但这种也有个问题。
在侧滑范围内手指长按屏幕(没有离开屏幕),侧滑菜单就会展开,如果这个范围设置的屏幕宽度差不多(比较大),侧滑菜单就会过度右移,造成左侧边缘有空白。
通过分析源码,我发现DrawerLayout的ViewDragCallback类重写了onEdgeTouched方法,而他的实现就是调用了下面的peekDrawer方法:

private final Runnable mPeekRunnable = new Runnable() {
            @Override public void run() {
                peekDrawer();
            }
        };
@Override
        public void onEdgeTouched(int edgeFlags, int pointerId) {
            postDelayed(mPeekRunnable, PEEK_DELAY);
        }

void peekDrawer() {
            final View toCapture;
            final int childLeft;
            final int peekDistance = mDragger.getEdgeSize();
            final boolean leftEdge = mAbsGravity == Gravity.LEFT;
            if (leftEdge) {
                toCapture = findDrawerWithGravity(Gravity.LEFT);
                childLeft = (toCapture != null ? -toCapture.getWidth() : 0) + peekDistance;
            } else {
                toCapture = findDrawerWithGravity(Gravity.RIGHT);
                childLeft = getWidth() - peekDistance;
            }
            // Only peek if it would mean making the drawer more visible and the drawer isn't locked
            if (toCapture != null && ((leftEdge && toCapture.getLeft() < childLeft)
                    || (!leftEdge && toCapture.getLeft() > childLeft))
                    && getDrawerLockMode(toCapture) == LOCK_MODE_UNLOCKED) {
                final LayoutParams lp = (LayoutParams) toCapture.getLayoutParams();
                mDragger.smoothSlideViewTo(toCapture, childLeft, toCapture.getTop());
                lp.isPeeking = true;
                invalidate();

                closeOtherDrawer();

                cancelChildViewTouch();
            }
        }

注意mDragger.smoothSlideViewTo(toCapture, childLeft, toCapture.getTop())就是长按屏幕时,侧滑菜单会自动滑出来的原因,即使你不做任何修改,也可以在使用了DrawerLayout和NavigateView的界面测试这个现像:
直接长按屏幕左侧边缘,你就会发现侧滑菜单会自动滑出来一段距离,当然,只是一小部分,而这也是DrawerLayout默认的手势识别范围。所以如果通过反射修改了edgeSize,那么长按屏幕,自动滑出的部分也就越多,当edgeSize大于侧滑菜单的宽度,左侧就会有空白。

二、较为完美的解决方案

首先,肯定不是几句代码设置下就可以搞定的。但也可以不用重复造轮子。原理很简单,通过上面的分析其实很明了:

  • 去掉ViewDragCallback的onEdgeTouch的实现
  • 重写onInterceptTouchEvent添加自己的拦截逻辑
  • 修改ViewDragHelper的mEdgeSize
    ViewDragHelper.Callback 是个抽象类,DrawerLayout有个实现类ViewDragCallback,里面重写了onEdgeTouched方法,没有可以修改的API,所以直接复制源码比较直接(分分钟搞定)。

1、复制原有轮子

新建一个类XDrawerLayout,复制DrawerLayout的源码(注意一个细节,复制纯字符串,不然Android studio会把包引用一起复制的,改起来很麻烦),然后删除里面ViewDragCallback的onEdgeTouched,同时如果使用ActionBar或Toolbar配合DrawerLayout,还要复制ActionBarDrawerToggle类和ActionBarDrawerToggleHoneycomb类的相关代码。可以新建为
XDrawerToggle和XDrawerToggleHoneycomb。

2、重写XDrawerLayout的onInterceptTouchEvent

@Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        //其实就是吧原来的实现放到一个新的方法里,然后添加自己的逻辑。
        try {
            final float x = ev.getX();
            final float y = ev.getY();

            switch (ev.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    mLastMotionX = x;
                    mLastMotionY = y;
                    break;

                case MotionEvent.ACTION_MOVE:
                    //这里的判断拦截的逻辑是滑动的角度小于等于30°就是横向滑动,肯定拦截
                    //否者使用原来的逻辑,调用interceptTouchEvent
                    //这样写主要是有垂直滚动的RecyclewrView
                    //具体怎么处理,看自己具体需求
                    float xDiff = Math.abs(x - mLastMotionX);
                    float yDiff = Math.abs(y - mLastMotionY);
                    return xDiff > 0 && xDiff >= yDiff * Math.sqrt(3);
            }
            return interceptTouchEvent(ev);
        } catch (IllegalArgumentException ex) {
            ex.printStackTrace();
            return false;
        }
    }

    //这就是原来的onInterceptTouchEvent
    private boolean interceptTouchEvent(MotionEvent ev) {
        final int action = ev.getActionMasked();

        // "|" used deliberately here; both methods should be invoked.
        final boolean interceptForDrag = mLeftDragger.shouldInterceptTouchEvent(ev)
                | mRightDragger.shouldInterceptTouchEvent(ev);

        boolean interceptForTap = false;

        switch (action) {
            case MotionEvent.ACTION_DOWN: {
                final float x = ev.getX();
                final float y = ev.getY();
                mInitialMotionX = x;
                mInitialMotionY = y;
                if (mScrimOpacity > 0) {
                    final View child = mLeftDragger.findTopChildUnder((int) x, (int) y);
                    if (child != null && isContentView(child)) {
                        interceptForTap = true;
                    }
                }
                mDisallowInterceptRequested = false;
                mChildrenCanceledTouch = false;
                break;
            }

            case MotionEvent.ACTION_MOVE: {
                // If we cross the touch slop, don't perform the delayed peek for an edge touch.
                if (mLeftDragger.checkTouchSlop(ViewDragHelper.DIRECTION_ALL)) {
                    mLeftCallback.removeCallbacks();
                    mRightCallback.removeCallbacks();
                }
                break;
            }

            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP: {
                closeDrawers(true);
                mDisallowInterceptRequested = false;
                mChildrenCanceledTouch = false;
            }
        }

        return interceptForDrag || interceptForTap || hasPeekingDrawer() || mChildrenCanceledTouch;
    }

3、反射修改edgeSize

public static void setCustomLeftEdgeSize(@NonNull XDrawerLayout drawerLayout, float displayWidthPercentage) {
        try {
            // find ViewDragHelper and set it accessible
            Field leftDraggerField = drawerLayout.getClass().getDeclaredField("mLeftDragger");
            if (leftDraggerField == null) {
                return;
            }
            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);
            // set new edgesize
            int widthPixels = DisplayUtils.getWindowWidth(drawerLayout.getContext());
            edgeSizeField.setInt(leftDragger, Math.max(edgeSize, (int) (widthPixels * displayWidthPercentage)));
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

4、使用XDrawerLayout替换DrawerLayout

布局文件,Activity里都要替换,如果使用ActionBar或Toolbar配合DrawerLayout,还要替换ActionBarDrawerToggle--->XDrawerToggle
ActionBarDrawerToggleHoneycomb--->XDrawerToggleHoneycomb
合适的地方调用setCustomLeftEdgeSize(mXDrawerLayout,1f)就可以了。
设置为1f就可以全屏。
注意:由于用了反射,如果你使用了代码混淆,一定要keep XDrawerLayout,不然会失效的。

#XDrawerLayout反射
-keepclasseswithmembernames class [packagespace].XDrawerLayout{
    <fields>;
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,254评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,875评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,682评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,896评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,015评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,152评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,208评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,962评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,388评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,700评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,867评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,551评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,186评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,901评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,142评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,689评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,757评论 2 351

推荐阅读更多精彩内容