Android开发Android开发

Android事件体系

2018-11-14  本文已影响8人  小智pikapika

一切的起源

https://blog.csdn.net/a992036795/article/details/51690303 这篇文章中对Android事件的起源进行了详细的分析,虽说代码版本应该是Android5.0以前的,但也具有一定的参考价值。
总结起来大致可以概括为:Activity启动过程中会创建PhoneWindow和DecorView,然后通过ViewRootImpl向InputManagerService注册一个InputChannel以监听屏幕触摸事件,后续的工作原理涉及硬件层的知识就不再追溯。从这个流程里面我们可以提取出我们所关心的问题:Android事件的源头是在Activity启动过程中注册的,事件流向应用层可以简单认为是Activity->View。

事件传递机制

event.png
Android整个事件流转过程如上图所示,是一个从Activity->ViewGroup->View自上而下的过程,当某一层拦截了事件传递便就此结束,如果下面的层级都不需要拦截事件,则再次一层层向上回传,最终回到Acitivity的onTouchEvent方法。整个过程和现实中工作安排是一模一样的,老板把任务安排给部门领导,部门领导再安排给手下,一层层向下委派,当底层手下无法解决这个问题时,再一层层向上反馈,最终回到老板处。其中ViewGroup的事件分发伪代码如下:
public boolean dispatchTouchEvent(MotionEvent event){
  //是否处理事件标志
  boolean handled = false;
  if(!onInterceptTouchEvent()){
    //遍历子view分发
    handled = childView.dispatchTouchEvent(event);
  }
  if(!handled) handled = onTouchEvent(event);
  return handled;
}

事件序列

Android的touch事件从手指触摸屏幕开始,到手指离开屏幕,可以视为一个完整的事件序列,从MotionEvent的类型来看,一个事件序列包含一个ACTION_DOWN事件、n个ACTION_MOVE事件、一个ACTION_UP事件:
ACTION_DOWN:手指刚接触屏幕
ACTION_MOVE:手指在屏幕上滑动
ACTION_UP:手指离开屏幕
一个完整的事件序列只能被同一个view消耗,简而言之,一旦某个view决定拦截ACTION_DOWN事件之后,这个事件序列之后的事件都将由该view消耗,其他的view即使想要单独拦截ACTION_MOVE、ACTION_DOWN事件也是做不到的,我们可以写个简单的demo来论证这个理论。

public class CustomViewGroup extends ViewGroup {
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.e("TAG","ViewGroup dispatchTouchEvent:"+ev.getAction());
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.e("TAG","ViewGroup onInterceptTouchEvent:"+ev.getAction());
//第二次测试        
//        if(event.getAction() == MotionEvent.ACTION_DOWN) {
//            return true;
//        }else{
//第三次测试
//            return false;
//        }  
        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.e("TAG","ViewGroup onTouchEvent:"+event.getAction());
//        if(event.getAction() == MotionEvent.ACTION_DOWN) {
//            return true;
//        }else{
//第三次测试
//            return false;
//        }  
        return super.onTouchEvent(event);
    }
}
public class CustomView extends TextView {
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.e("TAG","View dispatchTouchEvent:"+ev.getAction());
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.e("TAG","View onTouchEvent:"+event.getAction());
//        if(event.getAction() == MotionEvent.ACTION_DOWN) {
//            return true;
//        }
        return super.onTouchEvent(event);
    }
}

初次测试:CustomViewGroup,CustomView均设置为不可点击,不拦截任何事件,日志如下:

//0-ACTION_DOWN,1-ACTION_UP,2-ACTION_MOVE
11-13 23:05:05.751 17354-17354/ E/TAG: ViewGroup dispatchTouchEvent:0
11-13 23:05:05.751 17354-17354/ E/TAG: ViewGroup onInterceptTouchEvent:0
11-13 23:05:05.751 17354-17354/ E/TAG: View dispatchTouchEvent:0
11-13 23:05:05.751 17354-17354/ E/TAG: View onTouchEvent:0
11-13 23:05:05.751 17354-17354/ E/TAG: ViewGroup onTouchEvent:0

正如事件分发机制所述,事件经过CustomViewGroup#dispatchTouchEvent->CustomViewGroup#onInterceptTouchEvent->子View#dispatchTouchEvent->子View#onTouchEvent,子View无法处理后再次回到CustomViewGroup#onTouchEvent。(这边发现日志只有ACTION_DOWN的事件分发,暂且记下后续会有说明)

第二次测试:CustomViewGroup,CustomView均设置拦截ACTION_DOWN事件,日志如下:

//
11-13 23:17:00.691 22798-22798/ E/TAG: ViewGroup dispatchTouchEvent:0
11-13 23:17:00.691 22798-22798/ E/TAG: ViewGroup onInterceptTouchEvent:0
11-13 23:17:00.691 22798-22798/ E/TAG: ViewGroup onTouchEvent:0
11-13 23:17:00.751 22798-22798/ E/TAG: ViewGroup dispatchTouchEvent:1
11-13 23:17:00.751 22798-22798/ E/TAG: ViewGroup onTouchEvent:1

由日志可看出,ViewGroup决定拦截ACTION_DOWN事件之后,都没有调用子view的dispatch,而且在分发ACTION_UP事件时都没有调用onInterceptTouchEvent。

第三次测试:CustomViewGroup,CustomView均设置拦截ACTION_DOWN事件,并且CustomViewGroup设置只拦截ACTION_DOWN事件,日志如下:

//
11-13 23:22:39.181 24428-24428/ E/TAG: ViewGroup dispatchTouchEvent:0
11-13 23:22:39.181 24428-24428/ E/TAG: ViewGroup onInterceptTouchEvent:0
11-13 23:22:39.181 24428-24428/ E/TAG: ViewGroup onTouchEvent:0
11-13 23:22:39.261 24428-24428/ E/TAG: ViewGroup dispatchTouchEvent:2
11-13 23:22:39.261 24428-24428/ E/TAG: ViewGroup onTouchEvent:2
11-13 23:22:39.261 24428-24428/ E/TAG: ViewGroup dispatchTouchEvent:1
11-13 23:22:39.261 24428-24428/ E/TAG: ViewGroup onTouchEvent:1

由日志可看出,ViewGroup决定拦截ACTION_DOWN事件之后,即使设置不拦截其他类型事件,整个事件序列的日志还是到ViewGroup就结束了。
综上所述,Android对于事件序列的分发有某种特殊的机制:一个事件序列只会由拦截ACTION_DOWN的那个View消耗,同时在事件分发时有做过优化,能够在分发ACTION_UP、ACTION_MOVE事件时找到拦截ACTION_DOWN的那个View并跳过onIntercept方法。得出该结论之后,再回到源码也能找到相应的依据,具体代码片段见ViewGroup的dispatchTouchEvent方法:

//处理ACTION_DOWN事件时会记录touchTarget
if (actionMasked == MotionEvent.ACTION_DOWN
                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
           ...
           newTouchTarget = addTouchTarget(child, idBitsToAssign);
           ...
}

          //后续会对touchTarget进行判断
           if (mFirstTouchTarget == null) {
                // No touch targets so treat this as an ordinary view.
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            }else {
                // Dispatch to touch targets, excluding the new touch target if we already
                // dispatched to it.  Cancel touch targets if necessary.
                TouchTarget predecessor = null;
                TouchTarget target = mFirstTouchTarget;
                while (target != null) {
                    final TouchTarget next = target.next;
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                        handled = true;
                    } else {
                        final boolean cancelChild = resetCancelNextUpFlag(target.child)
                                || intercepted;
                        if (dispatchTransformedTouchEvent(ev, cancelChild,
                                target.child, target.pointerIdBits)) {
                            handled = true;
                        }
                

由此可见,在一次事件序列分发过程中,ViewGroup会记录最初的touchTarget,有利于提高后续的事件分发效率。

滑动冲突

对Android View的事件分发体系有了完整的了解之后,比较经典的应用就是滑动冲突的解决,核心思想就是ACTION_UP事件拦截的特殊处理,方法有内部拦截法和外部拦截法两种,有兴趣的童鞋可以查查资料了解下。

上一篇 下一篇

猜你喜欢

热点阅读