DrawerLayout 是 Android 官方的側滑菜單控制項,而 ViewPager 相信大家都很熟悉了。今天這裡就講一下當在 DrawerLayout 中嵌套 ViewPager 時,要如何解決滑動衝突的問題,效果如下: 首先,讓我們先來解決 DrawerLayout 和 ViewPager ...
DrawerLayout 是 Android 官方的側滑菜單控制項,而 ViewPager 相信大家都很熟悉了。今天這裡就講一下當在 DrawerLayout 中嵌套 ViewPager 時,要如何解決滑動衝突的問題,效果如下:
首先,讓我們先來解決 DrawerLayout 和 ViewPager 的側滑事件衝突。當 DrawerLayout 中嵌套 ViewPager 時,側滑預設是執行 DrawerLayout 的側滑事件,因為 Android 的事件分發是從 外層 ViewGroup 向里逐級傳遞到 View 的。
所以會先執行 DrawerLayout 的 onTouchEvent 方法:
@Override
public boolean onTouchEvent(MotionEvent ev) {
mLeftDragger.processTouchEvent(ev);
mRightDragger.processTouchEvent(ev);
final int action = ev.getAction(); boolean wantTouchEvents = true;
switch (action & MotionEventCompat.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: {
final float x = ev.getX();
final float y = ev.getY();
mInitialMotionX = x;
mInitialMotionY = y;
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;
break;
}
case MotionEvent.ACTION_CANCEL: {
closeDrawers(true);
mDisallowInterceptRequested = false;
mChildrenCanceledTouch = false; break;
}
}
return wantTouchEvents;
}
可以看到在最後始終返回 wantTouchEvents,也就是返回 true,意味著點擊事件在 DrawerLayout 就被消費掉了,無法傳到 ViewPager。
所以,我們像下麵這樣,監聽當 Drawer 打開時,將 DrawerLayout 設置為 LOCK_MODE_LOCKED_OPEN,這樣在 Drawer 被打開時,就能夠觸發 ViewPager 的滑動事件了。
mDrawerLayout.addDrawerListener(new DrawerLayout.DrawerListener() {
@Override
public void onDrawerSlide(View drawerView, float slideOffset) {
}
@Override
public void onDrawerOpened(View drawerView) {
mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_OPEN);
}
@Override public void onDrawerClosed(View drawerView) {
mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED);
}
@Override public void onDrawerStateChanged(int newState) {
}
});
但是,當側邊欄的 ViewPager 滑動到最後一頁,再向左滑動時,我們會希望能夠自然的關閉 Drawer。這就需要我們監聽 ViewPager 的 PageChange 事件,當滑動到最後一頁時,將 DrawerLayout 的 LockMode 設置回 LOCK_MODE_UNLOCKED。
這裡,選擇在 DrawerFragment(也就是定義側邊欄的 Fragment) 中定義一個介面:
/**
* 監聽側邊欄的頁面選擇。
*/
public interface OnDrawerPageChangeListener {
void onPageSelected(boolean isLast);
}
然後讓 MainActivity 實現這個介面:
@Override
public void onPageSelected(boolean isLast) {
if (isLast) {
mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED);
} else if (mDrawerLayout.getDrawerLockMode(GravityCompat.START) == DrawerLayout.LOCK_MODE_UNLOCKED) {
mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_OPEN);
}
}
再在 DrawerFragment 中 ViewPager 的 PageChange 事件中使用:
final OnDrawerPageChangeListener drawerPageChangeListener = (OnDrawerPageChangeListener) getActivity();
mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
if (position == fragmentList.size() - 1) {
drawerPageChangeListener.onPageSelected(true);
} else {
drawerPageChangeListener.onPageSelected(false);
}
}
@Override
public void onPageScrollStateChanged(int state) {
}
});
這樣我們就解決了 DrawerLayout 和 ViewPager 的側滑事件衝突問題,剩下最後一個要處理的小問題就是在點擊空白區域時,也想要關閉側邊欄,這個就只需要:
// 點擊除開側邊欄的區域會收起側邊欄。
mDrawerLayout.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mDrawerLayout.closeDrawers();
break;
}
return false;
}
});
到這裡就大功告成啦!
作者:Hevin - 極光開發者
原文:Android 中 DrawerLayout + ViewPager 怎麼解決滑動衝突?
知乎專欄:極光日報