从Android源码的角度了解事件分发机制

事件的定义

定义:当用户触摸屏幕时,将产生的触摸行为(Touch事件)

事件类型
  • MotionEvent.ACTION_DOWN 手指刚接触屏幕
  • MotionEvent.ACTION_UP 手指从屏幕移开
  • MotionEvent.ACTION_MOVE 手指在屏幕上滑动
  • MotionEvent.ACTION_CANCEL 非人为因素取消
事件序列

正常情况下,手指触摸屏幕的行为会发出一些列事件:

  • 点击屏幕后立即松开,事件序列为DOWN—>UP
  • 点击屏幕滑动一会儿松开,事件序列为DOWN—>MOVE…MOVE…MOVE—>UP
  • 还有一种非人为因素的取消,CANCEL

事件分发对象
  • Activity:控制生命周期&处理事件
  • ViewGroup:一组View的集合(含多个子View)
  • View:所有UI组件的基类
事件分发的主要方法
  • dispatchTouchEvent(MotionEvent ev) -> 用来进行事件分发
  • onInterceptTouchEvent(MotionEvent ev) -> 判断是否拦截事件(只存在于ViewGroup中)
  • onTouchEvent(MotionEvent ev) -> 处理拦截事件

事件分发

Activity的事件分发

当事件发生时,首先会调用Activity的dispatchMotionEvent()方法,跟踪源码发现最终调用了ViewGroup的dispatchMotionEvent()方法,进入源码查查:

//Activity中的方法
public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        onUserInteraction(); //空方法供子类实现
    }
    if (getWindow().superDispatchTouchEvent(ev)) {
        //调用window的superDispatchTouchEvent(),由于在Window中是一个抽象方法,而PhoneWindow是其唯一实现类,又调用到PhoneWindow的方法
        return true;
    }
    return onTouchEvent(ev);
}

//Window中
//这是一个抽象方法,需要寻找其唯一实现类PhoneWindow的方法
public abstract boolean superDispatchTouchEvent(MotionEvent event);

//PhoneWindow中
public boolean superDispatchTouchEvent(MotionEvent event) {
    //返回了DecorView的superDispatchTouchEvent(),而DecorView其实就是一个Framelayout,最终就调用到了ViewGroup中的superDispatchTouchEvent()
    return mDecor.superDispatchTouchEvent(event);
}

//ViewGroup中
public boolean dispatchTouchEvent(MotionEvent ev) {
    //省略大量代码
    如果handled = true,在Activity中就完成了此次的事件分发,如果handled = false将会调用onTouchEvent()方法
    return handled;
}

//在Activity中
public boolean onTouchEvent(MotionEvent event) {
    if (mWindow.shouldCloseOnTouch(this, event)) {
        除了这里返回true时结束Activity,
        finish();
        return true;
    }
    //一律不处理,本次事件分发结束
    return false;
}

//此方法主要在判断是否是点击了Activity外部且是ACTION_DOWN事件,其实典型的案例就是dialog型的activity
public boolean shouldCloseOnTouch(Context context, MotionEvent event) {
    if (mCloseOnTouchOutside && event.getAction() == MotionEvent.ACTION_DOWN
             && isOutOfBounds(context, event) && peekDecorView() != null) {
        return true;
    }
    return false;
  }
ViewGroup的事件分发

由于我们的一个完整的分发事件开始于dispatchTouchEvent()方法,并且开始的事件一定是ACTION_DOWN,所以我们来看源码

//ViewGroup中
public boolean dispatchTouchEvent(MotionEvent ev) {
    //省略的代码
    
    //(1)、Handle an initial down.所以在这里需要初始化一些东西
    if (actionMasked == MotionEvent.ACTION_DOWN) {
        // Throw away all previous state when starting a new touch gesture.
        // The framework may have dropped the up or cancel event for the previous gesture
        // due to an app switch, ANR, or some other state change.
        
        //这是源码注释,取消和清除所有的触摸状态 Cancels and clears all touch targets.
        cancelAndClearTouchTargets(ev); 
        //源码注释,重置所有触摸状态,为新的循环做准备。Resets all touch state in preparation for a new cycle.
        resetTouchState();
        
        //此刻初始化的操作完成
    }
    
    //(2)、Check for interception. 检查是否拦截
    final boolean intercepted;//标记是否拦截的状态
    //先判断是否是ACTION_DOWN事件,或者是其他子控件处理了(mFirstTouchTarget != null表示有其他子控件处理)
    if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
        //这里判断是否拦截的操作FLAG_DISALLOW_INTERCEPT那么这个在哪里设置呢,转到下一个片段
        final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
        if (!disallowIntercept) {
            //需要拦截就调用此方法,这里默认都是返回false,继续向下传递处理,如果为true只能是子View继承了去修改控制,那么直接让当前的ViewGroup的onTouchEvent处理不会向下传递了
            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.
        //不是ACTION_DOWN事件也没有子控件处理那么久拦截传递到activity去处理
        intercepted = true;
    }
    
    /**
     * mGroupFlags的FLAG_DISALLOW_INTERCEPT的设置位置,在ViewGroup里有这样一个方法,通常在子控件中实现调用,所有我们在处理事件分发是需要用到此方法
    **/
    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
        //省略源码
    }
    
    
}
ViewGroup的onInterceptTouchEvent()分析:
public boolean onInterceptTouchEvent(MotionEvent ev) {
    //继续向下传播,为true的时候就直接发送给当前viewGroup的onTouchEvent()方法处理了且不继续向下传播
    return false;
}
  • 可以通过实现这个方法去拦截所有的touch事件;这个可以让viewgroup监视分发给它的子view的事件,可以在任何时候取得当前手势事件的控制权;
  • down事件可能会被这个viewGroup的一个子孩子处理掉,或者给自己的onTouchEvent()事件处理了。这意味着你需要实现onTouchEvent()方法然后返回true,然后你就会继续接收到剩下的手势事件(而不是继续向上找一个父View去处理这个事件)。同时,onTouchEvent()方法返回true的时候,viewGroup的onInterceptTouchEvent()方法就不会再接收到陆续的方法事件,所有的touch事件会正常的直接给onTouchEvent()方法。
  • 如果这个方法返回false,接下来的后续事件都会先传递到这里,然后再传递给处理这个事件的目标view的onTouchEvent。
  • 如果你这里返回true,你不会再接收到后续的事件,目标view会接受到相同的事件产生的ACTION_CANCEL;其他的事件也会直接传递到你的onTouchEvent,而再也不会在onInterceptTouchEvent()方法里捕捉到。
  • 如果返回true就会从他的子孩子抢占过来这个事件且将这个事件分发给当前ViewGroup的onTouchEvent()事件处理。当前的目标View会接受到ACTION_CANCEL事件,且不会再接受到后续的事件了。
继续ViewGroup的dispatchTouchEvent()分析
public boolean dispatchTouchEvent(MotionEvent ev) {
    //没有被取消也没有被拦截
    if (!canceled && !intercepted) {
        //这里主要做了一件事情,有哪些子控件能够去接受此次事件的分发
        for (int i = childrenCount - 1; i >= 0; i--) {
            //for遍历所有的子控件
            if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) {
                //判断是否在我点击的范围内是能够接收此次事件的view,如果不是则继续遍历
                continue;
            }
            
            //继续执行到这里
            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {}
        }
    }
}

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
    //这里都是做一些判断,最终都调用到了View的dispatchTouchEvent()方法
    if (child == null) {
            handled = super.dispatchTouchEvent(transformedEvent); // 调父类的dispatchTouchEvent()
    } else {
        final float offsetX = mScrollX - child.mLeft;
        final float offsetY = mScrollY - child.mTop;
        transformedEvent.offsetLocation(offsetX, offsetY);
        if (! child.hasIdentityMatrix()) {
            transformedEvent.transform(child.getInverseMatrix());
        }

        handled = child.dispatchTouchEvent(transformedEvent);// 调子类的dispatchTouchEvent()
    }
}
View的事件分发
View中同样也是从dispatchTouchEvent()开始的事件分发
public boolean dispatchTouchEvent(MotionEvent event) {
    final int actionMasked = event.getActionMasked();
    if (actionMasked == MotionEvent.ACTION_DOWN) {
        // Defensive cleanup for new gesture
        stopNestedScroll(); //先判断是否是点击事件,然后停止其他的一些滚动事件
    }
    
    //这里选择调用那种onTouch()
    if (onFilterTouchEventForSecurity(event)) {
        //noinspection SimplifiableIfStatement
        ListenerInfo li = mListenerInfo;
        //这个监听不为空那么执行此方法
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {
            result = true;
        }
        
        //如果上面没有执行这里就判断进入并执行onTouchEvent()
        if (!result && onTouchEvent(event)) {
            result = true;
        }
        
        //从上看出增加了监听的事件优先级更高一些
    }
}
View的onTouchEvent()执行流程分析
public boolean onTouchEvent(MotionEvent event) {
    //先有一段代码判断是否可点击状态
    
    //如果设置代理事件,那么直接交给代理去处理
    if (mTouchDelegate != null) {
        if (mTouchDelegate.onTouchEvent(event)) {
            return true;
        }
    }
    
    //如果View可点击,可长按最后一定返回true
    if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
        //接下来就会进入switch
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                //按下,主要去处理长按事件
                break;
            case MotionEvent.ACTION_MOVE:
                //移动,判断触摸是否划出了控件,移除响应事件
                break;
            case MotionEvent.ACTION_UP:
                //抬起,判断是否处理点击事件
                break;
            case MotionEvent.ACTION_CANCEL:
                break;
        }
        return true;
    }
    return false;
}
事件分发模型流程图

事件分发结论
  • 一个事件序列从手指接触屏幕到手指离开屏幕,在这个过程中产生一系列
    事件,以DOWN事件开始,中间含有不定数的MOVE事件,以UP事件结束;
  • 正常情况下,一个事件序列只能被一个View拦截并且消耗;
  • 某个View一旦决定拦截,那么这个事件序列都将由它的onTouchEvent处理,并且它的onInterceptTouchEvent不会再调用;
  • 某个View-旦开始处理事件,如果它不消耗ACTION_DOWN事件(onTouchEvent返回false),那么同一事 件序列中其他事件都不会再交给它处理。并且重新交由它的父元素处理(父元素onTouchEvent被调用);
  • 事件的传递过程是由外向内的,即事件总是先传递给父元素,然后再由父元素分发给子View,通过requestDisallowInterceptTouchEvent方法可以在子View中干预父元素的事件分发过程,但ACTION_ DOWN除外;
  • ViewGroup默认不拦截任何事件,即onInterceptTouchEvent默认返回false。View没有onInterceptTouchEvent方法,一旦有点击事件传递给它,那么它的onTouchEvent方法就会被调用;
  • View的onTouchEvent默认会消耗事件(返回true),除非它是不可点击的
    (clickable和longClickable同时为false)。View的longClickable默认都为false, clickable要分情况,比如Button的clickable默认为true,TextView的clickable默认为false;
  • View的enable属性不影响onTouchEvent的默认返回值。哪怕一个View是disable状态的,只要它的clickable或者longClickable有一个为true,那么它的onTouchEvent就返回true;
  • onClick会响应的前提是当前View是可点击的,并且收到了ACTION_ DOWN和ACTION_ _UP的事件,并且受长按事件影响,当长按事件返回true时,onClick不 会响应;
  • onLongClick在ACTION_ DOWN里判断是否进行响应,要想执行长按事件该View必须是longClickable的并且设置了OnLongClickListener.
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值