事件分发机制之 源码解析

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了事件分发机制之 源码解析相关的知识,希望对你有一定的参考价值。

事件的下发:dispatchTouchEvent

ViewGroup相关事件有三个:onInterceptTouchEvent、dispatchTouchEvent、onTouchEvent
View相关事件有两个:dispatchTouchEvent、onTouchEvent
简单来说就是:当一个Touch事件到达根节点时,它会依次【下发】,下发的过程是调用子View的dispatchTouchEvent方法实现的。

详细来说就是:ViewGroup遍历它包含着的【子View】,如果Touch事件在屏幕上的位置处于该子View的范围内,则调用此子View的dispatchTouchEvent方法;如果此子View为ViewGroup时,又会通过调用此子ViwGroup的dispatchTouchEvent方法继续调用其子View的dispatchTouchEvent方法;一旦发现某个View的dispatchTouchEvent方法返回值为true时,则下发过程结束。

注意:上面说的"结束"指的是"下发"过程结束,也即一旦某个View的dispatchTouchEvent方法返回值为true,则其子View就不可能会获取到任何Touch事件;但是此View的所有父View都可以获取到此View能获取到的任何事件,包括ACTION_DOWN、ACTION_UP 和ACTION_MOVE 

View mTarget=null;//记录捕获Touch事件处理的那个View
public boolean dispatchTouchEvent(MotionEvent ev) {
    if(ev.getAction()==KeyEvent.ACTION_DOWN){//当是DOWN事件时
        if(!onInterceptTouchEvent()){//如果没有拦截
        mTarget=null;//每次Down事件,都置为Null
        View[] views=getChildView();
        for(int i=0;i<views.length;i++){//逐个遍历所有子View
            if(...){ //判断Touch事件在屏幕上的位置是否在该子View的范围内……
               if(views[i].dispatchTouchEvent(ev))  mTarget=views[i];//判断该子View的dispatchTouchEvent方法是否返回true
                  return true;//如果返回true,则分发过程结束,且此ViewGroup的dispatchTouchEvent也返回true
      }
    }
    //当子View没有捕获down事件时,ViewGroup自身处理。这里处理的Touch事件包含Down、Up和Move
    if(mTarget==null) return super.dispatchTouchEvent(ev);
    ...
    if(onInterceptTouchEvent()){ }
    //这一步在Action_Down中是不会执行到的,只有Move和UP才会执行到。
    return mTarget.dispatchTouchEvent(ev);
}

    //View
    public boolean dispatchTouchEvent(MotionEvent event) {
        if (!onFilterTouchEventForSecurity(event)) return false;
        //如果mOnTouchListener不为null,并且view是enable的状态,并且mOnTouchListener的onTouch的返回值为true
        if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED 
&& mOnTouchListener.onTouch(this, event)) {
            return true;//返回true。也就是说,此时View自己的onTouchEvent方法就不会被执行了
        }
        return onTouchEvent(event);//否则,返回值完全由自己的onTouchEvent方法决定
    }

在此可以看出,【ViewGroup】的dispatchTouchEvent是真正在执行事件【分发】工作,而【View】的dispatchTouchEvent方法只是调用了自己的【onTouchEvent】方法,此方法决定了View是否要处理此touch事件。一般情况下,我们不该在普通View内重写dispatchTouchEvent方法,因为它并不执行分发逻辑,它仅仅是返回了onTouchEvent方法的返回值而已,我们只需重写onTouchEvent方法即可。

事件的上传:onTouchEvent

    public boolean onTouchEvent(MotionEvent event) {
        final int viewFlags = mViewFlags;
        if ((viewFlags & ENABLED_MASK) == DISABLED) {
            // A disabled view that is clickable still consumes the touch events, it just doesn‘t respond to them.
            // 如果当前View是Disabled状态且是可点击则会消费掉事件
            return (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
        }
         // 果设置了mTouchDelegate,则会将事件交给代理者处理,直接return true
        if (mTouchDelegate != null && mTouchDelegate.onTouchEvent(event))  return true;
         // 我们的View可以点击或者可以长按,则……注意if范围内,最终一定是 return true ;
        if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
            switch (event.getAction()) {
            case MotionEvent.ACTION_UP:
                boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;
                if ((mPrivateFlags & PRESSED) != 0 || prepressed) {
                    // take focus if we don‘t have it already and we should in touch mode.  
                    boolean focusTaken = false;
                    if (isFocusable() && isFocusableInTouchMode() && !isFocused())  focusTaken = requestFocus();
                    if (!mHasPerformedLongPress) {
                        // This is a tap, so remove the longpress check  
                        removeLongPressCallback();
                        // Only perform take click actions if we were in the pressed state  
                        if (!focusTaken) {
                            // Use a Runnable and post this rather than calling performClick directly. 
                            // This lets other visual state of the view update before click actions start.  
                            if (mPerformClick == null)  mPerformClick = new PerformClick();
                            if (!post(mPerformClick))  performClick();
                        }
                    }
                    if (mUnsetPressedState == null)  mUnsetPressedState = new UnsetPressedState();
                    if (prepressed) {
                        mPrivateFlags |= PRESSED;
                        refreshDrawableState();
                        postDelayed(mUnsetPressedState, ViewConfiguration.getPressedStateDuration());
                    } else if (!post(mUnsetPressedState)) {
                        // If the post failed, unpress right now  
                        mUnsetPressedState.run();
                    }
                    removeTapCallback();
                }
                break;
            case MotionEvent.ACTION_DOWN:
                if (mPendingCheckForTap == null)  mPendingCheckForTap = new CheckForTap();
                mPrivateFlags |= PREPRESSED;
                mHasPerformedLongPress = false;
                postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                break;
            case MotionEvent.ACTION_CANCEL:
                mPrivateFlags &= ~PRESSED;
                refreshDrawableState();
                removeTapCallback();
                break;
            case MotionEvent.ACTION_MOVE:
                final int x = (int) event.getX();
                final int y = (int) event.getY();
                // Be lenient about moving outside of buttons  
                int slop = mTouchSlop;
                if ((x < 0 - slop) || (x >= getWidth() + slop) || (y < 0 - slop) || (y >= getHeight() + slop)) {
                    // Outside button  判断当前触摸点有没有移出我们的View
                    removeTapCallback();
                    if ((mPrivateFlags & PRESSED) != 0) {
                        // Remove any future long press/tap checks  
                        removeLongPressCallback();
                        // Need to switch from pressed to not pressed  
                        mPrivateFlags &= ~PRESSED;
                        refreshDrawableState();
                    }
                }
                break;
            }
            return true;
        }
        return false;
    }

关于对ACTION_DOWN的处理

当用户按下触发ACTION_DOWN后,首先会设置标识为PREPRESSED(预压),如果115ms后没有抬起,会去掉PREPRESSED标识并将View的标识设置为PRESSED(压),然后发出一个检测长按的延迟任务,延时为:ViewConfiguration.getLongPressTimeout() - delayOffset(500ms -115ms),这个115ms刚好是检测PREPRESSED时间。

也就是用户从DOWN触发开始算起,如果500ms内没有抬起则认为触发了长按事件:
  • 如果此时设置了长按的回调,则执行长按时的回调,且如果长按的回调返回true才把mHasPerformedLongPress置为ture
  • 否则,如果没有设置长按回调或者长按回调返回的是false,则mHasPerformedLongPress依然是false

关于ACTION_MOVE的处理

首先拿到当前触摸的x,y坐标,然后判断当前触摸点有没有移出我们的View,如果移出了:
  • 1、执行removeTapCallback(),移除DOWN触发时设置的PREPRESSED的检测,即当前触发时机在DOWN触发不到115ms时,你就已经移出控件外了;
  • 2、然后判断是否包含PRESSED标识(如果115ms后才移出控件外,则包含),如果包含,调用removeLongPressCallback()移除长按的检查
  • 3、然后把mPrivateFlags中PRESSED标识去除
  • 4、最后刷新背景

简单说就是:只要用户移出了我们的控件,则将mPrivateFlags取出PRESSED标识,且移除所有在DOWN中设置的检测,长按等。

关于ACTION_UP的处理

如果mPrivateFlags包含PRESSED或者PREPRESSED则进入执行体,也就是无论是115ms内或者之后抬起都会进入执行体。
如果mHasPerformedLongPress没有被执行,则调用removeLongPressCallback()移除长按的检测;
如果mPerformClick为null则初始化一个实例,然后立即通过handler添加到消息队列尾部
  • 如果添加失败则直接执行 performClick()
  • 如果添加成功,则在mPerformClick的run方法中执行performClick()

下面看一下performClick()方法:
    public boolean performClick() {
        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
        if (mOnClickListener != null) {
            playSoundEffect(SoundEffectConstants.CLICK);
            mOnClickListener.onClick(this);
            return true;
        }
        return false;
    }
久违了~我们的mOnClickListener ;


如果prepressed为true,为mPrivateFlags设置表示为PRESSED,刷新背景,125毫秒后执行mUnsetPressedState.run()
否则mUnsetPressedState.run()立即执行;也就是不管咋样,最后mUnsetPressedState.run()都会执行;
看看这个UnsetPressedState主要干什么:
    private final class UnsetPressedState implements Runnable {
        public void run() {
            setPressed(false);
        }
    }
    public void setPressed(boolean pressed) {
        if (pressed)  mPrivateFlags |= PRESSED;
        else  mPrivateFlags &= ~PRESSED;
        refreshDrawableState();
        dispatchSetPressed(pressed);
    }
把我们的mPrivateFlags中的PRESSED取消,然后刷新背景,把setPress转发下去。

onTouch和onTouchEvent的区别

对于View,我们可以通过重写onTouchEvent方法来处理Touch事件,也可以通过实现OnTouchListener的接口,然后在onTouch方法中达到同样的目的,这两种监听有什么区别呢?

这两个方法都是在View的dispatchTouchEvent中调用的,onTouch优先于onTouchEvent执行。如果在onTouch方法中通过返回true将事件消费掉,onTouchEvent将不会再执行。

另外需要注意的是,onTouch能够得到执行需要两个前提条件,第一mOnTouchListener的值不能为空,第二当前点击的控件必须是enable的。因此如果你有一个控件是非enable的,那么给它注册onTouch事件将永远得不到执行。对于这一类控件,如果我们想要监听它的touch事件,就必须通过在该控件中重写onTouchEvent方法来实现。

总结
  • 1、onTouchListener的onTouch方法优先级比onTouchEvent方法高,会先触发。
  • 2、假如onTouch方法返回false会接着触发onTouchEvent方法,反之onTouchEvent方法不会被调用。
  • 3、内置诸如click事件的实现等等都基于onTouchEvent,假如onTouch返回true,这些事件将不会被触发。

以上是关于事件分发机制之 源码解析的主要内容,如果未能解决你的问题,请参考以下文章

Android事件分发机制完全解析,带你从源码的角度彻底理解

Android View体系从源码解析View的事件分发机制

Android事件分发机制完全解析,带你从源码的角度彻底理解(下)

安卓中的事件分发机制源码解析

事件分发机制完全解析_下

Android事件分发机制完全解析,带你从源码的角度彻底理解(上)