Android中的事件分发(上)

来源:互联网 发布:管道保温计算软件 编辑:程序博客网 时间:2024/06/10 08:43

不用我多说,相信大家都知道事件分发的重要性吧。它是Android知识体系中重要的一部分,当然对于初学Android的人来说是比较难的一部分,但是又不能不学,因为它是解决滑动冲突的理论基础,而且面试的时候面试官非常喜欢问这部分内容。
我不会单单的贴贴源码,我也不会单单的写个Demo打打Log,我也不会单单的写写结论。因为这篇文章里源码、log、结论都有……(哈哈开个玩笑),我觉得学习事件分发不能靠背背结论,看看别人写的流程图就算了,而是真的要看看源码写写Demo体会一下,这样才会有深刻的认识。
好了,废话就不说了,下面谈谈事件分发吧。本文主要谈View的事件分发。

我们先来写个栗子,就是给一个Button设置setOnTouchListener和setOnClickListener,其中onTouch是有返回值的,默认返回false。下面我们打一下log:
这里写图片描述
那么我们修改onTouch返回值为true后:
这里写图片描述
那么我们暂时可以得到两个小结论:

  • onTouch先于onClick被触发
  • 如果onTouch返回值为true,那么onClick便不会被触发

下面我们再写个栗子,写个MyButton类继承Button,重写dispatchTouchEvent和onTouchEvent方法。暂时并不加任何其它代码,只是打一下log:
onTouch返回值为true:
图1
onTouch返回值为false:
图2
如果修改dispatchTouchEvent返回值为false(这里onTouch返回true时):

@Overridepublic boolean dispatchTouchEvent(MotionEvent event){    Log.d("-------MyButton-------","----dispatchTouchEvent----"+event.getAction());    // return super.dispatchTouchEvent(event);    super.dispatchTouchEvent(event);    return false;}

这里写图片描述
我们可以看到当dispatchTouchEvent返回值为false时,Button已经接收不到move、up事件了。而是传递给了它的父控件。

好了两个栗子到此结束,等我们看完了源码再回来看栗子吧。。
View的事件分发涉及到dispatchTouchEvent和onTouchEvent两个方法,我们先看dispatchTouchEvent的源码:

public boolean dispatchTouchEvent(MotionEvent event){     //......省略    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;         }        if (!result && onTouchEvent(event)) {            result = true;        }    }    //.......省略        return result;    }

这里是dispatchTouchEvent的关键代码,我们看一下第二个if语句:

  • 条件一:只要我们对View设置了Listener,那么li!=null
  • 条件二:只要我们对控件设置了setOnTouchListener事件,则条件二为true
  • 条件三:该View是否是enabled
  • 条件四:onTouch的返回值,其实这里可以看做是先执行onTouch,再判断返回值

再看一下第三个if语句,如果没有进入第二个if语句并且onTouchEvent返回值为true,才会进入第三个if里面。
其实两个if语句里只有一句代码,就是修改了一个boolean类型变量值,我们可以把dispatchTouchEvent源码修改下更容易理解:

public boolean dispatchTouchEvent(MotionEvent event){     //......省略       if (li != null && li.mOnTouchListener != null&& (mViewFlags & ENABLED_MASK) == ENABLED&& li.mOnTouchListener.onTouch(this, event)) {            return true;         }    //.......省略        return onTouchEvent(event);    }

暂时可以得到下面几个结论:

  • 假设View设置了setOnTouchListener,如果onTouch返回值为true,那么onTouchEvent不会被调用;如果onTouch返回值为false,那么onTouchEvent会被调用,此时onTouch也是会被调用的,这点要注意。
  • 如果该View是disenabled的,那么onTouch不会被调用,但是onTouchEvent会被调用。
  • 注意分析dispatchTouchEvent的返回值,跟onTouch或者onTouchEvent有关系。

那么onClick是在哪里被调用的呢?别着急,我们先看看接着上面去看看onTouchEvent的源码,我们可以看到有个方法performClick(),其实是在这里面触发的onClick

public boolean performClick() {    final boolean result;    final ListenerInfo li = mListenerInfo;    if (li != null && li.mOnClickListener != null) {         playSoundEffect(SoundEffectConstants.CLICK);         li.mOnClickListener.onClick(this);         result = true;    } else {         result = false;    }   sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);    return result;}

现在我们对各个方法和结论再总结一下:

  • 同一个事件序列包括down事件、n个move事件、up事件。
  • 事件分发的顺序为dispatchTouchEvent–>onTouch–>onToucEvent
  • dispatchTouchEvent方法是控制事件分发的,返回值为true时表示中止down事件的分发,同事件序列的move、up事件会传递给该控件;返回值为false时表示不中止down事件的分发,这个事件该控件不要,同事件序列的move、up事件不会传递给该控件。
  • 当onTouchEvent被调用,它的返回值就是dispatchTouchEvent的返回值。

好了,View的事件分发大概就写这么多了,其实还有更多的结论,但是都可以通过源码分析出来,这里不过多介绍了。如有错误,欢迎指正。