不知道大家是否有這個疑問,在 剛開始做Android的時候 ViewPager 占卧,ViewPager 嵌套 ViewPager 并沒有出現(xiàn)過滑動沖突遗菠×可是為什么在 ViewPager 的升級版 ViewPager2 中卻出現(xiàn)了滑動沖突呢?
- 首先我們看下
ViewPager
的源碼
滑動沖突是需要在 onInterceptTouchEvent()方法中進行處理的辙纬,根據(jù)自身條件豁遭,來決定是否要攔截事件。在 ViewPager 的源碼中看到以下代碼:
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
final int action = ev.getAction() & MotionEvent.ACTION_MASK;
if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
// 在事件取消或者抬起手指后重置狀態(tài)
resetTouch();
return false;
}
switch (action) {
case MotionEvent.ACTION_MOVE: {
// 這里判斷在水平方向上的滑動距離大于豎直方向的2倍贺拣,則認為是有效的切換頁面的滑動
if (xDiff > mTouchSlop && xDiff * 0.5f > yDiff) {
mIsBeingDragged = true;
// 禁止Parent View攔截事件蓖谢,即事件要能夠傳遞到ViewPager
requestParentDisallowInterceptTouchEvent(true);
setScrollState(SCROLL_STATE_DRAGGING);
} else if (yDiff > mTouchSlop) {
mIsUnableToDrag = true;
}
break;
}
case MotionEvent.ACTION_DOWN: {
if (mScrollState == SCROLL_STATE_SETTLING
&& Math.abs(mScroller.getFinalX() - mScroller.getCurrX()) > mCloseEnough) {
// 在Down事件中禁止Parent View攔截事件,是為了事件序列能夠傳遞到ViewPager
requestParentDisallowInterceptTouchEvent(true);
setScrollState(SCROLL_STATE_DRAGGING);
} else {
completeScroll(false);
mIsBeingDragged = false;
}
break;
}
case MotionEvent.ACTION_POINTER_UP:
onSecondaryPointerUp(ev);
break;
}
return mIsBeingDragged;
}
- 可以看 在
ACTION_DOWN
與ACTION_MOVE
中根據(jù)一些判斷條件譬涡,調(diào)用了 requestParentDisallowInterceptTouchEvent(true) 方法來禁止Parent View
攔截事件闪幽。也就是說,ViewPager
已經(jīng)幫我們處理了滑動沖突涡匀,所以我們只管用即可盯腌,無需擔心滑動沖突問題。 - 現(xiàn)在陨瘩,我們看
ViewPager2
翻閱源碼發(fā)現(xiàn)腕够,只有在RecyclerView
的實現(xiàn)類中有 onInterceptTouchEvent() 的相關(guān)方法,而且這句代碼僅僅是處理禁用了用戶輸入的邏輯
private class RecyclerViewImpl extends RecyclerView {
.... // 省略部分代碼
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return isUserInputEnabled() && super.onInterceptTouchEvent(ev);
}
}
-
ViewPager2
其實并沒有幫我們處理滑動沖突舌劳!
ViewPager2
被聲明了 final帚湘,意味著我們不能像繼承 ViewPager一樣,來修改ViewPager2
甚淡。如果官方在ViewPager2
內(nèi)部自行處理了滑動沖突大诸,那么如果有特殊的需求,需要根據(jù)我們自己的情況材诽,來處理ViewPager2
的滑動底挫,那么官方寫的處理滑動沖突的代碼,是不是會影響到我們自己的需求? - 由于
ViewPager2
被設(shè)置成了final
脸侥,我們無法通過繼承的方式來處理建邓,因此就需要我們在ViewPager2
外部加一層自定義的Layout
。這層Layout
其實相當于夾在了內(nèi)層 View 和外層 View 的中間睁枕,其實就是這層Layout
就變成了內(nèi)層.
詳情代碼實現(xiàn):
/**
* @Desciption :ViewPager2 嵌套后的滑動沖突解決方案
*/
class ViewPager2Container @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : RelativeLayout(context, attrs, defStyleAttr) {
private var mViewPager2: ViewPager2? = null
private var disallowParentInterceptDownEvent = true
private var startX = 0
private var startY = 0
//遍歷ViewPager2Container 的所有子 View官边,如果沒有找到 ViewPager2 就拋出異常
override fun onFinishInflate() {
super.onFinishInflate()
for (i in 0 until childCount) {
val childView = getChildAt(i)
if (childView is ViewPager2) {
mViewPager2 = childView
break
}
}
if (mViewPager2 == null) {
throw IllegalStateException("The root child of ViewPager2Container must contains a ViewPager2")
}
}
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
val doNotNeedIntercept = (!mViewPager2!!.isUserInputEnabled
|| (mViewPager2?.adapter != null
&& mViewPager2?.adapter!!.itemCount <= 1))
if (doNotNeedIntercept) {
return super.onInterceptTouchEvent(ev)
}
when (ev.action) {
MotionEvent.ACTION_DOWN -> {
startX = ev.x.toInt()
startY = ev.y.toInt()
parent.requestDisallowInterceptTouchEvent(!disallowParentInterceptDownEvent)
}
MotionEvent.ACTION_MOVE -> {
val endX = ev.x.toInt()
val endY = ev.y.toInt()
val disX = abs(endX - startX)
val disY = abs(endY - startY)
if (mViewPager2!!.orientation == ViewPager2.ORIENTATION_VERTICAL) {
onVerticalActionMove(endY, disX, disY)
} else if (mViewPager2!!.orientation == ViewPager2.ORIENTATION_HORIZONTAL) {
onHorizontalActionMove(endX, disX, disY)
}
}
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> parent.requestDisallowInterceptTouchEvent(false)
}
return super.onInterceptTouchEvent(ev)
}
private fun onHorizontalActionMove(endX: Int, disX: Int, disY: Int) {
if (mViewPager2?.adapter == null) {
return
}
if (disX > disY) {
val currentItem = mViewPager2?.currentItem
val itemCount = mViewPager2?.adapter!!.itemCount
if (currentItem == 0 && endX - startX > 0) {
parent.requestDisallowInterceptTouchEvent(false)
} else {
parent.requestDisallowInterceptTouchEvent(currentItem != itemCount - 1
|| endX - startX >= 0)
}
} else if (disY > disX) {
parent.requestDisallowInterceptTouchEvent(false)
}
}
private fun onVerticalActionMove(endY: Int, disX: Int, disY: Int) {
if (mViewPager2?.adapter == null) {
return
}
val currentItem = mViewPager2?.currentItem
val itemCount = mViewPager2?.adapter!!.itemCount
if (disY > disX) {
if (currentItem == 0 && endY - startY > 0) {
parent.requestDisallowInterceptTouchEvent(false)
} else {
parent.requestDisallowInterceptTouchEvent(currentItem != itemCount - 1
|| endY - startY >= 0)
}
} else if (disX > disY) {
parent.requestDisallowInterceptTouchEvent(false)
}
}
}
- 使用方法用
ViewPager2Container
包裹ViewPager2
就可以