<div id="article_details" class="details">
<div class="article_title">
<span class="ico ico_type_Original"></span>
<h1>
<span class="link_title"><a href="/guolin_blog/article/details/9097463">
Android事件分发机制完全解析,带你从源码的角度彻底理解(上)
</a></span>
</h1>
</div>
<div class="article_manage clearfix">
<div class="article_l">
<span class="link_categories">
标签:
<a href="http://www.csdn.net/tag/Android" target="_blank" οnclick="_gaq.push(['_trackEvent','function', 'onclick', 'blog_articles_tag']);">Android</a><a href="http://www.csdn.net/tag/%e4%ba%8b%e4%bb%b6%e5%88%86%e5%8f%91" target="_blank" οnclick="_gaq.push(['_trackEvent','function', 'onclick', 'blog_articles_tag']);">事件分发</a><a href="http://www.csdn.net/tag/onTouch" target="_blank" οnclick="_gaq.push(['_trackEvent','function', 'onclick', 'blog_articles_tag']);">onTouch</a><a href="http://www.csdn.net/tag/dispatchTouchEvent" target="_blank" οnclick="_gaq.push(['_trackEvent','function', 'onclick', 'blog_articles_tag']);">dispatchTouchEvent</a><a href="http://www.csdn.net/tag/onTouchEvent" target="_blank" οnclick="_gaq.push(['_trackEvent','function', 'onclick', 'blog_articles_tag']);">onTouchEvent</a>
</span>
</div>
<div class="article_r">
<span class="link_postdate">2013-06-20 08:30</span>
<span class="link_view" title="阅读次数">236048人阅读</span>
<span class="link_comments" title="评论次数"> <a href="#comments" οnclick="_gaq.push(['_trackEvent','function', 'onclick', 'blog_articles_pinglun'])">评论</a>(277)</span>
<span class="link_collect tracking-ad" data-mod="popu_171"> <a href="javascript:void(0);" οnclick="javascript:collectArticle('Android%e4%ba%8b%e4%bb%b6%e5%88%86%e5%8f%91%e6%9c%ba%e5%88%b6%e5%ae%8c%e5%85%a8%e8%a7%a3%e6%9e%90%ef%bc%8c%e5%b8%a6%e4%bd%a0%e4%bb%8e%e6%ba%90%e7%a0%81%e7%9a%84%e8%a7%92%e5%ba%a6%e5%bd%bb%e5%ba%95%e7%90%86%e8%a7%a3(%e4%b8%8a)','9097463');return false;" title="收藏" target="_blank">收藏</a></span>
<span class="link_report"> <a href="#report" οnclick="javascript:report(9097463,2);return false;" title="举报">举报</a></span>
</div>
</div>
<div class="embody" style="display:none" id="embody">
<span class="embody_t">本文章已收录于:</span>
<div class="embody_c" id="lib" value="{"err":0,"msg":"ok","data":[]}"></div>
</div>
<style type="text/css">
.embody{
padding:10px 10px 10px;
margin:0 -20px;
border-bottom:solid 1px #ededed;
}
.embody_b{
margin:0 ;
padding:10px 0;
}
.embody .embody_t,.embody .embody_c{
display: inline-block;
margin-right:10px;
}
.embody_t{
font-size: 12px;
color:#999;
}
.embody_c{
font-size: 12px;
}
.embody_c img,.embody_c em{
display: inline-block;
vertical-align: middle;
}
.embody_c img{
width:30px;
height:30px;
}
.embody_c em{
margin: 0 20px 0 10px;
color:#333;
font-style: normal;
}
</style>
<script type="text/javascript">
$(function () {
try
{
var lib = eval("("+$("#lib").attr("value")+")");
var html = "";
if (lib.err == 0) {
$.each(lib.data, function (i) {
var obj = lib.data[i];
//html += '<img src="' + obj.logo + '"/>' + obj.name + " ";
html += ' <a href="' + obj.url + '" target="_blank">';
html += ' <img src="' + obj.logo + '">';
html += ' <em><b>' + obj.name + '</b></em>';
html += ' </a>';
});
if (html != "") {
setTimeout(function () {
$("#lib").html(html);
$("#embody").show();
}, 100);
}
}
} catch (err)
{ }
});
</script>
<div class="category clearfix">
<div class="category_l">
<img src="http://static.blog.csdn.net/images/category_icon.jpg">
<span>分类:</span>
</div>
<div class="category_r">
<label οnclick="GetCategoryArticles('1399638','sinyu890807','top','9097463');">
<span οnclick="_gaq.push(['_trackEvent','function', 'onclick', 'blog_articles_fenlei']);">Android疑难解析<em>(35)</em></span>
<img class="arrow-down" src="http://static.blog.csdn.net/images/arrow_triangle _down.jpg" style="display:inline;">
<img class="arrow-up" src="http://static.blog.csdn.net/images/arrow_triangle_up.jpg" style="display:none;">
<div class="subItem">
<div class="subItem_t"><a href="http://blog.csdn.net/sinyu890807/article/category/1399638" target="_blank">作者同类文章</a><i class="J_close">X</i></div>
<ul class="subItem_l" id="top_1399638">
</ul>
</div>
</label>
</div>
</div>
<script type="text/javascript" src="http://static.blog.csdn.net/scripts/category.js"></script>
<div class="bog_copyright">
<p class="copyright_p">版权声明:本文出自郭霖的博客,转载必须注明出处。</p>
</div>
<div id="article_content" class="article_content">
<p>转载请注明出处:<a target="_blank" href="http://blog.csdn.net/sinyu890807/article/details/8986285"></a><a target="_blank" href="http://blog.csdn.net/sinyu890807/article/details/9097463"></a><a target="_blank" href="http://blog.csdn.net/guolin_blog/article/details/9097463">http://blog.csdn.net/guolin_blog/article/details/9097463</a><br></p><p><br></p><p>其实我一直准备写一篇关于<a href="http://lib.csdn.net/base/android" class="replace_word" title="Android知识库" target="_blank" style="color:#df3434; font-weight:bold;">Android</a>事件分发机制的文章,从我的第一篇博客开始,就零零散散在好多地方使用到了Android事件分发的知识。也有好多朋友问过我各种问题,比如:onTouch和onTouchEvent有什么区别,又该如何使用?为什么给ListView引入了一个滑动菜单的功能,ListView就不能滚动了?为什么图片轮播器里的图片使用Button而不用ImageView?等等……对于这些问题,我并没有给出非常详细的回答,因为我知道如果想要彻底搞明白这些问题,掌握Android事件分发机制是必不可少的,而Android事件分发机制绝对不是三言两语就能说得清的。</p><p><br></p><p>在我经过较长时间的筹备之后,终于决定开始写这样一篇文章了。目前虽然网上相关的文章也不少,但我觉得没有哪篇写得特别详细的(也许我还没有找到),多数文章只是讲了讲理论,然后配合demo运行了一下结果。而我准备带着大家从源码的角度进行分析,相信大家可以更加深刻地理解Android事件分发机制。<br></p><p><br></p><p>阅读源码讲究由浅入深,循序渐进,因此我们也从简单的开始,本篇先带大家探究View的事件分发,下篇再去探究难度更高的ViewGroup的事件分发。<br></p><p><br></p><p>那我们现在就开始吧!比如说你当前有一个非常简单的项目,只有一个Activity,并且Activity中只有一个按钮。你可能已经知道,如果想要给这个按钮注册一个点击事件,只需要调用:</p><div class="dp-highlighter bg_java"><div class="bar"><div class="tools"><b>[java]</b> <a href="#" class="ViewSource" title="view plain" οnclick="dp.sh.Toolbar.Command('ViewSource',this);return false;">view plain</a><span data-mod="popu_168"> <a href="#" class="CopyToClipboard" title="copy" οnclick="dp.sh.Toolbar.Command('CopyToClipboard',this);return false;">copy</a><div style="position: absolute; left: 659px; top: 1029px; width: 18px; height: 18px; z-index: 99;"><embed id="ZeroClipboardMovie_1" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" loop="false" menu="false" quality="best" bgcolor="#ffffff" width="18" height="18" name="ZeroClipboardMovie_1" align="middle" allowscriptaccess="always" allowfullscreen="false" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer" flashvars="id=1&width=18&height=18" wmode="transparent"></div></span><span data-mod="popu_169"> <a href="#" class="PrintSource" title="print" οnclick="dp.sh.Toolbar.Command('PrintSource',this);return false;">print</a></span><a href="#" class="About" title="?" οnclick="dp.sh.Toolbar.Command('About',this);return false;">?</a></div></div><ol start="1" class="dp-j"><li class="alt"><span><span>button.setOnClickListener(</span><span class="keyword">new</span><span> OnClickListener() { </span></span></li><li class=""><span> <span class="annotation">@Override</span><span> </span></span></li><li class="alt"><span> <span class="keyword">public</span><span> </span><span class="keyword">void</span><span> onClick(View v) { </span></span></li><li class=""><span> Log.d(<span class="string">"TAG"</span><span>, </span><span class="string">"onClick execute"</span><span>); </span></span></li><li class="alt"><span> } </span></li><li class=""><span>}); </span></li></ol><div class="save_code tracking-ad" data-mod="popu_249" style="display: none;"><a href="javascript:;" target="_blank"><img src="http://static.blog.csdn.net/images/save_snippets.png"></a></div></div><pre name="code" class="java" style="display: none;">button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Log.d("TAG", "onClick execute");
}
});</pre>这样在onClick方法里面写实现,就可以在按钮被点击的时候执行。你可能也已经知道,如果想给这个按钮再添加一个touch事件,只需要调用:<div class="dp-highlighter bg_java"><div class="bar"><div class="tools"><b>[java]</b> <a href="#" class="ViewSource" title="view plain" οnclick="dp.sh.Toolbar.Command('ViewSource',this);return false;">view plain</a><span data-mod="popu_168"> <a href="#" class="CopyToClipboard" title="copy" οnclick="dp.sh.Toolbar.Command('CopyToClipboard',this);return false;">copy</a><div style="position: absolute; left: 659px; top: 1270px; width: 18px; height: 18px; z-index: 99;"><embed id="ZeroClipboardMovie_2" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" loop="false" menu="false" quality="best" bgcolor="#ffffff" width="18" height="18" name="ZeroClipboardMovie_2" align="middle" allowscriptaccess="always" allowfullscreen="false" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer" flashvars="id=2&width=18&height=18" wmode="transparent"></div></span><span data-mod="popu_169"> <a href="#" class="PrintSource" title="print" οnclick="dp.sh.Toolbar.Command('PrintSource',this);return false;">print</a></span><a href="#" class="About" title="?" οnclick="dp.sh.Toolbar.Command('About',this);return false;">?</a></div></div><ol start="1" class="dp-j"><li class="alt"><span><span>button.setOnTouchListener(</span><span class="keyword">new</span><span> OnTouchListener() { </span></span></li><li class=""><span> <span class="annotation">@Override</span><span> </span></span></li><li class="alt"><span> <span class="keyword">public</span><span> </span><span class="keyword">boolean</span><span> onTouch(View v, MotionEvent event) { </span></span></li><li class=""><span> Log.d(<span class="string">"TAG"</span><span>, </span><span class="string">"onTouch execute, action "</span><span> + event.getAction()); </span></span></li><li class="alt"><span> <span class="keyword">return</span><span> </span><span class="keyword">false</span><span>; </span></span></li><li class=""><span> } </span></li><li class="alt"><span>}); </span></li></ol><div class="save_code tracking-ad" data-mod="popu_249" style="display: none;"><a href="javascript:;" target="_blank"><img src="http://static.blog.csdn.net/images/save_snippets.png"></a></div></div><pre name="code" class="java" style="display: none;">button.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.d("TAG", "onTouch execute, action " + event.getAction());
return false;
}
});</pre><p>onTouch方法里能做的事情比onClick要多一些,比如判断手指按下、抬起、移动等事件。那么如果我两个事件都注册了,哪一个会先执行呢?我们来试一下就知道了,运行程序点击按钮,打印结果如下:</p><p><br></p><p style="text-align: center;"><img src="https://img-blog.csdn.net/20130614230234671" alt=""><br></p><p style="text-align: center;"><br></p><p>可以看到,onTouch是优先于onClick执行的,并且onTouch执行了两次,一次是ACTION_DOWN,一次是ACTION_UP(你还可能会有多次ACTION_MOVE的执行,如果你手抖了一下)。因此事件传递的顺序是先经过onTouch,再传递到onClick。</p><p><br></p><p>细心的朋友应该可以注意到,onTouch方法是有返回值的,这里我们返回的是false,如果我们尝试把onTouch方法里的返回值改成true,再运行一次,结果如下:</p><p><br></p><p style="text-align: center;"><img src="https://img-blog.csdn.net/20130614231001468" alt=""><br></p><p style="text-align: center;"><br></p><p>我们发现,onClick方法不再执行了!为什么会这样呢?你可以先理解成onTouch方法返回true就认为这个事件被onTouch消费掉了,因而不会再继续向下传递。</p><p><br></p><p>如果到现在为止,以上的所有知识点你都是清楚的,那么说明你对Android事件传递的基本用法应该是掌握了。不过别满足于现状,让我们从源码的角度分析一下,出现上述现象的原理是什么。</p><p><br></p><p>首先你需要知道一点,只要你触摸到了任何一个控件,就一定会调用该控件的dispatchTouchEvent方法。那当我们去点击按钮的时候,就会去调用Button类里的dispatchTouchEvent方法,可是你会发现Button类里并没有这个方法,那么就到它的父类TextView里去找一找,你会发现TextView里也没有这个方法,那没办法了,只好继续在TextView的父类View里找一找,这个时候你终于在View里找到了这个方法,示意图如下:</p><p><br></p><p style="text-align: center;"><img src="https://img-blog.csdn.net/20130616154134500" alt=""><br></p><p style="text-align: center;"><br></p><p>然后我们来看一下View中dispatchTouchEvent方法的源码:</p><div class="dp-highlighter bg_java"><div class="bar"><div class="tools"><b>[java]</b> <a href="#" class="ViewSource" title="view plain" οnclick="dp.sh.Toolbar.Command('ViewSource',this);return false;">view plain</a><span data-mod="popu_168"> <a href="#" class="CopyToClipboard" title="copy" οnclick="dp.sh.Toolbar.Command('CopyToClipboard',this);return false;">copy</a><div style="position: absolute; left: 659px; top: 2806px; width: 18px; height: 18px; z-index: 99;"><embed id="ZeroClipboardMovie_3" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" loop="false" menu="false" quality="best" bgcolor="#ffffff" width="18" height="18" name="ZeroClipboardMovie_3" align="middle" allowscriptaccess="always" allowfullscreen="false" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer" flashvars="id=3&width=18&height=18" wmode="transparent"></div></span><span data-mod="popu_169"> <a href="#" class="PrintSource" title="print" οnclick="dp.sh.Toolbar.Command('PrintSource',this);return false;">print</a></span><a href="#" class="About" title="?" οnclick="dp.sh.Toolbar.Command('About',this);return false;">?</a></div></div><ol start="1" class="dp-j"><li class="alt"><span><span class="keyword">public</span><span> </span><span class="keyword">boolean</span><span> dispatchTouchEvent(MotionEvent event) { </span></span></li><li class=""><span> <span class="keyword">if</span><span> (mOnTouchListener != </span><span class="keyword">null</span><span> && (mViewFlags & ENABLED_MASK) == ENABLED && </span></span></li><li class="alt"><span> mOnTouchListener.onTouch(<span class="keyword">this</span><span>, event)) { </span></span></li><li class=""><span> <span class="keyword">return</span><span> </span><span class="keyword">true</span><span>; </span></span></li><li class="alt"><span> } </span></li><li class=""><span> <span class="keyword">return</span><span> onTouchEvent(event); </span></span></li><li class="alt"><span>} </span></li></ol><div class="save_code tracking-ad" data-mod="popu_249" style="display: none;"><a href="javascript:;" target="_blank"><img src="http://static.blog.csdn.net/images/save_snippets.png"></a></div></div><pre name="code" class="java" style="display: none;">public boolean dispatchTouchEvent(MotionEvent event) {
if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
mOnTouchListener.onTouch(this, event)) {
return true;
}
return onTouchEvent(event);
}</pre><p>这个方法非常的简洁,只有短短几行代码!我们可以看到,在这个方法内,首先是进行了一个判断,如果mOnTouchListener != null,(mViewFlags & ENABLED_MASK) == ENABLED和mOnTouchListener.onTouch(this, event)这三个条件都为真,就返回true,否则就去执行onTouchEvent(event)方法并返回。</p><p><br></p><p>先看一下第一个条件,mOnTouchListener这个变量是在哪里赋值的呢?我们寻找之后在View里发现了如下方法:</p><div class="dp-highlighter bg_java"><div class="bar"><div class="tools"><b>[java]</b> <a href="#" class="ViewSource" title="view plain" οnclick="dp.sh.Toolbar.Command('ViewSource',this);return false;">view plain</a><span data-mod="popu_168"> <a href="#" class="CopyToClipboard" title="copy" οnclick="dp.sh.Toolbar.Command('CopyToClipboard',this);return false;">copy</a><div style="position: absolute; left: 659px; top: 3168px; width: 18px; height: 18px; z-index: 99;"><embed id="ZeroClipboardMovie_4" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" loop="false" menu="false" quality="best" bgcolor="#ffffff" width="18" height="18" name="ZeroClipboardMovie_4" align="middle" allowscriptaccess="always" allowfullscreen="false" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer" flashvars="id=4&width=18&height=18" wmode="transparent"></div></span><span data-mod="popu_169"> <a href="#" class="PrintSource" title="print" οnclick="dp.sh.Toolbar.Command('PrintSource',this);return false;">print</a></span><a href="#" class="About" title="?" οnclick="dp.sh.Toolbar.Command('About',this);return false;">?</a></div></div><ol start="1" class="dp-j"><li class="alt"><span><span class="keyword">public</span><span> </span><span class="keyword">void</span><span> setOnTouchListener(OnTouchListener l) { </span></span></li><li class=""><span> mOnTouchListener = l; </span></li><li class="alt"><span>} </span></li></ol><div class="save_code tracking-ad" data-mod="popu_249" style="display: none;"><a href="javascript:;" target="_blank"><img src="http://static.blog.csdn.net/images/save_snippets.png"></a></div></div><pre name="code" class="java" style="display: none;">public void setOnTouchListener(OnTouchListener l) {
mOnTouchListener = l;
}</pre><p>Bingo!找到了,mOnTouchListener正是在setOnTouchListener方法里赋值的,也就是说只要我们给控件注册了touch事件,mOnTouchListener就一定被赋值了。</p><p><br></p><p>第二个条件(mViewFlags & ENABLED_MASK) == ENABLED是判断当前点击的控件是否是enable的,按钮默认都是enable的,因此这个条件恒定为true。</p><p><br></p><p>第三个条件就比较关键了,mOnTouchListener.onTouch(this, event),其实也就是去回调控件注册touch事件时的onTouch方法。也就是说如果我们在onTouch方法里返回true,就会让这三个条件全部成立,从而整个方法直接返回true。如果我们在onTouch方法里返回false,就会再去执行onTouchEvent(event)方法。</p><p><br></p><p>现在我们可以结合前面的例子来分析一下了,首先在dispatchTouchEvent中最先执行的就是onTouch方法,因此onTouch肯定是要优先于onClick执行的,也是印证了刚刚的打印结果。而如果在onTouch方法里返回了true,就会让dispatchTouchEvent方法直接返回true,不会再继续往下执行。而打印结果也证实了如果onTouch返回true,onClick就不会再执行了。</p><p><br></p><p>根据以上源码的分析,从原理上解释了我们前面例子的运行结果。而上面的分析还透漏出了一个重要的信息,那就是onClick的调用肯定是在onTouchEvent(event)方法中的!那我们马上来看下onTouchEvent的源码,如下所示:</p><div class="dp-highlighter bg_java"><div class="bar"><div class="tools"><b>[java]</b> <a href="#" class="ViewSource" title="view plain" οnclick="dp.sh.Toolbar.Command('ViewSource',this);return false;">view plain</a><span data-mod="popu_168"> <a href="#" class="CopyToClipboard" title="copy" οnclick="dp.sh.Toolbar.Command('CopyToClipboard',this);return false;">copy</a><div style="position: absolute; left: 659px; top: 3840px; width: 18px; height: 18px; z-index: 99;"><embed id="ZeroClipboardMovie_5" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" loop="false" menu="false" quality="best" bgcolor="#ffffff" width="18" height="18" name="ZeroClipboardMovie_5" align="middle" allowscriptaccess="always" allowfullscreen="false" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer" flashvars="id=5&width=18&height=18" wmode="transparent"></div></span><span data-mod="popu_169"> <a href="#" class="PrintSource" title="print" οnclick="dp.sh.Toolbar.Command('PrintSource',this);return false;">print</a></span><a href="#" class="About" title="?" οnclick="dp.sh.Toolbar.Command('About',this);return false;">?</a></div></div><ol start="1" class="dp-j"><li class="alt"><span><span class="keyword">public</span><span> </span><span class="keyword">boolean</span><span> onTouchEvent(MotionEvent event) { </span></span></li><li class=""><span> <span class="keyword">final</span><span> </span><span class="keyword">int</span><span> viewFlags = mViewFlags; </span></span></li><li class="alt"><span> <span class="keyword">if</span><span> ((viewFlags & ENABLED_MASK) == DISABLED) { </span></span></li><li class=""><span> <span class="comment">// A disabled view that is clickable still consumes the touch</span><span> </span></span></li><li class="alt"><span> <span class="comment">// events, it just doesn't respond to them.</span><span> </span></span></li><li class=""><span> <span class="keyword">return</span><span> (((viewFlags & CLICKABLE) == CLICKABLE || </span></span></li><li class="alt"><span> (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)); </span></li><li class=""><span> } </span></li><li class="alt"><span> <span class="keyword">if</span><span> (mTouchDelegate != </span><span class="keyword">null</span><span>) { </span></span></li><li class=""><span> <span class="keyword">if</span><span> (mTouchDelegate.onTouchEvent(event)) { </span></span></li><li class="alt"><span> <span class="keyword">return</span><span> </span><span class="keyword">true</span><span>; </span></span></li><li class=""><span> } </span></li><li class="alt"><span> } </span></li><li class=""><span> <span class="keyword">if</span><span> (((viewFlags & CLICKABLE) == CLICKABLE || </span></span></li><li class="alt"><span> (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) { </span></li><li class=""><span> <span class="keyword">switch</span><span> (event.getAction()) { </span></span></li><li class="alt"><span> <span class="keyword">case</span><span> MotionEvent.ACTION_UP: </span></span></li><li class=""><span> <span class="keyword">boolean</span><span> prepressed = (mPrivateFlags & PREPRESSED) != </span><span class="number">0</span><span>; </span></span></li><li class="alt"><span> <span class="keyword">if</span><span> ((mPrivateFlags & PRESSED) != </span><span class="number">0</span><span> || prepressed) { </span></span></li><li class=""><span> <span class="comment">// take focus if we don't have it already and we should in</span><span> </span></span></li><li class="alt"><span> <span class="comment">// touch mode.</span><span> </span></span></li><li class=""><span> <span class="keyword">boolean</span><span> focusTaken = </span><span class="keyword">false</span><span>; </span></span></li><li class="alt"><span> <span class="keyword">if</span><span> (isFocusable() && isFocusableInTouchMode() && !isFocused()) { </span></span></li><li class=""><span> focusTaken = requestFocus(); </span></li><li class="alt"><span> } </span></li><li class=""><span> <span class="keyword">if</span><span> (!mHasPerformedLongPress) { </span></span></li><li class="alt"><span> <span class="comment">// This is a tap, so remove the longpress check</span><span> </span></span></li><li class=""><span> removeLongPressCallback(); </span></li><li class="alt"><span> <span class="comment">// Only perform take click actions if we were in the pressed state</span><span> </span></span></li><li class=""><span> <span class="keyword">if</span><span> (!focusTaken) { </span></span></li><li class="alt"><span> <span class="comment">// Use a Runnable and post this rather than calling</span><span> </span></span></li><li class=""><span> <span class="comment">// performClick directly. This lets other visual state</span><span> </span></span></li><li class="alt"><span> <span class="comment">// of the view update before click actions start.</span><span> </span></span></li><li class=""><span> <span class="keyword">if</span><span> (mPerformClick == </span><span class="keyword">null</span><span>) { </span></span></li><li class="alt"><span> mPerformClick = <span class="keyword">new</span><span> PerformClick(); </span></span></li><li class=""><span> } </span></li><li class="alt"><span> <span class="keyword">if</span><span> (!post(mPerformClick)) { </span></span></li><li class=""><span> performClick(); </span></li><li class="alt"><span> } </span></li><li class=""><span> } </span></li><li class="alt"><span> } </span></li><li class=""><span> <span class="keyword">if</span><span> (mUnsetPressedState == </span><span class="keyword">null</span><span>) { </span></span></li><li class="alt"><span> mUnsetPressedState = <span class="keyword">new</span><span> UnsetPressedState(); </span></span></li><li class=""><span> } </span></li><li class="alt"><span> <span class="keyword">if</span><span> (prepressed) { </span></span></li><li class=""><span> mPrivateFlags |= PRESSED; </span></li><li class="alt"><span> refreshDrawableState(); </span></li><li class=""><span> postDelayed(mUnsetPressedState, </span></li><li class="alt"><span> ViewConfiguration.getPressedStateDuration()); </span></li><li class=""><span> } <span class="keyword">else</span><span> </span><span class="keyword">if</span><span> (!post(mUnsetPressedState)) { </span></span></li><li class="alt"><span> <span class="comment">// If the post failed, unpress right now</span><span> </span></span></li><li class=""><span> mUnsetPressedState.run(); </span></li><li class="alt"><span> } </span></li><li class=""><span> removeTapCallback(); </span></li><li class="alt"><span> } </span></li><li class=""><span> <span class="keyword">break</span><span>; </span></span></li><li class="alt"><span> <span class="keyword">case</span><span> MotionEvent.ACTION_DOWN: </span></span></li><li class=""><span> <span class="keyword">if</span><span> (mPendingCheckForTap == </span><span class="keyword">null</span><span>) { </span></span></li><li class="alt"><span> mPendingCheckForTap = <span class="keyword">new</span><span> CheckForTap(); </span></span></li><li class=""><span> } </span></li><li class="alt"><span> mPrivateFlags |= PREPRESSED; </span></li><li class=""><span> mHasPerformedLongPress = <span class="keyword">false</span><span>; </span></span></li><li class="alt"><span> postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout()); </span></li><li class=""><span> <span class="keyword">break</span><span>; </span></span></li><li class="alt"><span> <span class="keyword">case</span><span> MotionEvent.ACTION_CANCEL: </span></span></li><li class=""><span> mPrivateFlags &= ~PRESSED; </span></li><li class="alt"><span> refreshDrawableState(); </span></li><li class=""><span> removeTapCallback(); </span></li><li class="alt"><span> <span class="keyword">break</span><span>; </span></span></li><li class=""><span> <span class="keyword">case</span><span> MotionEvent.ACTION_MOVE: </span></span></li><li class="alt"><span> <span class="keyword">final</span><span> </span><span class="keyword">int</span><span> x = (</span><span class="keyword">int</span><span>) event.getX(); </span></span></li><li class=""><span> <span class="keyword">final</span><span> </span><span class="keyword">int</span><span> y = (</span><span class="keyword">int</span><span>) event.getY(); </span></span></li><li class="alt"><span> <span class="comment">// Be lenient about moving outside of buttons</span><span> </span></span></li><li class=""><span> <span class="keyword">int</span><span> slop = mTouchSlop; </span></span></li><li class="alt"><span> <span class="keyword">if</span><span> ((x < </span><span class="number">0</span><span> - slop) || (x >= getWidth() + slop) || </span></span></li><li class=""><span> (y < <span class="number">0</span><span> - slop) || (y >= getHeight() + slop)) { </span></span></li><li class="alt"><span> <span class="comment">// Outside button</span><span> </span></span></li><li class=""><span> removeTapCallback(); </span></li><li class="alt"><span> <span class="keyword">if</span><span> ((mPrivateFlags & PRESSED) != </span><span class="number">0</span><span>) { </span></span></li><li class=""><span> <span class="comment">// Remove any future long press/tap checks</span><span> </span></span></li><li class="alt"><span> removeLongPressCallback(); </span></li><li class=""><span> <span class="comment">// Need to switch from pressed to not pressed</span><span> </span></span></li><li class="alt"><span> mPrivateFlags &= ~PRESSED; </span></li><li class=""><span> refreshDrawableState(); </span></li><li class="alt"><span> } </span></li><li class=""><span> } </span></li><li class="alt"><span> <span class="keyword">break</span><span>; </span></span></li><li class=""><span> } </span></li><li class="alt"><span> <span class="keyword">return</span><span> </span><span class="keyword">true</span><span>; </span></span></li><li class=""><span> } </span></li><li class="alt"><span> <span class="keyword">return</span><span> </span><span class="keyword">false</span><span>; </span></span></li><li class=""><span>} </span></li></ol><div class="save_code tracking-ad" data-mod="popu_249" style="display: none;"><a href="javascript:;" target="_blank"><img src="http://static.blog.csdn.net/images/save_snippets.png"></a></div></div><pre name="code" class="java" style="display: none;">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.
return (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
}
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
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
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;
}
</pre><p>相较于刚才的dispatchTouchEvent方法,onTouchEvent方法复杂了很多,不过没关系,我们只挑重点看就可以了。</p><p><br></p><p>首先在第14行我们可以看出,如果该控件是可以点击的就会进入到第16行的switch判断中去,而如果当前的事件是抬起手指,则会进入到MotionEvent.ACTION_UP这个case当中。在经过种种判断之后,会执行到第38行<span style="font-family: Consolas, "Courier New", Courier, mono, serif;"><span style="line-height:18px">的performClick()方法,那我们进入到这个方法里瞧一瞧:</span></span></p><div class="dp-highlighter bg_java"><div class="bar"><div class="tools"><b>[java]</b> <a href="#" class="ViewSource" title="view plain" οnclick="dp.sh.Toolbar.Command('ViewSource',this);return false;">view plain</a><span data-mod="popu_168"> <a href="#" class="CopyToClipboard" title="copy" οnclick="dp.sh.Toolbar.Command('CopyToClipboard',this);return false;">copy</a><div style="position: absolute; left: 659px; top: 5637px; width: 18px; height: 18px; z-index: 99;"><embed id="ZeroClipboardMovie_6" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" loop="false" menu="false" quality="best" bgcolor="#ffffff" width="18" height="18" name="ZeroClipboardMovie_6" align="middle" allowscriptaccess="always" allowfullscreen="false" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer" flashvars="id=6&width=18&height=18" wmode="transparent"></div></span><span data-mod="popu_169"> <a href="#" class="PrintSource" title="print" οnclick="dp.sh.Toolbar.Command('PrintSource',this);return false;">print</a></span><a href="#" class="About" title="?" οnclick="dp.sh.Toolbar.Command('About',this);return false;">?</a></div></div><ol start="1" class="dp-j"><li class="alt"><span><span class="keyword">public</span><span> </span><span class="keyword">boolean</span><span> performClick() { </span></span></li><li class=""><span> sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); </span></li><li class="alt"><span> <span class="keyword">if</span><span> (mOnClickListener != </span><span class="keyword">null</span><span>) { </span></span></li><li class=""><span> playSoundEffect(SoundEffectConstants.CLICK); </span></li><li class="alt"><span> mOnClickListener.onClick(<span class="keyword">this</span><span>); </span></span></li><li class=""><span> <span class="keyword">return</span><span> </span><span class="keyword">true</span><span>; </span></span></li><li class="alt"><span> } </span></li><li class=""><span> <span class="keyword">return</span><span> </span><span class="keyword">false</span><span>; </span></span></li><li class="alt"><span>} </span></li></ol><div class="save_code tracking-ad" data-mod="popu_249" style="display: none;"><a href="javascript:;" target="_blank"><img src="http://static.blog.csdn.net/images/save_snippets.png"></a></div></div><pre name="code" class="java" style="display: none;">public boolean performClick() {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
if (mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
mOnClickListener.onClick(this);
return true;
}
return false;
}</pre>可以看到,只要mOnClickListener不是null,就会去调用它的onClick方法,那mOnClickListener又是在哪里赋值的呢?经过寻找后找到如下方法:<div class="dp-highlighter bg_java"><div class="bar"><div class="tools"><b>[java]</b> <a href="#" class="ViewSource" title="view plain" οnclick="dp.sh.Toolbar.Command('ViewSource',this);return false;">view plain</a><span data-mod="popu_168"> <a href="#" class="CopyToClipboard" title="copy" οnclick="dp.sh.Toolbar.Command('CopyToClipboard',this);return false;">copy</a><div style="position: absolute; left: 659px; top: 5929px; width: 18px; height: 18px; z-index: 99;"><embed id="ZeroClipboardMovie_7" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" loop="false" menu="false" quality="best" bgcolor="#ffffff" width="18" height="18" name="ZeroClipboardMovie_7" align="middle" allowscriptaccess="always" allowfullscreen="false" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer" flashvars="id=7&width=18&height=18" wmode="transparent"></div></span><span data-mod="popu_169"> <a href="#" class="PrintSource" title="print" οnclick="dp.sh.Toolbar.Command('PrintSource',this);return false;">print</a></span><a href="#" class="About" title="?" οnclick="dp.sh.Toolbar.Command('About',this);return false;">?</a></div></div><ol start="1" class="dp-j"><li class="alt"><span><span class="keyword">public</span><span> </span><span class="keyword">void</span><span> setOnClickListener(OnClickListener l) { </span></span></li><li class=""><span> <span class="keyword">if</span><span> (!isClickable()) { </span></span></li><li class="alt"><span> setClickable(<span class="keyword">true</span><span>); </span></span></li><li class=""><span> } </span></li><li class="alt"><span> mOnClickListener = l; </span></li><li class=""><span>} </span></li></ol><div class="save_code tracking-ad" data-mod="popu_249" style="display: none;"><a href="javascript:;" target="_blank"><img src="http://static.blog.csdn.net/images/save_snippets.png"></a></div></div><pre name="code" class="java" style="display: none;">public void setOnClickListener(OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
mOnClickListener = l;
}</pre><p>一切都是那么清楚了!当我们通过调用setOnClickListener方法来给控件注册一个点击事件时,就会给mOnClickListener赋值。然后每当控件被点击时,都会在performClick()方法里回调被点击控件的onClick方法。</p><p><span style="font-family:Consolas,'Courier New',Courier,mono,serif; line-height:18px"><br></span></p><p>这样View的整个事件分发的流程就让我们搞清楚了!不过别高兴的太早,现在还没结束,还有一个很重要的知识点需要说明,就是touch事件的层级传递。我们都知道如果给一个控件注册了touch事件,每次点击它的时候都会触发一系列的ACTION_DOWN,ACTION_MOVE,ACTION_UP等事件。这里需要注意,如果你在执行ACTION_DOWN的时候返回了false,后面一系列其它的action就不会再得到执行了。简单的说,就是当dispatchTouchEvent在进行事件分发的时候,只有前一个action返回true,才会触发后一个action。</p><p><span style="font-family: Consolas, "Courier New", Courier, mono, serif; line-height: 18px;"><br></span></p><p>说到这里,很多的朋友肯定要有巨大的疑问了。这不是在自相矛盾吗?前面的例子中,明明在onTouch事件里面返回了false,ACTION_DOWN和ACTION_UP不是都得到执行了吗?其实你只是被假象所迷惑了,让我们仔细分析一下,在前面的例子当中,我们到底返回的是什么。</p><p>参考着我们前面分析的源码,首先在onTouch事件里返回了false,就一定会进入到onTouchEvent方法中,然后我们来看一下onTouchEvent方法的细节。由于我们点击了按钮,就会进入到第14行这个if判断的内部,然后你会发现,不管当前的action是什么,最终都一定会走到第89行,返回一个true。</p><p><br></p><p>是不是有一种被欺骗的感觉?明明在onTouch事件里返回了false,系统还是在onTouchEvent方法中帮你返回了true。就因为这个原因,才使得前面的例子中ACTION_UP可以得到执行。</p><p><span style="font-family:Consolas,Courier New,Courier,mono,serif"><span style="line-height:18px"><span style="font-family:Consolas,'Courier New',Courier,mono,serif; line-height:18px"><br></span></span></span></p><p>那我们可以换一个控件,将按钮替换成ImageView,然后给它也注册一个touch事件,并返回false。如下所示:</p><div class="dp-highlighter bg_java"><div class="bar"><div class="tools"><b>[java]</b> <a href="#" class="ViewSource" title="view plain" οnclick="dp.sh.Toolbar.Command('ViewSource',this);return false;">view plain</a><span data-mod="popu_168"> <a href="#" class="CopyToClipboard" title="copy" οnclick="dp.sh.Toolbar.Command('CopyToClipboard',this);return false;">copy</a><div style="position: absolute; left: 659px; top: 6671px; width: 18px; height: 18px; z-index: 99;"><embed id="ZeroClipboardMovie_8" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" loop="false" menu="false" quality="best" bgcolor="#ffffff" width="18" height="18" name="ZeroClipboardMovie_8" align="middle" allowscriptaccess="always" allowfullscreen="false" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer" flashvars="id=8&width=18&height=18" wmode="transparent"></div></span><span data-mod="popu_169"> <a href="#" class="PrintSource" title="print" οnclick="dp.sh.Toolbar.Command('PrintSource',this);return false;">print</a></span><a href="#" class="About" title="?" οnclick="dp.sh.Toolbar.Command('About',this);return false;">?</a></div></div><ol start="1" class="dp-j"><li class="alt"><span><span>imageView.setOnTouchListener(</span><span class="keyword">new</span><span> OnTouchListener() { </span></span></li><li class=""><span> <span class="annotation">@Override</span><span> </span></span></li><li class="alt"><span> <span class="keyword">public</span><span> </span><span class="keyword">boolean</span><span> onTouch(View v, MotionEvent event) { </span></span></li><li class=""><span> Log.d(<span class="string">"TAG"</span><span>, </span><span class="string">"onTouch execute, action "</span><span> + event.getAction()); </span></span></li><li class="alt"><span> <span class="keyword">return</span><span> </span><span class="keyword">false</span><span>; </span></span></li><li class=""><span> } </span></li><li class="alt"><span>}); </span></li></ol><div class="save_code tracking-ad" data-mod="popu_249" style="display: none;"><a href="javascript:;" target="_blank"><img src="http://static.blog.csdn.net/images/save_snippets.png"></a></div></div><pre name="code" class="java" style="display: none;">imageView.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.d("TAG", "onTouch execute, action " + event.getAction());
return false;
}
});</pre><p>运行一下程序,点击ImageView,你会发现结果如下:</p><p><span style="font-family:Consolas,Courier New,Courier,mono,serif"><span style="line-height:18px"><span style="font-family:Consolas,'Courier New',Courier,mono,serif; line-height:18px"><br></span></span></span></p><p style="text-align: center;"><span style="font-family:Consolas,Courier New,Courier,mono,serif"><span style="line-height:18px"><span style="font-family:Consolas,'Courier New',Courier,mono,serif; line-height:18px"><img src="https://img-blog.csdn.net/20130617193504328" alt=""><br></span></span></span></p><p style="text-align: center;"><span style="font-family:Consolas,Courier New,Courier,mono,serif"><span style="line-height:18px"><span style="font-family:Consolas,'Courier New',Courier,mono,serif; line-height:18px"><br></span></span></span></p><p>在ACTION_DOWN执行完后,后面的一系列action都不会得到执行了。这又是为什么呢?因为ImageView和按钮不同,它是默认不可点击的,因此在onTouchEvent的第14行判断时无法进入到if的内部,直接跳到第91行返回了false,也就导致后面其它的action都无法执行了。</p><p><span style="font-family:Consolas,Courier New,Courier,mono,serif"><span style="line-height:18px"><span style="font-family:Consolas,'Courier New',Courier,mono,serif; line-height:18px"><br></span></span></span></p><p>好了,关于View的事件分发,我想讲的东西全都在这里了。现在我们再来回顾一下开篇时提到的那三个问题,相信每个人都会有更深一层的理解。</p><p><span style="font-family:Consolas,Courier New,Courier,mono,serif"><span style="line-height:18px"><span style="font-family:Consolas,'Courier New',Courier,mono,serif; line-height:18px"><br></span></span></span></p><p><strong>1. onTouch和onTouchEvent有什么区别,又该如何使用?</strong></p><p>从源码中可以看出,这两个方法都是在View的dispatchTouchEvent中调用的,onTouch优先于onTouchEvent执行。如果在onTouch方法中通过返回true将事件消费掉,onTouchEvent将不会再执行。</p><p><span style="font-family:Consolas,Courier New,Courier,mono,serif"><span style="line-height:18px"><span style="font-family:Consolas,'Courier New',Courier,mono,serif; line-height:18px"><span style="font-family:Consolas,'Courier New',Courier,mono,serif; line-height:18px"><span style="font-family:Consolas,'Courier New',Courier,mono,serif; line-height:18px"><br></span></span></span></span></span></p><p>另外需要注意的是,onTouch能够得到执行需要两个前提条件,第一mOnTouchListener的值不能为空,第二当前点击的控件必须是enable的。因此如果你有一个控件是非enable的,那么给它注册onTouch事件将永远得不到执行。对于这一类控件,如果我们想要监听它的touch事件,就必须通过在该控件中重写onTouchEvent方法来实现。</p><p><span style="font-family:Consolas,Courier New,Courier,mono,serif"><span style="line-height:18px"><span style="font-family:Consolas,'Courier New',Courier,mono,serif; line-height:18px"><span style="font-family:Consolas,'Courier New',Courier,mono,serif; line-height:18px"><span style="font-family:Consolas,'Courier New',Courier,mono,serif; line-height:18px"><span style="font-family:Consolas,'Courier New',Courier,mono,serif; line-height:18px"><br></span></span></span></span></span></span></p><p><strong>2. 为什么给ListView引入了一个滑动菜单的功能,ListView就不能滚动了?</strong></p><p>如果你阅读了<a target="_blank" href="http://blog.csdn.net/sinyu890807/article/details/8744400">Android实现图片滚动控件,含页签功能,让你的应用像淘宝一样炫起来</a> 这篇文章。当时我在图片轮播器里使用Button,主要就是因为Button是可点击的,而ImageView是不可点击的。如果想要使用ImageView,可以有两种改法。第一,在ImageView的onTouch方法里返回true,这样可以保证ACTION_DOWN之后的其它action都能得到执行,才能实现图片滚动的效果。第二,在布局文件里面给ImageView增加一个android:clickable="true"的属性,这样ImageView变成可点击的之后,即使在onTouch里返回了false,ACTION_DOWN之后的其它action也是可以得到执行的。</p><p><span style="font-family:Consolas,Courier New,Courier,mono,serif"><span style="line-height:18px"><span style="font-family:Consolas,'Courier New',Courier,mono,serif; line-height:18px"><br></span></span></span></p><p>今天的讲解就到这里了,相信大家现在对Android事件分发机制又有了进一步的认识,在后面的文章中我会再带大家一起探究Android中ViewGroup的事件分发机制,感兴趣的朋友请继续阅读 <a target="_blank" href="http://blog.csdn.net/sinyu890807/article/details/9153747">Android事件分发机制完全解析,带你从源码的角度彻底理解(下)</a> 。</p><p><span style="font-family:Consolas,Courier New,Courier,mono,serif"><span style="line-height:18px"><br></span></span></p><style>blockquote{
border-left: 10px solid rgba(128,128,128,0.075);
background-color: rgba(128,128,128,0.05);
border-radius: 0 5px 5px 0;
padding: 15px 20px;
}</style><blockquote><p>关注我的技术公众号,每天都有优质技术文章推送。关注我的娱乐公众号,工作、学习累了的时候放松一下自己。</p> <p><a href="http://lib.csdn.net/base/wechat" class="replace_word" title="微信开发知识库" target="_blank" style="color:#df3434; font-weight:bold;">微信</a>扫一扫下方二维码即可关注:</p> <p><img src="https://img-blog.csdn.net/20160507110203928" width="200px" alt=""> <img src="https://img-blog.csdn.net/20161011100137978" width="200px" alt=""></p></blockquote>
</div>
<!-- Baidu Button BEGIN -->
<div class="bdsharebuttonbox tracking-ad bdshare-button-style0-16" style="float: right;" data-mod="popu_172" data-bd-bind="1488720721329">
<a href="#" class="bds_more" data-cmd="more" style="background-position:0 0 !important; background-image: url(http://bdimg.share.baidu.com/static/api/img/share/icons_0_16.png?v=d754dcc0.png) !important" target="_blank"></a>
<a href="#" class="bds_qzone" data-cmd="qzone" title="分享到QQ空间" style="background-position:0 -52px !important" target="_blank"></a>
<a href="#" class="bds_tsina" data-cmd="tsina" title="分享到新浪微博" style="background-position:0 -104px !important" target="_blank"></a>
<a href="#" class="bds_tqq" data-cmd="tqq" title="分享到腾讯微博" style="background-position:0 -260px !important" target="_blank"></a>
<a href="#" class="bds_renren" data-cmd="renren" title="分享到人人网" style="background-position:0 -208px !important" target="_blank"></a>
<a href="#" class="bds_weixin" data-cmd="weixin" title="分享到微信" style="background-position:0 -1612px !important" target="_blank"></a>
</div>
<script>window._bd_share_config = { "common": { "bdSnsKey": {}, "bdText": "", "bdMini": "1", "bdMiniList": false, "bdPic": "", "bdStyle": "0", "bdSize": "16" }, "share": {} }; with (document) 0[(getElementsByTagName('head')[0] || body).appendChild(createElement('script')).src = 'http://bdimg.share.baidu.com/static/api/js/share.js?v=89860593.js?cdnversion=' + ~(-new Date() / 36e5)];</script>
<!-- Baidu Button END -->
<link rel="stylesheet" href="http://static.blog.csdn.net/css/blog_detail.css">
<!--172.16.140.13-->
<!-- Baidu Button BEGIN -->
<script type="text/javascript" id="bdshare_js" data="type=tools&uid=1536434" src="http://bdimg.share.baidu.com/static/js/bds_s_v2.js?cdnversion=413534"></script>
<script type="text/javascript">
document.getElementById("bdshell_js").src = "http://bdimg.share.baidu.com/static/js/shell_v2.js?cdnversion=" + Math.ceil(new Date()/3600000)
</script>
<!-- Baidu Button END -->
<div id="digg" articleid="9097463">
<dl id="btnDigg" class="digg digg_enable" οnclick="btndigga();">
<dt>顶</dt>
<dd>338</dd>
</dl>
<dl id="btnBury" class="digg digg_enable" οnclick="btnburya();">
<dt>踩</dt>
<dd>23</dd>
</dl>
</div>
<div class="tracking-ad" data-mod="popu_222"><a href="javascript:void(0);" target="_blank"> </a> </div>
<div class="tracking-ad" data-mod="popu_223"> <a href="javascript:void(0);" target="_blank"> </a></div>
<script type="text/javascript">
function btndigga() {
$(".tracking-ad[data-mod='popu_222'] a").click();
}
function btnburya() {
$(".tracking-ad[data-mod='popu_223'] a").click();
}
</script>
<ul class="article_next_prev">
<li class="prev_article"><span οnclick="_gaq.push(['_trackEvent','function', 'onclick', 'blog_articles_shangyipian']);location.href='/guolin_blog/article/details/9050671';">上一篇</span><a href="/guolin_blog/article/details/9050671" οnclick="_gaq.push(['_trackEvent','function', 'onclick', 'blog_articles_shangyipian'])">Android系统联系人全特效实现(下),字母表快速滚动</a></li>
<li class="next_article"><span οnclick="_gaq.push(['_trackEvent','function', 'onclick', 'blog_articles_xiayipian']);location.href='/guolin_blog/article/details/9153753';">下一篇</span><a href="/guolin_blog/article/details/9153753" οnclick="_gaq.push(['_trackEvent','function', 'onclick', 'blog_articles_xiayipian'])">Java设计模式透析之 —— 组合(Composite)</a></li>
</ul>
<div style="clear:both; height:10px;"></div>
<div class="similar_article" style="">
<h4>我的同类文章</h4>
<div class="similar_c" style="margin:20px 0px 0px 0px">
<div class="similar_c_t">
<label class="similar_cur">
<span style="cursor:pointer" οnclick="GetCategoryArticles('1399638','sinyu890807','foot','9097463');">Android疑难解析<em>(35)</em></span>
</label>
</div>
<div class="similar_wrap tracking-ad" data-mod="popu_141" style="max-height:195px;">
<a href="http://blog.csdn.net" style="display:none" target="_blank">http://blog.csdn.net</a>
<ul class="similar_list fl"><li><em>•</em><a href="http://blog.csdn.net/sinyu890807/article/details/53122387" id="foot_aritcle_53122387undefined9739549518562853" target="_blank" title="Android新特性介绍,ConstraintLayout完全解析">Android新特性介绍,ConstraintLayout完全解析</a><span>2017-02-03</span><label><i>阅读</i><b>15557</b></label></li> <li><em>•</em><a href="http://blog.csdn.net/sinyu890807/article/details/51336415" id="foot_aritcle_51336415undefined3187948311679065" target="_blank" title="Android提醒微技巧,你真的了解Dialog、Toast和Snackbar吗?">Android提醒微技巧,你真的了解Dialog、Toast和Snackbar吗?</a><span>2016-07-26</span><label><i>阅读</i><b>23263</b></label></li> <li><em>•</em><a href="http://blog.csdn.net/sinyu890807/article/details/51019856" id="foot_aritcle_51019856undefined41644060146063566" target="_blank" title="Android冷启动白屏解析,带你一步步分析和解决问题">Android冷启动白屏解析,带你一步步分析和解决问题</a><span>2016-06-28</span><label><i>阅读</i><b>26412</b></label></li> <li><em>•</em><a href="http://blog.csdn.net/sinyu890807/article/details/50727753" id="foot_aritcle_50727753undefined9010448888875544" target="_blank" title="Android drawable微技巧,你所不知道的drawable的那些细节">Android drawable微技巧,你所不知道的drawable的那些细节</a><span>2016-04-28</span><label><i>阅读</i><b>48116</b></label></li> <li><em>•</em><a href="http://blog.csdn.net/sinyu890807/article/details/49738023" id="foot_aritcle_49738023undefined12490149750374258" target="_blank" title="Android安全攻防战,反编译与混淆技术完全解析(上)">Android安全攻防战,反编译与混淆技术完全解析(上)</a><span>2016-02-18</span><label><i>阅读</i><b>49338</b></label></li> <li><em>•</em><a href="http://blog.csdn.net/sinyu890807/article/details/47028975" id="foot_aritcle_47028975undefined2662747986614704" target="_blank" title="Android Context完全解析,你所不知道的Context的各种细节">Android Context完全解析,你所不知道的Context的各种细节</a><span>2015-11-09</span><label><i>阅读</i><b>71106</b></label></li> </ul>
<ul class="similar_list fr"><li><em>•</em><a href="http://blog.csdn.net/sinyu890807/article/details/51763825" id="foot_aritcle_51763825undefined44748647417873144" target="_blank" title="Android状态栏微技巧,带你真正理解沉浸式模式">Android状态栏微技巧,带你真正理解沉浸式模式</a><span>2016-08-23</span><label><i>阅读</i><b>56859</b></label></li> <li><em>•</em><a href="http://blog.csdn.net/sinyu890807/article/details/51271369" id="foot_aritcle_51271369undefined8458959648851305" target="_blank" title="Android Studio新功能解析,你真的了解Instant Run吗?">Android Studio新功能解析,你真的了解Instant Run吗?</a><span>2016-06-02</span><label><i>阅读</i><b>29879</b></label></li> <li><em>•</em><a href="http://blog.csdn.net/sinyu890807/article/details/50945228" id="foot_aritcle_50945228undefined4225691510364413" target="_blank" title="Android通知栏微技巧,那些你所没关注过的小细节">Android通知栏微技巧,那些你所没关注过的小细节</a><span>2016-05-23</span><label><i>阅读</i><b>17614</b></label></li> <li><em>•</em><a href="http://blog.csdn.net/sinyu890807/article/details/50451259" id="foot_aritcle_50451259undefined7140819621272385" target="_blank" title="Android安全攻防战,反编译与混淆技术完全解析(下)">Android安全攻防战,反编译与混淆技术完全解析(下)</a><span>2016-03-17</span><label><i>阅读</i><b>32141</b></label></li> <li><em>•</em><a href="http://blog.csdn.net/sinyu890807/article/details/48719871" id="foot_aritcle_48719871undefined02064753440208733" target="_blank" title="Android Scroller完全解析,关于Scroller你所需知道的一切">Android Scroller完全解析,关于Scroller你所需知道的一切</a><span>2016-01-21</span><label><i>阅读</i><b>45522</b></label></li> </ul>
<a href="http://blog.csdn.net/sinyu890807/article/category/1399638" class="MoreArticle">更多文章</a></div>
</div>
</div>
<script type="text/javascript">
$(function () {
GetCategoryArticles('1399638', 'sinyu890807','foot','9097463');
});
</script>
</div>
<div class="article_title">
<span class="ico ico_type_Original"></span>
<h1>
<span class="link_title"><a href="/guolin_blog/article/details/9097463">
Android事件分发机制完全解析,带你从源码的角度彻底理解(上)
</a></span>
</h1>
</div>
<div class="article_manage clearfix">
<div class="article_l">
<span class="link_categories">
标签:
<a href="http://www.csdn.net/tag/Android" target="_blank" οnclick="_gaq.push(['_trackEvent','function', 'onclick', 'blog_articles_tag']);">Android</a><a href="http://www.csdn.net/tag/%e4%ba%8b%e4%bb%b6%e5%88%86%e5%8f%91" target="_blank" οnclick="_gaq.push(['_trackEvent','function', 'onclick', 'blog_articles_tag']);">事件分发</a><a href="http://www.csdn.net/tag/onTouch" target="_blank" οnclick="_gaq.push(['_trackEvent','function', 'onclick', 'blog_articles_tag']);">onTouch</a><a href="http://www.csdn.net/tag/dispatchTouchEvent" target="_blank" οnclick="_gaq.push(['_trackEvent','function', 'onclick', 'blog_articles_tag']);">dispatchTouchEvent</a><a href="http://www.csdn.net/tag/onTouchEvent" target="_blank" οnclick="_gaq.push(['_trackEvent','function', 'onclick', 'blog_articles_tag']);">onTouchEvent</a>
</span>
</div>
<div class="article_r">
<span class="link_postdate">2013-06-20 08:30</span>
<span class="link_view" title="阅读次数">236048人阅读</span>
<span class="link_comments" title="评论次数"> <a href="#comments" οnclick="_gaq.push(['_trackEvent','function', 'onclick', 'blog_articles_pinglun'])">评论</a>(277)</span>
<span class="link_collect tracking-ad" data-mod="popu_171"> <a href="javascript:void(0);" οnclick="javascript:collectArticle('Android%e4%ba%8b%e4%bb%b6%e5%88%86%e5%8f%91%e6%9c%ba%e5%88%b6%e5%ae%8c%e5%85%a8%e8%a7%a3%e6%9e%90%ef%bc%8c%e5%b8%a6%e4%bd%a0%e4%bb%8e%e6%ba%90%e7%a0%81%e7%9a%84%e8%a7%92%e5%ba%a6%e5%bd%bb%e5%ba%95%e7%90%86%e8%a7%a3(%e4%b8%8a)','9097463');return false;" title="收藏" target="_blank">收藏</a></span>
<span class="link_report"> <a href="#report" οnclick="javascript:report(9097463,2);return false;" title="举报">举报</a></span>
</div>
</div>
<div class="embody" style="display:none" id="embody">
<span class="embody_t">本文章已收录于:</span>
<div class="embody_c" id="lib" value="{"err":0,"msg":"ok","data":[]}"></div>
</div>
<style type="text/css">
.embody{
padding:10px 10px 10px;
margin:0 -20px;
border-bottom:solid 1px #ededed;
}
.embody_b{
margin:0 ;
padding:10px 0;
}
.embody .embody_t,.embody .embody_c{
display: inline-block;
margin-right:10px;
}
.embody_t{
font-size: 12px;
color:#999;
}
.embody_c{
font-size: 12px;
}
.embody_c img,.embody_c em{
display: inline-block;
vertical-align: middle;
}
.embody_c img{
width:30px;
height:30px;
}
.embody_c em{
margin: 0 20px 0 10px;
color:#333;
font-style: normal;
}
</style>
<script type="text/javascript">
$(function () {
try
{
var lib = eval("("+$("#lib").attr("value")+")");
var html = "";
if (lib.err == 0) {
$.each(lib.data, function (i) {
var obj = lib.data[i];
//html += '<img src="' + obj.logo + '"/>' + obj.name + " ";
html += ' <a href="' + obj.url + '" target="_blank">';
html += ' <img src="' + obj.logo + '">';
html += ' <em><b>' + obj.name + '</b></em>';
html += ' </a>';
});
if (html != "") {
setTimeout(function () {
$("#lib").html(html);
$("#embody").show();
}, 100);
}
}
} catch (err)
{ }
});
</script>
<div class="category clearfix">
<div class="category_l">
<img src="http://static.blog.csdn.net/images/category_icon.jpg">
<span>分类:</span>
</div>
<div class="category_r">
<label οnclick="GetCategoryArticles('1399638','sinyu890807','top','9097463');">
<span οnclick="_gaq.push(['_trackEvent','function', 'onclick', 'blog_articles_fenlei']);">Android疑难解析<em>(35)</em></span>
<img class="arrow-down" src="http://static.blog.csdn.net/images/arrow_triangle _down.jpg" style="display:inline;">
<img class="arrow-up" src="http://static.blog.csdn.net/images/arrow_triangle_up.jpg" style="display:none;">
<div class="subItem">
<div class="subItem_t"><a href="http://blog.csdn.net/sinyu890807/article/category/1399638" target="_blank">作者同类文章</a><i class="J_close">X</i></div>
<ul class="subItem_l" id="top_1399638">
</ul>
</div>
</label>
</div>
</div>
<script type="text/javascript" src="http://static.blog.csdn.net/scripts/category.js"></script>
<div class="bog_copyright">
<p class="copyright_p">版权声明:本文出自郭霖的博客,转载必须注明出处。</p>
</div>
<div id="article_content" class="article_content">
<p>转载请注明出处:<a target="_blank" href="http://blog.csdn.net/sinyu890807/article/details/8986285"></a><a target="_blank" href="http://blog.csdn.net/sinyu890807/article/details/9097463"></a><a target="_blank" href="http://blog.csdn.net/guolin_blog/article/details/9097463">http://blog.csdn.net/guolin_blog/article/details/9097463</a><br></p><p><br></p><p>其实我一直准备写一篇关于<a href="http://lib.csdn.net/base/android" class="replace_word" title="Android知识库" target="_blank" style="color:#df3434; font-weight:bold;">Android</a>事件分发机制的文章,从我的第一篇博客开始,就零零散散在好多地方使用到了Android事件分发的知识。也有好多朋友问过我各种问题,比如:onTouch和onTouchEvent有什么区别,又该如何使用?为什么给ListView引入了一个滑动菜单的功能,ListView就不能滚动了?为什么图片轮播器里的图片使用Button而不用ImageView?等等……对于这些问题,我并没有给出非常详细的回答,因为我知道如果想要彻底搞明白这些问题,掌握Android事件分发机制是必不可少的,而Android事件分发机制绝对不是三言两语就能说得清的。</p><p><br></p><p>在我经过较长时间的筹备之后,终于决定开始写这样一篇文章了。目前虽然网上相关的文章也不少,但我觉得没有哪篇写得特别详细的(也许我还没有找到),多数文章只是讲了讲理论,然后配合demo运行了一下结果。而我准备带着大家从源码的角度进行分析,相信大家可以更加深刻地理解Android事件分发机制。<br></p><p><br></p><p>阅读源码讲究由浅入深,循序渐进,因此我们也从简单的开始,本篇先带大家探究View的事件分发,下篇再去探究难度更高的ViewGroup的事件分发。<br></p><p><br></p><p>那我们现在就开始吧!比如说你当前有一个非常简单的项目,只有一个Activity,并且Activity中只有一个按钮。你可能已经知道,如果想要给这个按钮注册一个点击事件,只需要调用:</p><div class="dp-highlighter bg_java"><div class="bar"><div class="tools"><b>[java]</b> <a href="#" class="ViewSource" title="view plain" οnclick="dp.sh.Toolbar.Command('ViewSource',this);return false;">view plain</a><span data-mod="popu_168"> <a href="#" class="CopyToClipboard" title="copy" οnclick="dp.sh.Toolbar.Command('CopyToClipboard',this);return false;">copy</a><div style="position: absolute; left: 659px; top: 1029px; width: 18px; height: 18px; z-index: 99;"><embed id="ZeroClipboardMovie_1" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" loop="false" menu="false" quality="best" bgcolor="#ffffff" width="18" height="18" name="ZeroClipboardMovie_1" align="middle" allowscriptaccess="always" allowfullscreen="false" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer" flashvars="id=1&width=18&height=18" wmode="transparent"></div></span><span data-mod="popu_169"> <a href="#" class="PrintSource" title="print" οnclick="dp.sh.Toolbar.Command('PrintSource',this);return false;">print</a></span><a href="#" class="About" title="?" οnclick="dp.sh.Toolbar.Command('About',this);return false;">?</a></div></div><ol start="1" class="dp-j"><li class="alt"><span><span>button.setOnClickListener(</span><span class="keyword">new</span><span> OnClickListener() { </span></span></li><li class=""><span> <span class="annotation">@Override</span><span> </span></span></li><li class="alt"><span> <span class="keyword">public</span><span> </span><span class="keyword">void</span><span> onClick(View v) { </span></span></li><li class=""><span> Log.d(<span class="string">"TAG"</span><span>, </span><span class="string">"onClick execute"</span><span>); </span></span></li><li class="alt"><span> } </span></li><li class=""><span>}); </span></li></ol><div class="save_code tracking-ad" data-mod="popu_249" style="display: none;"><a href="javascript:;" target="_blank"><img src="http://static.blog.csdn.net/images/save_snippets.png"></a></div></div><pre name="code" class="java" style="display: none;">button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Log.d("TAG", "onClick execute");
}
});</pre>这样在onClick方法里面写实现,就可以在按钮被点击的时候执行。你可能也已经知道,如果想给这个按钮再添加一个touch事件,只需要调用:<div class="dp-highlighter bg_java"><div class="bar"><div class="tools"><b>[java]</b> <a href="#" class="ViewSource" title="view plain" οnclick="dp.sh.Toolbar.Command('ViewSource',this);return false;">view plain</a><span data-mod="popu_168"> <a href="#" class="CopyToClipboard" title="copy" οnclick="dp.sh.Toolbar.Command('CopyToClipboard',this);return false;">copy</a><div style="position: absolute; left: 659px; top: 1270px; width: 18px; height: 18px; z-index: 99;"><embed id="ZeroClipboardMovie_2" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" loop="false" menu="false" quality="best" bgcolor="#ffffff" width="18" height="18" name="ZeroClipboardMovie_2" align="middle" allowscriptaccess="always" allowfullscreen="false" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer" flashvars="id=2&width=18&height=18" wmode="transparent"></div></span><span data-mod="popu_169"> <a href="#" class="PrintSource" title="print" οnclick="dp.sh.Toolbar.Command('PrintSource',this);return false;">print</a></span><a href="#" class="About" title="?" οnclick="dp.sh.Toolbar.Command('About',this);return false;">?</a></div></div><ol start="1" class="dp-j"><li class="alt"><span><span>button.setOnTouchListener(</span><span class="keyword">new</span><span> OnTouchListener() { </span></span></li><li class=""><span> <span class="annotation">@Override</span><span> </span></span></li><li class="alt"><span> <span class="keyword">public</span><span> </span><span class="keyword">boolean</span><span> onTouch(View v, MotionEvent event) { </span></span></li><li class=""><span> Log.d(<span class="string">"TAG"</span><span>, </span><span class="string">"onTouch execute, action "</span><span> + event.getAction()); </span></span></li><li class="alt"><span> <span class="keyword">return</span><span> </span><span class="keyword">false</span><span>; </span></span></li><li class=""><span> } </span></li><li class="alt"><span>}); </span></li></ol><div class="save_code tracking-ad" data-mod="popu_249" style="display: none;"><a href="javascript:;" target="_blank"><img src="http://static.blog.csdn.net/images/save_snippets.png"></a></div></div><pre name="code" class="java" style="display: none;">button.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.d("TAG", "onTouch execute, action " + event.getAction());
return false;
}
});</pre><p>onTouch方法里能做的事情比onClick要多一些,比如判断手指按下、抬起、移动等事件。那么如果我两个事件都注册了,哪一个会先执行呢?我们来试一下就知道了,运行程序点击按钮,打印结果如下:</p><p><br></p><p style="text-align: center;"><img src="https://img-blog.csdn.net/20130614230234671" alt=""><br></p><p style="text-align: center;"><br></p><p>可以看到,onTouch是优先于onClick执行的,并且onTouch执行了两次,一次是ACTION_DOWN,一次是ACTION_UP(你还可能会有多次ACTION_MOVE的执行,如果你手抖了一下)。因此事件传递的顺序是先经过onTouch,再传递到onClick。</p><p><br></p><p>细心的朋友应该可以注意到,onTouch方法是有返回值的,这里我们返回的是false,如果我们尝试把onTouch方法里的返回值改成true,再运行一次,结果如下:</p><p><br></p><p style="text-align: center;"><img src="https://img-blog.csdn.net/20130614231001468" alt=""><br></p><p style="text-align: center;"><br></p><p>我们发现,onClick方法不再执行了!为什么会这样呢?你可以先理解成onTouch方法返回true就认为这个事件被onTouch消费掉了,因而不会再继续向下传递。</p><p><br></p><p>如果到现在为止,以上的所有知识点你都是清楚的,那么说明你对Android事件传递的基本用法应该是掌握了。不过别满足于现状,让我们从源码的角度分析一下,出现上述现象的原理是什么。</p><p><br></p><p>首先你需要知道一点,只要你触摸到了任何一个控件,就一定会调用该控件的dispatchTouchEvent方法。那当我们去点击按钮的时候,就会去调用Button类里的dispatchTouchEvent方法,可是你会发现Button类里并没有这个方法,那么就到它的父类TextView里去找一找,你会发现TextView里也没有这个方法,那没办法了,只好继续在TextView的父类View里找一找,这个时候你终于在View里找到了这个方法,示意图如下:</p><p><br></p><p style="text-align: center;"><img src="https://img-blog.csdn.net/20130616154134500" alt=""><br></p><p style="text-align: center;"><br></p><p>然后我们来看一下View中dispatchTouchEvent方法的源码:</p><div class="dp-highlighter bg_java"><div class="bar"><div class="tools"><b>[java]</b> <a href="#" class="ViewSource" title="view plain" οnclick="dp.sh.Toolbar.Command('ViewSource',this);return false;">view plain</a><span data-mod="popu_168"> <a href="#" class="CopyToClipboard" title="copy" οnclick="dp.sh.Toolbar.Command('CopyToClipboard',this);return false;">copy</a><div style="position: absolute; left: 659px; top: 2806px; width: 18px; height: 18px; z-index: 99;"><embed id="ZeroClipboardMovie_3" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" loop="false" menu="false" quality="best" bgcolor="#ffffff" width="18" height="18" name="ZeroClipboardMovie_3" align="middle" allowscriptaccess="always" allowfullscreen="false" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer" flashvars="id=3&width=18&height=18" wmode="transparent"></div></span><span data-mod="popu_169"> <a href="#" class="PrintSource" title="print" οnclick="dp.sh.Toolbar.Command('PrintSource',this);return false;">print</a></span><a href="#" class="About" title="?" οnclick="dp.sh.Toolbar.Command('About',this);return false;">?</a></div></div><ol start="1" class="dp-j"><li class="alt"><span><span class="keyword">public</span><span> </span><span class="keyword">boolean</span><span> dispatchTouchEvent(MotionEvent event) { </span></span></li><li class=""><span> <span class="keyword">if</span><span> (mOnTouchListener != </span><span class="keyword">null</span><span> && (mViewFlags & ENABLED_MASK) == ENABLED && </span></span></li><li class="alt"><span> mOnTouchListener.onTouch(<span class="keyword">this</span><span>, event)) { </span></span></li><li class=""><span> <span class="keyword">return</span><span> </span><span class="keyword">true</span><span>; </span></span></li><li class="alt"><span> } </span></li><li class=""><span> <span class="keyword">return</span><span> onTouchEvent(event); </span></span></li><li class="alt"><span>} </span></li></ol><div class="save_code tracking-ad" data-mod="popu_249" style="display: none;"><a href="javascript:;" target="_blank"><img src="http://static.blog.csdn.net/images/save_snippets.png"></a></div></div><pre name="code" class="java" style="display: none;">public boolean dispatchTouchEvent(MotionEvent event) {
if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
mOnTouchListener.onTouch(this, event)) {
return true;
}
return onTouchEvent(event);
}</pre><p>这个方法非常的简洁,只有短短几行代码!我们可以看到,在这个方法内,首先是进行了一个判断,如果mOnTouchListener != null,(mViewFlags & ENABLED_MASK) == ENABLED和mOnTouchListener.onTouch(this, event)这三个条件都为真,就返回true,否则就去执行onTouchEvent(event)方法并返回。</p><p><br></p><p>先看一下第一个条件,mOnTouchListener这个变量是在哪里赋值的呢?我们寻找之后在View里发现了如下方法:</p><div class="dp-highlighter bg_java"><div class="bar"><div class="tools"><b>[java]</b> <a href="#" class="ViewSource" title="view plain" οnclick="dp.sh.Toolbar.Command('ViewSource',this);return false;">view plain</a><span data-mod="popu_168"> <a href="#" class="CopyToClipboard" title="copy" οnclick="dp.sh.Toolbar.Command('CopyToClipboard',this);return false;">copy</a><div style="position: absolute; left: 659px; top: 3168px; width: 18px; height: 18px; z-index: 99;"><embed id="ZeroClipboardMovie_4" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" loop="false" menu="false" quality="best" bgcolor="#ffffff" width="18" height="18" name="ZeroClipboardMovie_4" align="middle" allowscriptaccess="always" allowfullscreen="false" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer" flashvars="id=4&width=18&height=18" wmode="transparent"></div></span><span data-mod="popu_169"> <a href="#" class="PrintSource" title="print" οnclick="dp.sh.Toolbar.Command('PrintSource',this);return false;">print</a></span><a href="#" class="About" title="?" οnclick="dp.sh.Toolbar.Command('About',this);return false;">?</a></div></div><ol start="1" class="dp-j"><li class="alt"><span><span class="keyword">public</span><span> </span><span class="keyword">void</span><span> setOnTouchListener(OnTouchListener l) { </span></span></li><li class=""><span> mOnTouchListener = l; </span></li><li class="alt"><span>} </span></li></ol><div class="save_code tracking-ad" data-mod="popu_249" style="display: none;"><a href="javascript:;" target="_blank"><img src="http://static.blog.csdn.net/images/save_snippets.png"></a></div></div><pre name="code" class="java" style="display: none;">public void setOnTouchListener(OnTouchListener l) {
mOnTouchListener = l;
}</pre><p>Bingo!找到了,mOnTouchListener正是在setOnTouchListener方法里赋值的,也就是说只要我们给控件注册了touch事件,mOnTouchListener就一定被赋值了。</p><p><br></p><p>第二个条件(mViewFlags & ENABLED_MASK) == ENABLED是判断当前点击的控件是否是enable的,按钮默认都是enable的,因此这个条件恒定为true。</p><p><br></p><p>第三个条件就比较关键了,mOnTouchListener.onTouch(this, event),其实也就是去回调控件注册touch事件时的onTouch方法。也就是说如果我们在onTouch方法里返回true,就会让这三个条件全部成立,从而整个方法直接返回true。如果我们在onTouch方法里返回false,就会再去执行onTouchEvent(event)方法。</p><p><br></p><p>现在我们可以结合前面的例子来分析一下了,首先在dispatchTouchEvent中最先执行的就是onTouch方法,因此onTouch肯定是要优先于onClick执行的,也是印证了刚刚的打印结果。而如果在onTouch方法里返回了true,就会让dispatchTouchEvent方法直接返回true,不会再继续往下执行。而打印结果也证实了如果onTouch返回true,onClick就不会再执行了。</p><p><br></p><p>根据以上源码的分析,从原理上解释了我们前面例子的运行结果。而上面的分析还透漏出了一个重要的信息,那就是onClick的调用肯定是在onTouchEvent(event)方法中的!那我们马上来看下onTouchEvent的源码,如下所示:</p><div class="dp-highlighter bg_java"><div class="bar"><div class="tools"><b>[java]</b> <a href="#" class="ViewSource" title="view plain" οnclick="dp.sh.Toolbar.Command('ViewSource',this);return false;">view plain</a><span data-mod="popu_168"> <a href="#" class="CopyToClipboard" title="copy" οnclick="dp.sh.Toolbar.Command('CopyToClipboard',this);return false;">copy</a><div style="position: absolute; left: 659px; top: 3840px; width: 18px; height: 18px; z-index: 99;"><embed id="ZeroClipboardMovie_5" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" loop="false" menu="false" quality="best" bgcolor="#ffffff" width="18" height="18" name="ZeroClipboardMovie_5" align="middle" allowscriptaccess="always" allowfullscreen="false" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer" flashvars="id=5&width=18&height=18" wmode="transparent"></div></span><span data-mod="popu_169"> <a href="#" class="PrintSource" title="print" οnclick="dp.sh.Toolbar.Command('PrintSource',this);return false;">print</a></span><a href="#" class="About" title="?" οnclick="dp.sh.Toolbar.Command('About',this);return false;">?</a></div></div><ol start="1" class="dp-j"><li class="alt"><span><span class="keyword">public</span><span> </span><span class="keyword">boolean</span><span> onTouchEvent(MotionEvent event) { </span></span></li><li class=""><span> <span class="keyword">final</span><span> </span><span class="keyword">int</span><span> viewFlags = mViewFlags; </span></span></li><li class="alt"><span> <span class="keyword">if</span><span> ((viewFlags & ENABLED_MASK) == DISABLED) { </span></span></li><li class=""><span> <span class="comment">// A disabled view that is clickable still consumes the touch</span><span> </span></span></li><li class="alt"><span> <span class="comment">// events, it just doesn't respond to them.</span><span> </span></span></li><li class=""><span> <span class="keyword">return</span><span> (((viewFlags & CLICKABLE) == CLICKABLE || </span></span></li><li class="alt"><span> (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)); </span></li><li class=""><span> } </span></li><li class="alt"><span> <span class="keyword">if</span><span> (mTouchDelegate != </span><span class="keyword">null</span><span>) { </span></span></li><li class=""><span> <span class="keyword">if</span><span> (mTouchDelegate.onTouchEvent(event)) { </span></span></li><li class="alt"><span> <span class="keyword">return</span><span> </span><span class="keyword">true</span><span>; </span></span></li><li class=""><span> } </span></li><li class="alt"><span> } </span></li><li class=""><span> <span class="keyword">if</span><span> (((viewFlags & CLICKABLE) == CLICKABLE || </span></span></li><li class="alt"><span> (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) { </span></li><li class=""><span> <span class="keyword">switch</span><span> (event.getAction()) { </span></span></li><li class="alt"><span> <span class="keyword">case</span><span> MotionEvent.ACTION_UP: </span></span></li><li class=""><span> <span class="keyword">boolean</span><span> prepressed = (mPrivateFlags & PREPRESSED) != </span><span class="number">0</span><span>; </span></span></li><li class="alt"><span> <span class="keyword">if</span><span> ((mPrivateFlags & PRESSED) != </span><span class="number">0</span><span> || prepressed) { </span></span></li><li class=""><span> <span class="comment">// take focus if we don't have it already and we should in</span><span> </span></span></li><li class="alt"><span> <span class="comment">// touch mode.</span><span> </span></span></li><li class=""><span> <span class="keyword">boolean</span><span> focusTaken = </span><span class="keyword">false</span><span>; </span></span></li><li class="alt"><span> <span class="keyword">if</span><span> (isFocusable() && isFocusableInTouchMode() && !isFocused()) { </span></span></li><li class=""><span> focusTaken = requestFocus(); </span></li><li class="alt"><span> } </span></li><li class=""><span> <span class="keyword">if</span><span> (!mHasPerformedLongPress) { </span></span></li><li class="alt"><span> <span class="comment">// This is a tap, so remove the longpress check</span><span> </span></span></li><li class=""><span> removeLongPressCallback(); </span></li><li class="alt"><span> <span class="comment">// Only perform take click actions if we were in the pressed state</span><span> </span></span></li><li class=""><span> <span class="keyword">if</span><span> (!focusTaken) { </span></span></li><li class="alt"><span> <span class="comment">// Use a Runnable and post this rather than calling</span><span> </span></span></li><li class=""><span> <span class="comment">// performClick directly. This lets other visual state</span><span> </span></span></li><li class="alt"><span> <span class="comment">// of the view update before click actions start.</span><span> </span></span></li><li class=""><span> <span class="keyword">if</span><span> (mPerformClick == </span><span class="keyword">null</span><span>) { </span></span></li><li class="alt"><span> mPerformClick = <span class="keyword">new</span><span> PerformClick(); </span></span></li><li class=""><span> } </span></li><li class="alt"><span> <span class="keyword">if</span><span> (!post(mPerformClick)) { </span></span></li><li class=""><span> performClick(); </span></li><li class="alt"><span> } </span></li><li class=""><span> } </span></li><li class="alt"><span> } </span></li><li class=""><span> <span class="keyword">if</span><span> (mUnsetPressedState == </span><span class="keyword">null</span><span>) { </span></span></li><li class="alt"><span> mUnsetPressedState = <span class="keyword">new</span><span> UnsetPressedState(); </span></span></li><li class=""><span> } </span></li><li class="alt"><span> <span class="keyword">if</span><span> (prepressed) { </span></span></li><li class=""><span> mPrivateFlags |= PRESSED; </span></li><li class="alt"><span> refreshDrawableState(); </span></li><li class=""><span> postDelayed(mUnsetPressedState, </span></li><li class="alt"><span> ViewConfiguration.getPressedStateDuration()); </span></li><li class=""><span> } <span class="keyword">else</span><span> </span><span class="keyword">if</span><span> (!post(mUnsetPressedState)) { </span></span></li><li class="alt"><span> <span class="comment">// If the post failed, unpress right now</span><span> </span></span></li><li class=""><span> mUnsetPressedState.run(); </span></li><li class="alt"><span> } </span></li><li class=""><span> removeTapCallback(); </span></li><li class="alt"><span> } </span></li><li class=""><span> <span class="keyword">break</span><span>; </span></span></li><li class="alt"><span> <span class="keyword">case</span><span> MotionEvent.ACTION_DOWN: </span></span></li><li class=""><span> <span class="keyword">if</span><span> (mPendingCheckForTap == </span><span class="keyword">null</span><span>) { </span></span></li><li class="alt"><span> mPendingCheckForTap = <span class="keyword">new</span><span> CheckForTap(); </span></span></li><li class=""><span> } </span></li><li class="alt"><span> mPrivateFlags |= PREPRESSED; </span></li><li class=""><span> mHasPerformedLongPress = <span class="keyword">false</span><span>; </span></span></li><li class="alt"><span> postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout()); </span></li><li class=""><span> <span class="keyword">break</span><span>; </span></span></li><li class="alt"><span> <span class="keyword">case</span><span> MotionEvent.ACTION_CANCEL: </span></span></li><li class=""><span> mPrivateFlags &= ~PRESSED; </span></li><li class="alt"><span> refreshDrawableState(); </span></li><li class=""><span> removeTapCallback(); </span></li><li class="alt"><span> <span class="keyword">break</span><span>; </span></span></li><li class=""><span> <span class="keyword">case</span><span> MotionEvent.ACTION_MOVE: </span></span></li><li class="alt"><span> <span class="keyword">final</span><span> </span><span class="keyword">int</span><span> x = (</span><span class="keyword">int</span><span>) event.getX(); </span></span></li><li class=""><span> <span class="keyword">final</span><span> </span><span class="keyword">int</span><span> y = (</span><span class="keyword">int</span><span>) event.getY(); </span></span></li><li class="alt"><span> <span class="comment">// Be lenient about moving outside of buttons</span><span> </span></span></li><li class=""><span> <span class="keyword">int</span><span> slop = mTouchSlop; </span></span></li><li class="alt"><span> <span class="keyword">if</span><span> ((x < </span><span class="number">0</span><span> - slop) || (x >= getWidth() + slop) || </span></span></li><li class=""><span> (y < <span class="number">0</span><span> - slop) || (y >= getHeight() + slop)) { </span></span></li><li class="alt"><span> <span class="comment">// Outside button</span><span> </span></span></li><li class=""><span> removeTapCallback(); </span></li><li class="alt"><span> <span class="keyword">if</span><span> ((mPrivateFlags & PRESSED) != </span><span class="number">0</span><span>) { </span></span></li><li class=""><span> <span class="comment">// Remove any future long press/tap checks</span><span> </span></span></li><li class="alt"><span> removeLongPressCallback(); </span></li><li class=""><span> <span class="comment">// Need to switch from pressed to not pressed</span><span> </span></span></li><li class="alt"><span> mPrivateFlags &= ~PRESSED; </span></li><li class=""><span> refreshDrawableState(); </span></li><li class="alt"><span> } </span></li><li class=""><span> } </span></li><li class="alt"><span> <span class="keyword">break</span><span>; </span></span></li><li class=""><span> } </span></li><li class="alt"><span> <span class="keyword">return</span><span> </span><span class="keyword">true</span><span>; </span></span></li><li class=""><span> } </span></li><li class="alt"><span> <span class="keyword">return</span><span> </span><span class="keyword">false</span><span>; </span></span></li><li class=""><span>} </span></li></ol><div class="save_code tracking-ad" data-mod="popu_249" style="display: none;"><a href="javascript:;" target="_blank"><img src="http://static.blog.csdn.net/images/save_snippets.png"></a></div></div><pre name="code" class="java" style="display: none;">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.
return (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
}
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
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
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;
}
</pre><p>相较于刚才的dispatchTouchEvent方法,onTouchEvent方法复杂了很多,不过没关系,我们只挑重点看就可以了。</p><p><br></p><p>首先在第14行我们可以看出,如果该控件是可以点击的就会进入到第16行的switch判断中去,而如果当前的事件是抬起手指,则会进入到MotionEvent.ACTION_UP这个case当中。在经过种种判断之后,会执行到第38行<span style="font-family: Consolas, "Courier New", Courier, mono, serif;"><span style="line-height:18px">的performClick()方法,那我们进入到这个方法里瞧一瞧:</span></span></p><div class="dp-highlighter bg_java"><div class="bar"><div class="tools"><b>[java]</b> <a href="#" class="ViewSource" title="view plain" οnclick="dp.sh.Toolbar.Command('ViewSource',this);return false;">view plain</a><span data-mod="popu_168"> <a href="#" class="CopyToClipboard" title="copy" οnclick="dp.sh.Toolbar.Command('CopyToClipboard',this);return false;">copy</a><div style="position: absolute; left: 659px; top: 5637px; width: 18px; height: 18px; z-index: 99;"><embed id="ZeroClipboardMovie_6" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" loop="false" menu="false" quality="best" bgcolor="#ffffff" width="18" height="18" name="ZeroClipboardMovie_6" align="middle" allowscriptaccess="always" allowfullscreen="false" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer" flashvars="id=6&width=18&height=18" wmode="transparent"></div></span><span data-mod="popu_169"> <a href="#" class="PrintSource" title="print" οnclick="dp.sh.Toolbar.Command('PrintSource',this);return false;">print</a></span><a href="#" class="About" title="?" οnclick="dp.sh.Toolbar.Command('About',this);return false;">?</a></div></div><ol start="1" class="dp-j"><li class="alt"><span><span class="keyword">public</span><span> </span><span class="keyword">boolean</span><span> performClick() { </span></span></li><li class=""><span> sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); </span></li><li class="alt"><span> <span class="keyword">if</span><span> (mOnClickListener != </span><span class="keyword">null</span><span>) { </span></span></li><li class=""><span> playSoundEffect(SoundEffectConstants.CLICK); </span></li><li class="alt"><span> mOnClickListener.onClick(<span class="keyword">this</span><span>); </span></span></li><li class=""><span> <span class="keyword">return</span><span> </span><span class="keyword">true</span><span>; </span></span></li><li class="alt"><span> } </span></li><li class=""><span> <span class="keyword">return</span><span> </span><span class="keyword">false</span><span>; </span></span></li><li class="alt"><span>} </span></li></ol><div class="save_code tracking-ad" data-mod="popu_249" style="display: none;"><a href="javascript:;" target="_blank"><img src="http://static.blog.csdn.net/images/save_snippets.png"></a></div></div><pre name="code" class="java" style="display: none;">public boolean performClick() {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
if (mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
mOnClickListener.onClick(this);
return true;
}
return false;
}</pre>可以看到,只要mOnClickListener不是null,就会去调用它的onClick方法,那mOnClickListener又是在哪里赋值的呢?经过寻找后找到如下方法:<div class="dp-highlighter bg_java"><div class="bar"><div class="tools"><b>[java]</b> <a href="#" class="ViewSource" title="view plain" οnclick="dp.sh.Toolbar.Command('ViewSource',this);return false;">view plain</a><span data-mod="popu_168"> <a href="#" class="CopyToClipboard" title="copy" οnclick="dp.sh.Toolbar.Command('CopyToClipboard',this);return false;">copy</a><div style="position: absolute; left: 659px; top: 5929px; width: 18px; height: 18px; z-index: 99;"><embed id="ZeroClipboardMovie_7" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" loop="false" menu="false" quality="best" bgcolor="#ffffff" width="18" height="18" name="ZeroClipboardMovie_7" align="middle" allowscriptaccess="always" allowfullscreen="false" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer" flashvars="id=7&width=18&height=18" wmode="transparent"></div></span><span data-mod="popu_169"> <a href="#" class="PrintSource" title="print" οnclick="dp.sh.Toolbar.Command('PrintSource',this);return false;">print</a></span><a href="#" class="About" title="?" οnclick="dp.sh.Toolbar.Command('About',this);return false;">?</a></div></div><ol start="1" class="dp-j"><li class="alt"><span><span class="keyword">public</span><span> </span><span class="keyword">void</span><span> setOnClickListener(OnClickListener l) { </span></span></li><li class=""><span> <span class="keyword">if</span><span> (!isClickable()) { </span></span></li><li class="alt"><span> setClickable(<span class="keyword">true</span><span>); </span></span></li><li class=""><span> } </span></li><li class="alt"><span> mOnClickListener = l; </span></li><li class=""><span>} </span></li></ol><div class="save_code tracking-ad" data-mod="popu_249" style="display: none;"><a href="javascript:;" target="_blank"><img src="http://static.blog.csdn.net/images/save_snippets.png"></a></div></div><pre name="code" class="java" style="display: none;">public void setOnClickListener(OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
mOnClickListener = l;
}</pre><p>一切都是那么清楚了!当我们通过调用setOnClickListener方法来给控件注册一个点击事件时,就会给mOnClickListener赋值。然后每当控件被点击时,都会在performClick()方法里回调被点击控件的onClick方法。</p><p><span style="font-family:Consolas,'Courier New',Courier,mono,serif; line-height:18px"><br></span></p><p>这样View的整个事件分发的流程就让我们搞清楚了!不过别高兴的太早,现在还没结束,还有一个很重要的知识点需要说明,就是touch事件的层级传递。我们都知道如果给一个控件注册了touch事件,每次点击它的时候都会触发一系列的ACTION_DOWN,ACTION_MOVE,ACTION_UP等事件。这里需要注意,如果你在执行ACTION_DOWN的时候返回了false,后面一系列其它的action就不会再得到执行了。简单的说,就是当dispatchTouchEvent在进行事件分发的时候,只有前一个action返回true,才会触发后一个action。</p><p><span style="font-family: Consolas, "Courier New", Courier, mono, serif; line-height: 18px;"><br></span></p><p>说到这里,很多的朋友肯定要有巨大的疑问了。这不是在自相矛盾吗?前面的例子中,明明在onTouch事件里面返回了false,ACTION_DOWN和ACTION_UP不是都得到执行了吗?其实你只是被假象所迷惑了,让我们仔细分析一下,在前面的例子当中,我们到底返回的是什么。</p><p>参考着我们前面分析的源码,首先在onTouch事件里返回了false,就一定会进入到onTouchEvent方法中,然后我们来看一下onTouchEvent方法的细节。由于我们点击了按钮,就会进入到第14行这个if判断的内部,然后你会发现,不管当前的action是什么,最终都一定会走到第89行,返回一个true。</p><p><br></p><p>是不是有一种被欺骗的感觉?明明在onTouch事件里返回了false,系统还是在onTouchEvent方法中帮你返回了true。就因为这个原因,才使得前面的例子中ACTION_UP可以得到执行。</p><p><span style="font-family:Consolas,Courier New,Courier,mono,serif"><span style="line-height:18px"><span style="font-family:Consolas,'Courier New',Courier,mono,serif; line-height:18px"><br></span></span></span></p><p>那我们可以换一个控件,将按钮替换成ImageView,然后给它也注册一个touch事件,并返回false。如下所示:</p><div class="dp-highlighter bg_java"><div class="bar"><div class="tools"><b>[java]</b> <a href="#" class="ViewSource" title="view plain" οnclick="dp.sh.Toolbar.Command('ViewSource',this);return false;">view plain</a><span data-mod="popu_168"> <a href="#" class="CopyToClipboard" title="copy" οnclick="dp.sh.Toolbar.Command('CopyToClipboard',this);return false;">copy</a><div style="position: absolute; left: 659px; top: 6671px; width: 18px; height: 18px; z-index: 99;"><embed id="ZeroClipboardMovie_8" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" loop="false" menu="false" quality="best" bgcolor="#ffffff" width="18" height="18" name="ZeroClipboardMovie_8" align="middle" allowscriptaccess="always" allowfullscreen="false" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer" flashvars="id=8&width=18&height=18" wmode="transparent"></div></span><span data-mod="popu_169"> <a href="#" class="PrintSource" title="print" οnclick="dp.sh.Toolbar.Command('PrintSource',this);return false;">print</a></span><a href="#" class="About" title="?" οnclick="dp.sh.Toolbar.Command('About',this);return false;">?</a></div></div><ol start="1" class="dp-j"><li class="alt"><span><span>imageView.setOnTouchListener(</span><span class="keyword">new</span><span> OnTouchListener() { </span></span></li><li class=""><span> <span class="annotation">@Override</span><span> </span></span></li><li class="alt"><span> <span class="keyword">public</span><span> </span><span class="keyword">boolean</span><span> onTouch(View v, MotionEvent event) { </span></span></li><li class=""><span> Log.d(<span class="string">"TAG"</span><span>, </span><span class="string">"onTouch execute, action "</span><span> + event.getAction()); </span></span></li><li class="alt"><span> <span class="keyword">return</span><span> </span><span class="keyword">false</span><span>; </span></span></li><li class=""><span> } </span></li><li class="alt"><span>}); </span></li></ol><div class="save_code tracking-ad" data-mod="popu_249" style="display: none;"><a href="javascript:;" target="_blank"><img src="http://static.blog.csdn.net/images/save_snippets.png"></a></div></div><pre name="code" class="java" style="display: none;">imageView.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.d("TAG", "onTouch execute, action " + event.getAction());
return false;
}
});</pre><p>运行一下程序,点击ImageView,你会发现结果如下:</p><p><span style="font-family:Consolas,Courier New,Courier,mono,serif"><span style="line-height:18px"><span style="font-family:Consolas,'Courier New',Courier,mono,serif; line-height:18px"><br></span></span></span></p><p style="text-align: center;"><span style="font-family:Consolas,Courier New,Courier,mono,serif"><span style="line-height:18px"><span style="font-family:Consolas,'Courier New',Courier,mono,serif; line-height:18px"><img src="https://img-blog.csdn.net/20130617193504328" alt=""><br></span></span></span></p><p style="text-align: center;"><span style="font-family:Consolas,Courier New,Courier,mono,serif"><span style="line-height:18px"><span style="font-family:Consolas,'Courier New',Courier,mono,serif; line-height:18px"><br></span></span></span></p><p>在ACTION_DOWN执行完后,后面的一系列action都不会得到执行了。这又是为什么呢?因为ImageView和按钮不同,它是默认不可点击的,因此在onTouchEvent的第14行判断时无法进入到if的内部,直接跳到第91行返回了false,也就导致后面其它的action都无法执行了。</p><p><span style="font-family:Consolas,Courier New,Courier,mono,serif"><span style="line-height:18px"><span style="font-family:Consolas,'Courier New',Courier,mono,serif; line-height:18px"><br></span></span></span></p><p>好了,关于View的事件分发,我想讲的东西全都在这里了。现在我们再来回顾一下开篇时提到的那三个问题,相信每个人都会有更深一层的理解。</p><p><span style="font-family:Consolas,Courier New,Courier,mono,serif"><span style="line-height:18px"><span style="font-family:Consolas,'Courier New',Courier,mono,serif; line-height:18px"><br></span></span></span></p><p><strong>1. onTouch和onTouchEvent有什么区别,又该如何使用?</strong></p><p>从源码中可以看出,这两个方法都是在View的dispatchTouchEvent中调用的,onTouch优先于onTouchEvent执行。如果在onTouch方法中通过返回true将事件消费掉,onTouchEvent将不会再执行。</p><p><span style="font-family:Consolas,Courier New,Courier,mono,serif"><span style="line-height:18px"><span style="font-family:Consolas,'Courier New',Courier,mono,serif; line-height:18px"><span style="font-family:Consolas,'Courier New',Courier,mono,serif; line-height:18px"><span style="font-family:Consolas,'Courier New',Courier,mono,serif; line-height:18px"><br></span></span></span></span></span></p><p>另外需要注意的是,onTouch能够得到执行需要两个前提条件,第一mOnTouchListener的值不能为空,第二当前点击的控件必须是enable的。因此如果你有一个控件是非enable的,那么给它注册onTouch事件将永远得不到执行。对于这一类控件,如果我们想要监听它的touch事件,就必须通过在该控件中重写onTouchEvent方法来实现。</p><p><span style="font-family:Consolas,Courier New,Courier,mono,serif"><span style="line-height:18px"><span style="font-family:Consolas,'Courier New',Courier,mono,serif; line-height:18px"><span style="font-family:Consolas,'Courier New',Courier,mono,serif; line-height:18px"><span style="font-family:Consolas,'Courier New',Courier,mono,serif; line-height:18px"><span style="font-family:Consolas,'Courier New',Courier,mono,serif; line-height:18px"><br></span></span></span></span></span></span></p><p><strong>2. 为什么给ListView引入了一个滑动菜单的功能,ListView就不能滚动了?</strong></p><p>如果你阅读了<a target="_blank" href="http://blog.csdn.net/sinyu890807/article/details/8744400">Android实现图片滚动控件,含页签功能,让你的应用像淘宝一样炫起来</a> 这篇文章。当时我在图片轮播器里使用Button,主要就是因为Button是可点击的,而ImageView是不可点击的。如果想要使用ImageView,可以有两种改法。第一,在ImageView的onTouch方法里返回true,这样可以保证ACTION_DOWN之后的其它action都能得到执行,才能实现图片滚动的效果。第二,在布局文件里面给ImageView增加一个android:clickable="true"的属性,这样ImageView变成可点击的之后,即使在onTouch里返回了false,ACTION_DOWN之后的其它action也是可以得到执行的。</p><p><span style="font-family:Consolas,Courier New,Courier,mono,serif"><span style="line-height:18px"><span style="font-family:Consolas,'Courier New',Courier,mono,serif; line-height:18px"><br></span></span></span></p><p>今天的讲解就到这里了,相信大家现在对Android事件分发机制又有了进一步的认识,在后面的文章中我会再带大家一起探究Android中ViewGroup的事件分发机制,感兴趣的朋友请继续阅读 <a target="_blank" href="http://blog.csdn.net/sinyu890807/article/details/9153747">Android事件分发机制完全解析,带你从源码的角度彻底理解(下)</a> 。</p><p><span style="font-family:Consolas,Courier New,Courier,mono,serif"><span style="line-height:18px"><br></span></span></p><style>blockquote{
border-left: 10px solid rgba(128,128,128,0.075);
background-color: rgba(128,128,128,0.05);
border-radius: 0 5px 5px 0;
padding: 15px 20px;
}</style><blockquote><p>关注我的技术公众号,每天都有优质技术文章推送。关注我的娱乐公众号,工作、学习累了的时候放松一下自己。</p> <p><a href="http://lib.csdn.net/base/wechat" class="replace_word" title="微信开发知识库" target="_blank" style="color:#df3434; font-weight:bold;">微信</a>扫一扫下方二维码即可关注:</p> <p><img src="https://img-blog.csdn.net/20160507110203928" width="200px" alt=""> <img src="https://img-blog.csdn.net/20161011100137978" width="200px" alt=""></p></blockquote>
</div>
<!-- Baidu Button BEGIN -->
<div class="bdsharebuttonbox tracking-ad bdshare-button-style0-16" style="float: right;" data-mod="popu_172" data-bd-bind="1488720721329">
<a href="#" class="bds_more" data-cmd="more" style="background-position:0 0 !important; background-image: url(http://bdimg.share.baidu.com/static/api/img/share/icons_0_16.png?v=d754dcc0.png) !important" target="_blank"></a>
<a href="#" class="bds_qzone" data-cmd="qzone" title="分享到QQ空间" style="background-position:0 -52px !important" target="_blank"></a>
<a href="#" class="bds_tsina" data-cmd="tsina" title="分享到新浪微博" style="background-position:0 -104px !important" target="_blank"></a>
<a href="#" class="bds_tqq" data-cmd="tqq" title="分享到腾讯微博" style="background-position:0 -260px !important" target="_blank"></a>
<a href="#" class="bds_renren" data-cmd="renren" title="分享到人人网" style="background-position:0 -208px !important" target="_blank"></a>
<a href="#" class="bds_weixin" data-cmd="weixin" title="分享到微信" style="background-position:0 -1612px !important" target="_blank"></a>
</div>
<script>window._bd_share_config = { "common": { "bdSnsKey": {}, "bdText": "", "bdMini": "1", "bdMiniList": false, "bdPic": "", "bdStyle": "0", "bdSize": "16" }, "share": {} }; with (document) 0[(getElementsByTagName('head')[0] || body).appendChild(createElement('script')).src = 'http://bdimg.share.baidu.com/static/api/js/share.js?v=89860593.js?cdnversion=' + ~(-new Date() / 36e5)];</script>
<!-- Baidu Button END -->
<link rel="stylesheet" href="http://static.blog.csdn.net/css/blog_detail.css">
<!--172.16.140.13-->
<!-- Baidu Button BEGIN -->
<script type="text/javascript" id="bdshare_js" data="type=tools&uid=1536434" src="http://bdimg.share.baidu.com/static/js/bds_s_v2.js?cdnversion=413534"></script>
<script type="text/javascript">
document.getElementById("bdshell_js").src = "http://bdimg.share.baidu.com/static/js/shell_v2.js?cdnversion=" + Math.ceil(new Date()/3600000)
</script>
<!-- Baidu Button END -->
<div id="digg" articleid="9097463">
<dl id="btnDigg" class="digg digg_enable" οnclick="btndigga();">
<dt>顶</dt>
<dd>338</dd>
</dl>
<dl id="btnBury" class="digg digg_enable" οnclick="btnburya();">
<dt>踩</dt>
<dd>23</dd>
</dl>
</div>
<div class="tracking-ad" data-mod="popu_222"><a href="javascript:void(0);" target="_blank"> </a> </div>
<div class="tracking-ad" data-mod="popu_223"> <a href="javascript:void(0);" target="_blank"> </a></div>
<script type="text/javascript">
function btndigga() {
$(".tracking-ad[data-mod='popu_222'] a").click();
}
function btnburya() {
$(".tracking-ad[data-mod='popu_223'] a").click();
}
</script>
<ul class="article_next_prev">
<li class="prev_article"><span οnclick="_gaq.push(['_trackEvent','function', 'onclick', 'blog_articles_shangyipian']);location.href='/guolin_blog/article/details/9050671';">上一篇</span><a href="/guolin_blog/article/details/9050671" οnclick="_gaq.push(['_trackEvent','function', 'onclick', 'blog_articles_shangyipian'])">Android系统联系人全特效实现(下),字母表快速滚动</a></li>
<li class="next_article"><span οnclick="_gaq.push(['_trackEvent','function', 'onclick', 'blog_articles_xiayipian']);location.href='/guolin_blog/article/details/9153753';">下一篇</span><a href="/guolin_blog/article/details/9153753" οnclick="_gaq.push(['_trackEvent','function', 'onclick', 'blog_articles_xiayipian'])">Java设计模式透析之 —— 组合(Composite)</a></li>
</ul>
<div style="clear:both; height:10px;"></div>
<div class="similar_article" style="">
<h4>我的同类文章</h4>
<div class="similar_c" style="margin:20px 0px 0px 0px">
<div class="similar_c_t">
<label class="similar_cur">
<span style="cursor:pointer" οnclick="GetCategoryArticles('1399638','sinyu890807','foot','9097463');">Android疑难解析<em>(35)</em></span>
</label>
</div>
<div class="similar_wrap tracking-ad" data-mod="popu_141" style="max-height:195px;">
<a href="http://blog.csdn.net" style="display:none" target="_blank">http://blog.csdn.net</a>
<ul class="similar_list fl"><li><em>•</em><a href="http://blog.csdn.net/sinyu890807/article/details/53122387" id="foot_aritcle_53122387undefined9739549518562853" target="_blank" title="Android新特性介绍,ConstraintLayout完全解析">Android新特性介绍,ConstraintLayout完全解析</a><span>2017-02-03</span><label><i>阅读</i><b>15557</b></label></li> <li><em>•</em><a href="http://blog.csdn.net/sinyu890807/article/details/51336415" id="foot_aritcle_51336415undefined3187948311679065" target="_blank" title="Android提醒微技巧,你真的了解Dialog、Toast和Snackbar吗?">Android提醒微技巧,你真的了解Dialog、Toast和Snackbar吗?</a><span>2016-07-26</span><label><i>阅读</i><b>23263</b></label></li> <li><em>•</em><a href="http://blog.csdn.net/sinyu890807/article/details/51019856" id="foot_aritcle_51019856undefined41644060146063566" target="_blank" title="Android冷启动白屏解析,带你一步步分析和解决问题">Android冷启动白屏解析,带你一步步分析和解决问题</a><span>2016-06-28</span><label><i>阅读</i><b>26412</b></label></li> <li><em>•</em><a href="http://blog.csdn.net/sinyu890807/article/details/50727753" id="foot_aritcle_50727753undefined9010448888875544" target="_blank" title="Android drawable微技巧,你所不知道的drawable的那些细节">Android drawable微技巧,你所不知道的drawable的那些细节</a><span>2016-04-28</span><label><i>阅读</i><b>48116</b></label></li> <li><em>•</em><a href="http://blog.csdn.net/sinyu890807/article/details/49738023" id="foot_aritcle_49738023undefined12490149750374258" target="_blank" title="Android安全攻防战,反编译与混淆技术完全解析(上)">Android安全攻防战,反编译与混淆技术完全解析(上)</a><span>2016-02-18</span><label><i>阅读</i><b>49338</b></label></li> <li><em>•</em><a href="http://blog.csdn.net/sinyu890807/article/details/47028975" id="foot_aritcle_47028975undefined2662747986614704" target="_blank" title="Android Context完全解析,你所不知道的Context的各种细节">Android Context完全解析,你所不知道的Context的各种细节</a><span>2015-11-09</span><label><i>阅读</i><b>71106</b></label></li> </ul>
<ul class="similar_list fr"><li><em>•</em><a href="http://blog.csdn.net/sinyu890807/article/details/51763825" id="foot_aritcle_51763825undefined44748647417873144" target="_blank" title="Android状态栏微技巧,带你真正理解沉浸式模式">Android状态栏微技巧,带你真正理解沉浸式模式</a><span>2016-08-23</span><label><i>阅读</i><b>56859</b></label></li> <li><em>•</em><a href="http://blog.csdn.net/sinyu890807/article/details/51271369" id="foot_aritcle_51271369undefined8458959648851305" target="_blank" title="Android Studio新功能解析,你真的了解Instant Run吗?">Android Studio新功能解析,你真的了解Instant Run吗?</a><span>2016-06-02</span><label><i>阅读</i><b>29879</b></label></li> <li><em>•</em><a href="http://blog.csdn.net/sinyu890807/article/details/50945228" id="foot_aritcle_50945228undefined4225691510364413" target="_blank" title="Android通知栏微技巧,那些你所没关注过的小细节">Android通知栏微技巧,那些你所没关注过的小细节</a><span>2016-05-23</span><label><i>阅读</i><b>17614</b></label></li> <li><em>•</em><a href="http://blog.csdn.net/sinyu890807/article/details/50451259" id="foot_aritcle_50451259undefined7140819621272385" target="_blank" title="Android安全攻防战,反编译与混淆技术完全解析(下)">Android安全攻防战,反编译与混淆技术完全解析(下)</a><span>2016-03-17</span><label><i>阅读</i><b>32141</b></label></li> <li><em>•</em><a href="http://blog.csdn.net/sinyu890807/article/details/48719871" id="foot_aritcle_48719871undefined02064753440208733" target="_blank" title="Android Scroller完全解析,关于Scroller你所需知道的一切">Android Scroller完全解析,关于Scroller你所需知道的一切</a><span>2016-01-21</span><label><i>阅读</i><b>45522</b></label></li> </ul>
<a href="http://blog.csdn.net/sinyu890807/article/category/1399638" class="MoreArticle">更多文章</a></div>
</div>
</div>
<script type="text/javascript">
$(function () {
GetCategoryArticles('1399638', 'sinyu890807','foot','9097463');
});
</script>
</div>