要求:中插卡片不支持ViewPager左右滑動,支持Feed流豎向滑動,支持內部卡片橫向滑動。UI效果圖如下:
設計思路:如果子視圖消費事件,並且沒有調用disallowIntercept方法,理論上最外層視圖會優先攔截事件。
由於這個需求ViewPager和RecyclerView攔截事件的條件有重合,而且我們想讓RecyclerView優先攔截事件,我們只能定製ViewPager、RecyclerView,並通過中插卡片Layout來控制ViewPager和RecyclerView的攔截標記位。
先打開RecyclerView攔截標記位,然後在RecyclerView的onInterceptTouchEvent中調用disallow方法,並且在中插卡片Layout的dispatch方法中監聽ACTION_UP和ACTION_CANCEL事件,恢復ViewPager和RecyclerView可攔截狀態
需要注意的是,中插卡片Layout一定要消費事件(onTouchEvent返回true),否則沒辦法主動控制點擊到卡片灰色部分的邏輯。
關鍵代碼如下:
class ForbidHorizontalScrollLinearLayout : LinearLayout {
private var mTouchSlop: Int = 0
private var mInitialMotionX: Float = 0f
private var mInitialMotionY: Float = 0f
constructor(context: Context?) : super(context) {
init(context)
}
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) {
init(context)
}
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
init(context)
}
fun init(context: Context?) {
val configuration: ViewConfiguration? = context?.let { ViewConfiguration.get(it) }
mTouchSlop = configuration?.scaledTouchSlop ?: 8 * 2
}
/**
* RecyclerView
*
* final ViewConfiguration vc = ViewConfiguration.get(context);
* mTouchSlop = vc.getScaledTouchSlop();
* if (canScrollHorizontally && Math.abs(dx) > mTouchSlop) {
* mLastTouchX = x;
* startScroll = true;
* }
* if (canScrollVertically && Math.abs(dy) > mTouchSlop) {
* mLastTouchY = y;
* startScroll = true;
* }
*
* ViewPager
*
* final ViewConfiguration configuration = ViewConfiguration.get(context);
* mTouchSlop = configuration.getScaledPagingTouchSlop();
* if (xDiff > mTouchSlop && xDiff * 0.5f > yDiff) {
* mIsBeingDragged = true;
* }
*
*/
override fun dispatchTouchEvent(event: MotionEvent): Boolean {
when (event.action) {
MotionEvent.ACTION_DOWN -> {
mInitialMotionX = event.x
mInitialMotionY = event.y
changeAllScrollViewState(false)
}
MotionEvent.ACTION_MOVE -> {
handleRecyclerView(event)
}
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
changeAllScrollViewState(true)
}
}
return super.dispatchTouchEvent(event)
}
private fun changeAllScrollViewState(canScroll: Boolean) {
var viewParent = parent
while (viewParent != null) {
if (viewParent is ScrollStateConfigurableViewPager) {
viewParent.scrollEnable = canScroll
} else if (viewParent is ScrollStateConfigurableRecyclerView) {
viewParent.scrollEnable = canScroll
}
viewParent = viewParent.parent
}
}
private fun switchOnRecyclerView() {
var viewParent = parent
while (viewParent != null) {
if (viewParent is ScrollStateConfigurableRecyclerView) {
viewParent.scrollEnable = true
break
}
viewParent = viewParent.parent
}
}
private fun handleRecyclerView(event: MotionEvent) {
if (abs(event.y - mInitialMotionY) > mTouchSlop) {
if (abs(event.x - mInitialMotionX) * 0.5f < abs(event.y - mInitialMotionY)) {
switchOnRecyclerView()
}
}
}
/**
* 強制返回true
*/
@SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(event: MotionEvent): Boolean = true
}
RecyclerView:
class ConfigurableRecyclerView : PeppaPagingRecyclerView {
var scrollEnable = true
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet?, defStyle: Int) : super(context, attrs, defStyle)
override fun onInterceptTouchEvent(e: MotionEvent): Boolean {
return if (scrollEnable) {
if (super.onInterceptTouchEvent(e)) {
requestDisallowInterceptTouchEvent(true)
true
} else {
false
}
} else {
false
}
}
}
ViewPager:
class ConfigurableViewPager : SSViewPager {
var scrollEnable = true
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
override fun onInterceptTouchEvent(event: MotionEvent?): Boolean {
return if (scrollEnable) {
return super.onInterceptTouchEvent(event)
} else {
false
}
}
}