Android中事件分发机制的总结
原文出处:http://blog.csdn.net/jdsjlzx/article/details/52355249
事件传递虽然算不上某个单独的知识点,但是在实际项目开发中肯定会碰到,如果不明白其中的原理,那在设计各种滑动效果时就会感到很困惑。
关于事件的传递,我们可能会有以下疑问:
- 事件是如何传递的
- 事件是如何处理的
- 自定义view的时候,事件也冲突了怎么解决
带着这三个疑问,我们来总结一下事件传递机制是怎么回事。
事件分发原理
事件是如何传递的:
首先由Activity分发,分发给根View,也就是DecorView(DecorView为整个Window界面的最顶层View)
然后由根View分发到子的View
如下图所示:
再来看下面这张图:(这张图是整个事件传递机制的核心)
如上图所示:
在ViewGroup中可以通过onInterceptTouchEvent方法对事件传递进行拦截。
onInterceptTouchEvent方法:
- 返回true代表不允许事件继续向子View传递,将会触发当前View的onTouchEvent(),进行事件的消费;
- 返回false代表不对事件进行拦截,事件可以传递给子View;
- 默认返回false。
事件是如何处理的:
再来看下面这张图:
上图显示:子View中如果将传递的事件消费掉,父类的ViewGroup中将无法接收到任何事件。
onTouch和onClick事件同时发生的问题
首先这里要解释一下各种概念,避免混淆。
各种概念
事件:混合体(可能是点击事件也可能是触摸事件)。
触摸事件:按下,滑动和离开
点击事件:按下,停留一会儿和离开
触摸onTouch事件和点击onClick事件有什么关系?
执行先后不一样。触摸事件先执行
触摸事件返回值影响点击事件(前者影响后者,而后者不影响前者)
onTouch和onClick事件同时执行:
如果按钮的onTouch和onClick方法同时执行,会有什么效果呢?我们通过代码来看一下:
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;public class MainActivity extends Activity {private static final String TAG = "MainActivity";private Button btn;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);btn = (Button) findViewById(R.id.btn);//按钮的touch触摸事件btn.setOnTouchListener(new View.OnTouchListener() {@Overridepublic boolean onTouch(View v, MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_DOWN: //按下的动作Log.d(TAG, "btn is MotionEvent.ACTION_DOWN");break;case MotionEvent.ACTION_MOVE: //滑动的动作Log.d(TAG, "btn is MotionEvent.ACTION_MOVE");break;case MotionEvent.ACTION_UP: //离开的动作Log.d(TAG, "btn is MotionEvent.ACTION_UP");break;}return false; //默认的返回值}});//按钮的点击事件btn.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {Log.d(TAG, "btn is click");}});}
}
上方代码中,按钮btn既包含了onTouch事件,也包含了onClick事件,现在运行程序,点击按钮,后台打印的日志如下:
通过上方日志我们可以看到,onTouch事件是比onClick事件先执行的。
备注:这里提示一下,如果我们仅仅只是用手指点击按钮,然后马上松开,onTouch事件中只会执行ACTION_DOWN和ACTION_UP动作;如果用手机点击按钮,并且手指还在按钮上滑动了一会儿,那么滑动的过程中,ACTION_MOVE动作就会不停的执行。现在我们应该能明白这三个动作的含义了吧?
只执行onTouch事件,不执行onClick事件:
如果按钮的onTouch和onClick方法同时执行,在有些情况下不太满足产品的需求。那如果只想执行onTouch事件,不执行onClick事件,该怎么做呢?很简单,只需要在上方代码中,将第39行的代码改为return true,就行了,即:将onTouch方法的返回值改为true,就会只执行onTouch事件,不执行onClick事件。改完代码之后,后台的运行效果如下:
为什么这样改代码就可以了呢?这就需要从源码的角度来理解了。
button按钮中没有dispatchTouchEvent方法,需要去它的父类View.java中去找dispatchTouchEvent方法。源码如下所示:
上图分析:红框部分的onTouch()方法默认是返回false,所以就会执行蓝框部分的代码,即:调用onTouchEvent()方法。而onTouchEvent方法中,会在ACTION_UP动作里面会去初始化onClick事件。如下图所示:
于是onClick事件就得到了执行。
onClick和onLongClick事件能同时发生:
我们通过代码来演示一下。
1、onTouch事件、onLongClick事件、onClick事件默认是同时执行:(执行的先后顺序:onTouch > onLongClick > onClick)
完整版代码如下:
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;public class MainActivity extends Activity {private static final String TAG = "MainActivity";private Button btn;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);btn = (Button) findViewById(R.id.btn);//按钮的touch事件btn.setOnTouchListener(new View.OnTouchListener() {@Overridepublic boolean onTouch(View v, MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_DOWN: //按下的动作Log.d(TAG, "btn is MotionEvent.ACTION_DOWN");break;case MotionEvent.ACTION_MOVE: //滑动的动作Log.d(TAG, "btn is MotionEvent.ACTION_MOVE");break;case MotionEvent.ACTION_UP: //离开的动作Log.d(TAG, "btn is MotionEvent.ACTION_UP");break;}return false; //默认的返回值}});//按钮的onLongClick事件btn.setOnLongClickListener(new View.OnLongClickListener() {@Overridepublic boolean onLongClick(View v) {Log.d(TAG, "btn is onLongClick");return false; //默认的返回值}});//按钮的onClick事件btn.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {Log.d(TAG, "btn is onClick");}});}
}
运行程序后,长按按钮,后台日志如下:
源码比较长,就不贴出来了,通过查看源码我们得知,当onTouch事件中的ACTION_DOWN动作执行180ms之后,就会执行onLongClick事件。
那我们现在知道了,如果在一个按钮上按下的时间过长,onLongClick事件会比onClick事件先执行。
2、只执行onTouch事件和onLongClick事件,不执行onClick事件:
为了实现这种逻辑,也很简单,只需要将上方的第50行代码改为return true就行了,即:将onLongClick方法的返回值改为true,就不会执行onClick事件了。改完代码之后,后台的运行效果如下:
为什么这样改代码就可以了呢?这就需要从源码的角度来理解了。在View.java中的dispatchTouchEvent方法里,ACTION_UP动作里面对onLongTouch事件进行了处理,具体源码就不展示出来了,这个有点复杂。
事件传递机制调用顺序:
ViewGroup的事件传递方法:
- dispatchTouchEvent
- onInterceptTouchEvent
- onTouchEvent
View的事件传递方法:
- View的dispatchTouchEvent
- View的onTouchEvent
注意,只有父的ViewGroup容器才有onInterceptTouchEvent方法。这也很好理解,最小的那个子的view没必要再拦截了,因为无法继续向下传递事件,是否拦截已经没有意义了。
接下来,我们用LinearLayout代表ViewGroup,用Button代表子View,然后去重写LinearLayout和Button中的事件传递方法,看一下各个方法的调用顺序。代码如下:
1. MyLinearLayout.java:(重写LinearLayout中的事件传递方法)
package com.example.smyhvae.touchdemo;import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.LinearLayout;/*** Created by smyhvae on 2015/9/11.*/
public class MyLinearLayout extends LinearLayout {private static final String TAG = "MainActivity";public MyLinearLayout(Context context) {super(context);}public MyLinearLayout(Context context, AttributeSet attrs) {super(context, attrs);}public MyLinearLayout(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);}@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {switch (ev.getAction()) {case MotionEvent.ACTION_DOWN: //按下的动作Log.d(TAG, "ViewGroup dispatchTouchEvent ACTION_DOWN");break;case MotionEvent.ACTION_MOVE: //滑动的动作Log.d(TAG, "ViewGroup dispatchTouchEvent ACTION_MOVE");break;case MotionEvent.ACTION_UP: //离开的动作Log.d(TAG, "ViewGroup dispatchTouchEvent ACTION_UP");break;}return super.dispatchTouchEvent(ev);}@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {switch (ev.getAction()) {case MotionEvent.ACTION_DOWN: //按下的动作Log.d(TAG, "ViewGroup onInterceptTouchEvent ACTION_DOWN");break;case MotionEvent.ACTION_MOVE: //滑动的动作Log.d(TAG, "ViewGroup onInterceptTouchEvent ACTION_MOVE");break;case MotionEvent.ACTION_UP: //离开的动作Log.d(TAG, "ViewGroup onInterceptTouchEvent ACTION_UP");break;}return super.onInterceptTouchEvent(ev);}@Overridepublic boolean onTouchEvent(MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_DOWN: //按下的动作Log.d(TAG, "ViewGroup onTouchEvent ACTION_DOWN");break;case MotionEvent.ACTION_MOVE: //滑动的动作Log.d(TAG, "ViewGroup onTouchEvent ACTION_MOVE");break;case MotionEvent.ACTION_UP: //离开的动作Log.d(TAG, "ViewGroup onTouchEvent ACTION_UP");break;}return super.onTouchEvent(event);}
}
2. MyButton.java:(重写Button中的事件传递方法,注意:这里面没有onInterceptTouchEvent方法)
package com.example.smyhvae.touchdemo;import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.Button;/*** Created by smyhvae on 2015/9/11.*/
public class MyButton extends Button {private static final String TAG = "MainActivity";public MyButton(Context context) {super(context);}public MyButton(Context context, AttributeSet attrs) {super(context, attrs);}public MyButton(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);}@Overridepublic boolean dispatchTouchEvent(MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_DOWN: //按下的动作Log.d(TAG, "View dispatchTouchEvent ACTION_DOWN");break;case MotionEvent.ACTION_MOVE: //滑动的动作Log.d(TAG, "View dispatchTouchEvent ACTION_MOVE");break;case MotionEvent.ACTION_UP: //离开的动作Log.d(TAG, "View dispatchTouchEvent ACTION_UP");break;}return super.dispatchTouchEvent(event);}@Overridepublic boolean onTouchEvent(MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_DOWN: //按下的动作Log.d(TAG, "View onTouchEvent ACTION_DOWN");break;case MotionEvent.ACTION_MOVE: //滑动的动作Log.d(TAG, "View onTouchEvent ACTION_MOVE");break;case MotionEvent.ACTION_UP: //离开的动作Log.d(TAG, "View onTouchEvent ACTION_UP");break;}return true;}
}
上方代码中,将onTouchEvent方法的返回值修改为true(59行),表示这个子的view希望消费这个事件。
3. activity_main.xml:
<com.example.smyhvae.touchdemo.MyLinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".MainActivity"><com.example.smyhvae.touchdemo.MyButtonandroid:id="@+id/btn"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="按钮"/></com.example.smyhvae.touchdemo.MyLinearLayout>
上面的xml中,将我们自定义的MyLinearLayout和MyButton用上了。
4. MainActivity.java:
package com.example.smyhvae.touchdemo;import android.app.Activity;
import android.os.Bundle;
import android.widget.Button;public class MainActivity extends Activity {private static final String TAG = "MainActivity";private Button btn;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);btn = (Button) findViewById(R.id.btn); }
}
分析之前,我们先记住下面这句话:(记住这句话,分析下面的日志就好理解了)
在Android中,一切事件处理的开始都是从Down事件开始的,如何你处理了Down事件,其他的事件就都收不到了。
- 按照上面的代码,后台日志如下:
通过上图的箭头处可以看到,事件是传递给了子view去消费。
- 上面的代码中,将MyLinearLayout.java中onTouchEvent方法返回值修改为true,将MyButton.java中的onTouchEvent方法返回值修改为false,后台日志如下:
通过上图的箭头处可以看到,事件是传递给了子view,子view说它不消费了,于是又回传给父的ViewGroup,ViewGroup消费了这个事件。
- 上面的代码中,将MyLinearLayout.java中onTouchEvent方法返回值修改为true,将MyLinearLayout.java中onInterceptTouchEvent方法返回值修改为true,后台日志如下:
通过上图的箭头处可以看到,此时ViewGroup已经将事件拦截了,所以根本就不会传递给子的Veiw,父的ViewGroup自己把事件给消费掉了。
Android中事件分发机制的总结相关推荐
- Android之事件分发机制
本文主要包括以下内容 view的事件分发 viewGroup的事件分发 首先来看两张图 在执行touch事件时 首先执行dispatchTouchEvent方法,执行事件分发. 再执行onInterc ...
- Android 系统(218)---Android的事件分发机制以及滑动冲突的解决
Android的事件分发机制以及滑动冲突的解决 声明: 本文主要涉及VIew的事件分发与滑动冲突的解决,关于View的事件分发流程的部分内容参考自: Android事件分发机制详解:史上最全面.最 ...
- Android系统(120)-android的事件分发机制
android的事件分发机制 android的事件分发机制 比如说,现在你所在的公司中有一项任务被派发下来了,项目经理把项目交给你的老大,你的老大老大手下有很多人,看了看觉得你做很合适,把这个任务交给 ...
- Android的事件分发机制
前言 Android事件分发机制是Android开发者必须了解的基础 网上有大量关于Android事件分发机制的文章,但存在一些问题:内容不全.思路不清晰.无源码分析.简单问题复杂化等等 今天,我将把 ...
- android SDK-25事件分发机制--源码正确解析
android SDK-25事件分发机制–源码正确解析 Android 事件分发分为View和ViewGroup的事件分发,ViewGroup比View过一个拦截判断,viewgroup可以拦截事件, ...
- Android ViewGroup事件分发机制
理~ 1.案例 首先我们接着上一篇的代码,在代码中添加一个自定义的LinearLayout: [java] view plaincopy package com.example.zhy_event03 ...
- Android View 事件分发机制详解
想必很多android开发者都遇到过手势冲突的情况,我们一般都是通过内部拦截和外部拦截法解决此类问题.要想搞明白原理就必须了解View的分发机制.在此之前我们先来了解一下以下三个非常重要的方法: di ...
- 一文读懂Android View事件分发机制
Android View 虽然不是四大组件,但其并不比四大组件的地位低.而View的核心知识点事件分发机制则是不少刚入门同学的拦路虎.ScrollView嵌套RecyclerView(或者ListVi ...
- 完全理解Android TouchEvent事件分发机制(一)
本文能给你带来和解决一些你模糊的Touch事件概念及用法 1.掌握View及ViewGroup的TouchEvent事件分发机制 2.为解决View滑动冲突及点击事件消费提供支持 3.为你解决面试中的 ...
最新文章
- 物体的三维识别与6D位姿估计:PPF系列论文介绍(五)
- 快速傅里叶变换(FFT)算法【详解】
- android系统电量优化,基于Android系统网络耗电量优化方法的.pdf
- 揭秘阿里小蜜:基于检索模型和生成模型相结合的聊天引擎 | PaperDaily #25
- 永远不要去依赖别人_感悟人生的经典句子:不要轻易去依赖一个人
- Talib技术因子详解(九)
- 初级程序员考试大纲 (转)
- Mcafee之我见 * 一个木马引发的“麦咖啡”
- 职称计算机 frontpage 2003,职称计算机考试大纲:FrontPage2003网页设计与制作
- radasm相关问题
- 2的负x次幂图像_函数Y等于2的X次方图像怎么画?求过程
- 笔记本wifi共享出来能够连接但是没有网速
- web开发字体图标制作
- html前端显示tiff
- 伸缩式起重机的全球与中国市场2022-2028年:技术、参与者、趋势、市场规模及占有率研究报告
- 1-2 移动均线交叉策略1
- 台式机安装ubuntu 双系统(踩坑)
- html中蝴蝶飞飞怎么制作,【幼儿园折纸蝴蝶教案】手工折纸蝴蝶教案_幼儿园手工蝴蝶教案_亲亲宝贝网...
- 计算机管理恢复分区,如何在Windows中擦除恢复分区 | MOS86
- 2016年互联网面试总结