美文网首页UIAndroid开发经验谈Android开发
完美开启DrawerLayout全屏手势侧滑

完美开启DrawerLayout全屏手势侧滑

作者: BrightVan | 来源:发表于2018-03-25 15:19 被阅读169次

    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>;
    }
    

    相关文章

      网友评论

      本文标题:完美开启DrawerLayout全屏手势侧滑

      本文链接:https://www.haomeiwen.com/subject/llwhcftx.html