转载请标明出处:http://blog.csdn.net/weixin_39059543/article/details/73826250
对与DrawerLayout大家应该用过,是Google官方推出的一种抽屉式导航控件。打开左右两边菜单的方式是从手机屏
幕的边缘处滑动来触发,不过总有些**的需求要让它可以全屏滑动触发菜单,网上也有一些解决办法,无非就是用
setDrawerLeftEdgeSize()来
设置边缘大小,将一边的屏幕边缘扩大至整个屏幕,不过这会产生一些bug比如点击屏幕或上下滑动屏幕都会弹出菜单、多种手势不能识别,并且只能实现一边的全屏滑动,因为不能两边的边缘都扩大至全屏。所以要想从根本上解决这个这个问题,就要从源码上分析。
我们来看一下DrawerLayout的源码:
public class DrawerLayout extends ViewGroup implements DrawerLayoutImpl {
private static final String TAG = "DrawerLayout";
@IntDef({STATE_IDLE, STATE_DRAGGING, STATE_SETTLING})
@Retention(RetentionPolicy.SOURCE)
private @interface State {}
/**
* Indicates that any drawers are in an idle, settled state. No animation is in progress.
*/
public static final int STATE_IDLE = ViewDragHelper.STATE_IDLE;
/**
* Indicates that a drawer is currently being dragged by the user.
*/
public static final int STATE_DRAGGING = ViewDragHelper.STATE_DRAGGING;
它是继承ViewGroup的一个自定义类,所以我们可以仿照它的写法自定义一个MyDrawerLayout 只要在
onTouchEvent(MotionEvent ev)判断用户手势,左右滑动打开相对应用的菜单,原理就是这样。
我们继续看源码打开抽屉的方法:
@Override
public void onEdgeDragStarted(int edgeFlags, int pointerId) {
final View toCapture;
if ((edgeFlags & ViewDragHelper.EDGE_LEFT) == ViewDragHelper.EDGE_LEFT) {
toCapture = findDrawerWithGravity(Gravity.LEFT);
} else {
toCapture = findDrawerWithGravity(Gravity.RIGHT);
}
if (toCapture != null && getDrawerLockMode(toCapture) == LOCK_MODE_UNLOCKED) {
mDragger.captureChildView(toCapture, pointerId);
}
}
这个方法是当用户触发手机屏幕边缘的时候回调,我们可以将这个方法屏蔽掉,然后在
onTouchEvent(MotionEventev)里调用打开方法。
private static final int MIN_DRAWER_MARGIN = 64; // dp
注意一下源码中这个这是设置菜单的最大宽度离另一边的最小距离,简单来说要想你的菜单是全屏显示的话就把这
个值设置为0。
核心代码:
@Override
public boolean onTouchEvent(MotionEvent ev) {
mLeftDragger.processTouchEvent(ev);
mRightDragger.processTouchEvent(ev);
final View toCapture;
final int action = ev.getAction();
boolean wantTouchEvents = true;
if (ev.getPointerCount() > 1) { //多点触屏拦截,防止多点触屏滑出菜单 如果主内容里面有子内控件,可写到onInterceptTouchEvent交给子类拦截
return false;
}
switch (action & MotionEventCompat.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: {
final float x = ev.getX();
final float y = ev.getY();
mInitialMotionX = x;
mInitialMotionY = y;
closeDrawers(true);
closeKeyboard();
mDisallowInterceptRequested = false;
mChildrenCanceledTouch = false;
break;
}
case MotionEvent.ACTION_UP: {
final float x = ev.getX();
final float y = ev.getY();
boolean peekingOnly = true;
final View touchedView = mLeftDragger.findTopChildUnder((int) x, (int) y);
if (touchedView != null && isContentView(touchedView)) {
final float dx = x - mInitialMotionX;
final float dy = y - mInitialMotionY;
final int slop = mLeftDragger.getTouchSlop();
if (dx * dx + dy * dy < slop * slop) {
// Taps close a dimmed open drawer but only if it isn't
// locked open.
final View openDrawer = findOpenDrawer();
if (openDrawer != null) {
peekingOnly = getDrawerLockMode(openDrawer) == LOCK_MODE_LOCKED_OPEN;
}
}
}
closeDrawers(peekingOnly);
mDisallowInterceptRequested = false;
isCheck = false;
break;
}
case MotionEvent.ACTION_CANCEL: {
closeDrawers(true);
mDisallowInterceptRequested = false;
mChildrenCanceledTouch = false;
break;
}
//核心代码 滑动打开菜单
case MotionEvent.ACTION_MOVE: {
if (findDrawerWithGravity(Gravity.END).getVisibility() != View.VISIBLE &&
findDrawerWithGravity(Gravity.START).getVisibility() != View.VISIBLE) {
final float x = ev.getX();
final float dx = x - mInitialMotionX;
if (dx > 10) {
//isCheck为全局变量,用来控制每次滑动只能滑动一侧菜单
if (!isCheck) {
toCapture = findDrawerWithGravity(Gravity.START);
if (getDrawerLockMode(toCapture) == LOCK_MODE_UNLOCKED){
mLeftDragger.captureChildView(toCapture,ev.getPointerId(0));
}
isCheck = true;
}
} else if ((dx < -10)) {
if (!isCheck) {
toCapture = findDrawerWithGravity(Gravity.END);
if (getDrawerLockMode(toCapture) == LOCK_MODE_UNLOCKED) {
mRightDragger.captureChildView(toCapture, ev.getPointerId(0));
}
isCheck = true;
}
}
}
break;
}
}
return wantTouchEvents;
}
如果主内容里面有子控件,触摸拦截与传递在子父类的onIntercptTouchEvent和onTouchEvent的这两个方法中处理事件分发。
好了废话不多说,提供源码下载,自己分析:
https://download.csdn.net/download/weixin_39059543/11615398