事件分發(基礎理論)-附帶自定義View

推薦一篇事件分發好文章: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
        }
    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章