Android中事件传递机制

好久没做相关的UI开发,面试总被问到,这次自己看了下源码总结下,其实也并不是很复杂.
通常我们手指触摸屏幕后,会优先调用到Activity的

public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
}

这里MotionEvent就是Touch事件,会分发到Window里,我们都知道Window实现上在Activity里就是PhoneWindow了.

public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event);
}

上面会把点击事件分发到DecorView

public boolean superDispatchTouchEvent(MotionEvent event) {
        return super.dispatchTouchEvent(event);
}

DecorView实际上是一个FrameLayout,FrameLayout的爸爸是ViewGroup,ViewGroup的爸爸是View. 不过对于View中dispatchTouchEvent方法,ViewGroup自己重写了,所以上面super.dispatchTouchEvent(event)调用到的ViewGroup的dispatchTouchEvent。看看下面流程,下面代码是省略代码,为了方便理解,只写了部分关键流程

public boolean dispatchTouchEvent(MotionEvent ev) {
    final boolean intercepted;
    intercepted = onInterceptTouchEvent(ev);
    if(!intercepted) {
        //遍历子View, 走它们的dispatchTouchEvent,从而走子View的onTouchEvent函数
        for (int i = childrenCount - 1; i >= 0; i--) {  
            final View child = getAndVerifyPreorderedView(
                 preorderedList, children, childIndex);
                        if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                  //代码省略...
                                  newTouchTarget = addTouchTarget(child, idBitsToAssign);
                        }
        }
    } else {
        dispatchTransformedTouchEvent(ev, canceled, null,
        TouchTarget.ALL_POINTER_IDS); //这里会回掉父类的dispatchTouchEvent,走onTouchEvent函数
    }
        if (newTouchTarget == null) {
                  dispatchTransformedTouchEvent(ev, canceled, null,
        TouchTarget.ALL_POINTER_IDS); //这里会回掉父类的dispatchTouchEvent,走onTouchEvent函数
        }
}

这里处理流程是先看onInterceptTouchEvent返回值,onInterceptTouchEvent通常在自定义View中进行重写,返回是否拦截Touch事件,如果返回true拦截了,后续都走自己父类的dispatchTouchEvent(这里也就是View),从而走onTouchEvent流程,不会进行事件分发。如果是返回false不拦截,就会向子View分发Touch事件,但是这里要注意dispatchTransformedTouchEvent返回值,如果有子View消化了该Touch事件就不会走自己父类的dispatchTouchEvent,也就不会走到自己的onTouchEvent流程里了,如果没有消化事件还是会走自己父类的dispatchTouchEvent,从而走onTouchEvent流程。
抛开onInterceptTouchEvent()回调函数不谈,无非事件分发就是从ViewGroup,向子View分发事件(当然子View也包含ViewGroup),子View收到事件后不处理,还是会交给上层的ViewGroup自己处理.
我们来看看View中的dispatchTouchEvent()

public boolean dispatchTouchEvent(MotionEvent event) {
        //代码省略…
        boolean result = false;
        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
}

这里result代表了View是否处理该事件,通常这里会调用到onTouchEvent,onTouchEvent会处理View中Touch事件,上面的两层含义是,一个是外部设置了OnTouchListener,并在监听中返回true代表了View处理该Touch事件,另外一个onTouchEvent(event)中返回true也代表了View处理该Touch事件. 接下来看看onTouchEvent中的实现.

public boolean onTouchEvent(MotionEvent event) {
    //代码省略…
    switch (action) {
        case MotionEvent.ACTION_UP:
            performClickInternal(); //执行外部设置的OnClickListener
        break;
        case MotionEvent.ACTION_DOWN:
        break;
        case MotionEvent.ACTION_CANCEL:
        break;
        case MotionEvent.ACTION_MOVE:
        break;
    }
        return true;
    //代码省略…
}

上面通常是返回true,说明是处理了该Touch事件.
下面是举例子时间


image.png

实现两个自定义View, 一个TouchView和一个TouchViewGroup

class TouchView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
    override fun dispatchTouchEvent(event: MotionEvent?): Boolean {
        Log.i("touch", "touch dispatchTouchEvent event " + event!!.actionMasked)
        return super.dispatchTouchEvent(event)
    }
}
class TouchViewGroup(
    context: Context, attrs: AttributeSet?
) : FrameLayout(context, attrs) {

    override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
        Log.i("touch", "dispatchTouchEvent TouchViewGroup event " + ev!!.actionMasked)
        return super.dispatchTouchEvent(ev)
    }
}

放入如下布局




    

        

    

案例一:事件的大致传递流程, 事件是从Activity传递到PhoneWindow, 再到decorView,再到TouchViewGroup,然后到TouchView

基于上面代码,添加如下代码

class MainActivity : Activity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }

    override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
        Log.i("touch", "dispatchTouchEvent MainActivity event " + ev!!.actionMasked)
        return super.dispatchTouchEvent(ev)
    }
}

输出:

2020-04-13 16:31:01.434 28958-28958/com.example.k_demo I/touch: dispatchTouchEvent MainActivity event 0
2020-04-13 16:31:01.434 28958-28958/com.example.k_demo I/touch: dispatchTouchEvent TouchViewGroup event 0
2020-04-13 16:31:01.434 28958-28958/com.example.k_demo I/touch: dispatchTouchEvent TouchView event 0
2020-04-13 16:31:01.518 28958-28958/com.example.k_demo I/touch: dispatchTouchEvent MainActivity event 1
案例二,我想在TouchView处理自己事件,并不交给上层TouchViewGroup,怎么做?

处理方式有很多,实际上dispatchTouchEvent不经过不可能,我们能控制的无非是TouchViewGroup不会走它的onTouchEvent方法.

class TouchViewGroup(
    context: Context, attrs: AttributeSet?
) : FrameLayout(context, attrs) {

    override fun onTouchEvent(event: MotionEvent?): Boolean {
        Log.i("touch", "onTouchEvent TouchViewGroup event " + event!!.actionMasked)
        return super.onTouchEvent(event)
    }
}

方法一

class TouchView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
    override fun dispatchTouchEvent(event: MotionEvent?): Boolean {
        Log.i("touch", "dispatchTouchEvent TouchView event " + event!!.actionMasked)
        return true
    }
}

输出

2020-04-13 16:38:52.530 29573-29573/com.example.k_demo I/touch: dispatchTouchEvent TouchView event 0
2020-04-13 16:38:52.622 29573-29573/com.example.k_demo I/touch: dispatchTouchEvent TouchView event 2
2020-04-13 16:38:53.112 29573-29573/com.example.k_demo I/touch: dispatchTouchEvent TouchView event 2
2020-04-13 16:38:53.201 29573-29573/com.example.k_demo I/touch: dispatchTouchEvent TouchView event 1

这样可以在自定义TouchView做自己的touch逻辑不会回调到TouchViewGroup中的onTouchEvent了.
方法二

class TouchView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
    override fun onTouchEvent(event: MotionEvent?): Boolean {
        Log.i("touch", "onTouchEvent TouchView event " + event!!.actionMasked)
        return true
    }
}

输出结果同上

案例三,如何在TouchViewGroup拦截掉Touch,不分发到子View中
class TouchViewGroup(
    context: Context, attrs: AttributeSet?
) : FrameLayout(context, attrs) {

    override fun onTouchEvent(event: MotionEvent?): Boolean {
        Log.i("touch", "onTouchEvent TouchViewGroup event " + event!!.actionMasked)
        return true
    }

    override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
        return true
    }
}

onInterceptTouchEvent拦截向下传递事件,onTouchEvent返回true,表示TouchViewGroup需要处理该Touch事件.
如上,就写到这里,一个总结。

你可能感兴趣的:(Android中事件传递机制)