推薦一篇事件分發好文章:https://mp.weixin.qq.com/s/5zrZOVQlV6LAOgpD9DF35g
// Activity
public boolean dispatchTouchEvent(MotionEvent ev) {
//如果Activity所屬Window的dispatchTouchEvent返回了ture
//則Activity.dispatchTouchEvent返回ture,點擊事件停止往下傳遞
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
//如果Window的dispatchTouchEvent返回了false,則點擊事件傳遞給Activity.onTouchEvent
return onTouchEvent(ev);
}
// PhoneWindow
private DecorView mDecor;
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
// ViewGroup
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
// Check for interception.
final boolean intercepted; //是否攔截
// 當事件由ViewGroup的子元素處理時,mFirstTouchTarget會被賦值並指向子元素
if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
// FLAG_DISALLOW_INTERCEPT標記位是通過requestDisallowInterceptTouchEvent方法來設置,一般用在子View中。
// 如果FLAG_DISALLOW_INTERCEPT被設置後,ViewGroup將無法攔截除了ACTION_DOWN以外的其它點擊事件,這是
// 因爲ViewGroup在分發事件中,如果是ACTION_DOWN事件,將會重置FLAG_DISALLOW_INTERCEPT這個標記位
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
if (intercepted) {
// 遍歷所有子View
if (子View消費事件){
mFirstTouchTarget = 子View
return true
}
}
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
return result
}
要求:中插卡片不支持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
}
}
}