Android自定義View,實現全屏滑動的DrawerLayout
阿新 • • 發佈:2019-02-20
對與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的這兩個方法中處理事件分發。
好了廢話不多說,提供原始碼下載,自己分析: