起因
主頁的某一個(gè)Tab有是一個(gè)類似與新聞客戶端的多tab頁面世澜,如QQ圖片20180724175629.png
在用戶要滑動(dòng)的Banner活動(dòng)圖的時(shí)候播演,時(shí)而會(huì)觸發(fā)切換tab沧烈,時(shí)而會(huì)觸發(fā)切換banner導(dǎo)致操作上的一種不適感
現(xiàn)象大致分析
- 即使不看源碼暗赶,憑感覺也可以知道肯定是ViewPager的在處理Move行為的時(shí)候沒有判斷好(因?yàn)閐own的時(shí)候肯定是不知道讓哪一個(gè)組件來處理的)
需要達(dá)到的效果
- 在Banner處滑動(dòng)的時(shí)候彤避,交由Banner來處理
- 在Banner下方列表處上下滑動(dòng)交由該tab下的recycleview來處理(也就是不需要任何改動(dòng))
- 在Banner下方列表處左右滑動(dòng)交由ViewPager來處理
源碼分析
我們直接來看ViewPager的onInterceptTouchEvent方法,畢竟是這個(gè)方法來處理事件的攔截與否
switch (action) {
case MotionEvent.ACTION_MOVE: {
/*
* mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
* whether the user has moved far enough from his original down touch.
*/
/*
* Locally do absolute value. mLastMotionY is set to the y value
* of the down event.
*/
final int activePointerId = mActivePointerId;
if (activePointerId == INVALID_POINTER) {
// If we don't have a valid id, the touch down wasn't on content.
break;
}
final int pointerIndex = ev.findPointerIndex(activePointerId);
final float x = ev.getX(pointerIndex);
final float dx = x - mLastMotionX;
final float xDiff = Math.abs(dx);
final float y = ev.getY(pointerIndex);
final float yDiff = Math.abs(y - mInitialMotionY);
if (DEBUG) Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff);
if (dx != 0 && !isGutterDrag(mLastMotionX, dx)
&& canScroll(this, false, (int) dx, (int) x, (int) y)) {
// Nested view has scrollable area under this point. Let it be handled there.
mLastMotionX = x;
mLastMotionY = y;
mIsUnableToDrag = true;
return false;
}
if (xDiff > mTouchSlop && xDiff * 0.5f > yDiff) {
if (DEBUG) Log.v(TAG, "Starting drag!");
mIsBeingDragged = true;
requestParentDisallowInterceptTouchEvent(true);
setScrollState(SCROLL_STATE_DRAGGING);
mLastMotionX = dx > 0
? mInitialMotionX + mTouchSlop : mInitialMotionX - mTouchSlop;
mLastMotionY = y;
setScrollingCacheEnabled(true);
} else if (yDiff > mTouchSlop) {
// The finger has moved enough in the vertical
// direction to be counted as a drag... abort
// any attempt to drag horizontally, to work correctly
// with children that have scrolling containers.
if (DEBUG) Log.v(TAG, "Starting unable to drag!");
mIsUnableToDrag = true;
}
if (mIsBeingDragged) {
// Scroll to follow the motion event
if (performDrag(x)) {
ViewCompat.postInvalidateOnAnimation(this);
}
}
break;
}
case MotionEvent.ACTION_DOWN: {
/*
* Remember location of down touch.
* ACTION_DOWN always refers to pointer index 0.
*/
mLastMotionX = mInitialMotionX = ev.getX();
mLastMotionY = mInitialMotionY = ev.getY();
mActivePointerId = ev.getPointerId(0);
mIsUnableToDrag = false;
mIsScrollStarted = true;
mScroller.computeScrollOffset();
if (mScrollState == SCROLL_STATE_SETTLING
&& Math.abs(mScroller.getFinalX() - mScroller.getCurrX()) > mCloseEnough) {
// Let the user 'catch' the pager as it animates.
mScroller.abortAnimation();
mPopulatePending = false;
populate();
mIsBeingDragged = true;
requestParentDisallowInterceptTouchEvent(true);
setScrollState(SCROLL_STATE_DRAGGING);
} else {
completeScroll(false);
mIsBeingDragged = false;
}
if (DEBUG) {
Log.v(TAG, "Down at " + mLastMotionX + "," + mLastMotionY
+ " mIsBeingDragged=" + mIsBeingDragged
+ "mIsUnableToDrag=" + mIsUnableToDrag);
}
break;
}
case MotionEventCompat.ACTION_POINTER_UP:
onSecondaryPointerUp(ev);
break;
}
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(ev);
/*
* The only time we want to intercept motion events is if we are in the
* drag mode.
*/
return mIsBeingDragged;
}
返回值為mIsBeingDragged麦乞,并且上文說了只需要看Move的行為蕴茴,那么可以得知我們只需要看ViewPager什么時(shí)候返回了false(也就是你不要攔我啊,我要這個(gè)行為給我的孩子)
僅需要看這一段
if (dx != 0 && !isGutterDrag(mLastMotionX, dx)
&& canScroll(this, false, (int) dx, (int) x, (int) y)) {
// Nested view has scrollable area under this point. Let it be handled there.
mLastMotionX = x;
mLastMotionY = y;
mIsUnableToDrag = true;
return false;
}
- dx!=0不需要關(guān)注姐直,必然符合
- isGutterDrag表示是否是在ViewPager的縫隙處滑動(dòng)倦淀,不需要關(guān)注
- 重點(diǎn)來了,就是這個(gè)canScroll方法
protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) {
if (v instanceof ViewGroup) {
final ViewGroup group = (ViewGroup) v;
final int scrollX = v.getScrollX();
final int scrollY = v.getScrollY();
final int count = group.getChildCount();
// Count backwards - let topmost views consume scroll distance first.
for (int i = count - 1; i >= 0; i--) {
// TODO: Add versioned support here for transformed views.
// This will not work for transformed views in Honeycomb+
final View child = group.getChildAt(i);
if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight()
&& y + scrollY >= child.getTop() && y + scrollY < child.getBottom()
&& canScroll(child, true, dx, x + scrollX - child.getLeft(),
y + scrollY - child.getTop())) {
return true;
}
}
}
return checkV && ViewCompat.canScrollHorizontally(v, -dx);
}
緊接著
- 可以看到声畏,這個(gè)方法的目的就是在于遍歷子View撞叽,孫View等里是否有可以橫向滾動(dòng)的組件并且落點(diǎn)在組件內(nèi)部的
- 關(guān)鍵方法ViewCompat.canScrollHorizontally姻成,按理來說RecycleView設(shè)置了橫向的LinearLayoutManager,應(yīng)該是沒問題才對(duì)
- 進(jìn)到RecycleView源碼可知愿棋,并沒有重寫canScrollHorizontally科展,而是在合適的時(shí)候(比如onTouch)返回了mLayout.canScrollHorizontally()
處理方案
-
方案一
重寫RecycleView的canScrollHorizontally,直接返回為true
經(jīng)測(cè)試糠雨,有效
-
方案二
手動(dòng)處置ViewPager的onInterceptTouchEvent才睹,添加監(jiān)聽,在監(jiān)聽中判斷落點(diǎn)是否在Banner的組件里甘邀,并且在RecycleView重寫dispatchTouchEvent
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return mMoveListener == null ? !noScroller && super.onInterceptTouchEvent(ev) : mMoveListener.onMoveTouchEvent(ev) ? super.onInterceptTouchEvent(ev) : false;
}
private OnMoveTouchListener mMoveListener;
public void setOnMoveListener(OnMoveTouchListener l) {
mMoveListener = l;
}
public interface OnMoveTouchListener {
boolean onMoveTouchEvent(MotionEvent ev);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getY() > getMeasuredHeight())
return false;
return super.dispatchTouchEvent(ev);
}
- 還沒完琅攘,還需要判斷當(dāng)前界面是否顯示
@Override
public boolean onMoveTouchEvent(MotionEvent ev) {
boolean b = getFragmentVisible() ? !mModulePage.getBannerPager().dispatchTouchEvent(ev) : false;
return b;
}
總之非常的麻煩,效果也能達(dá)到松邪,還是方案一好坞琴,完結(jié)