Android 事件分发机制总结
2016-01-21 09:17
483 查看
前言
android事件分发机制是一个非常重要但是比较难理解的知识点,大部分自定义控件等功能都用到了事件分发机制,搞懂这方面的知识还是很有必要的,所有一直想总结一下android事件分发机制的原理,一来方便自己以后查看,二来加深自己的印象。网上这方面资料也比较多,我也参考了一下大神的文章,自己总结一下。分析
什么是事件分发?我们在操作手机屏幕的时候,每一个点击、移动动作都看做是一个事件,android屏幕捕捉到这些事件后,把这些事件一层一层的分发到下面的控件,由控件来决定是否对这个事件进行响应处理。
那么android是怎么进行事件分发的呢?
整个的一个Window页面,最顶层是DecorView,DecorView只有一个LinearLayout,是一个ViewGroup,android控件都是继承View的,ViewGroup也是继承View的,所以我们屏幕的Touch操作,其实就是调用内部的ViewGroup的Touch事件。
View在ViewGroup中,ViewGroup也可以在其他ViewGroup中:
上面就是ViewGroup结构图,在结束时间分发前,我们先要要了解View和ViewGroup的相关touch事件方法:
touch事件方法:
dispatchTouchEvent(MotionEvent ev):事件分发,以隧道方式(从根元素依次往下传递直到最内层子元素或在中间某一元素中由于某一条件停止传递)将事件传递给最外层
View 的 dispatchTouchEvent(MotionEvent ev) 方法,并由该 View 的 dispatchTouchEvent(MotionEvent ev) 方法对事件进行分发。dispatchTouchEvent 的事件分发逻辑如下:
如果
return true,事件会分发给当前 View并由 dispatchTouchEvent 方法进行消费,同时事件会停止向下传递;
如果
return false,事件分发给下一层的View,如果下层没有View,就交给父View的onTouchEvent进行处理
如果返回系统默认的
super.dispatchTouchEvent(ev),如果是ViewGroup事件会自动的分发给当前Viewgroup 的
onInterceptTouchEvent 方法,如果是View的话分发给当前View的onTouchEvent 进行处理
onInterceptTouchEvent(MotionEvent ev):事件拦截,只有ViewGroup有这个方法,View是没用的,因为View是最后一层,不用拦截。onInterceptTouchEvent的处理逻辑:
如果
onInterceptTouchEvent 返回 true,则表示将事件进行拦截,并将拦截到的事件交由当前 View 的 onTouchEvent 进行处理;
如果
onInterceptTouchEvent 返回 false,则表示将事件放行,当前 View 上的事件会被传递到子 View 上,再由子 View 的 dispatchTouchEvent 来开始这个事件的分发;
如果
onInterceptTouchEvent 返回 super.onInterceptTouchEvent(ev),事件默认不被拦截,往下传递。
onTouchEvent(MotionEvent ev):
事件响应,ViewGroup在
dispatchTouchEvent 返回 super.dispatchTouchEvent(ev) 并且 onInterceptTouchEvent 返回 true 或View在dispatchTouchEvent 返回super.onInterceptTouchEvent(ev) 的情况下 onTouchEvent 会被调用。onTouchEvent
的事件响应逻辑如下:
如果事件传递到当前 View 的 onTouchEvent 方法,而该方法返回了 false,那么这个事件会从当前 View 向上传递,并且都是由上层 View 的 onTouchEvent 来接收,如果传递到上面的 onTouchEvent 也返回 false,这个事件就会“消失”,而且接收不到下一次事件。
如果返回了 true 则会接收并消费该事件。
如果返回 super.onTouchEvent(ev) 默认处理事件的逻辑和返回 false 时相同。
requestDisallowInterceptTouchEvent(boolean flag):设置事件是否可以拦截,只有在ViewGroup中才有这个方法,View中没有。我们在子View的dispatchTouchEvent中添加getParent().requestDisallowInterceptTouchEvent(true)的,就可以防止我们View的事件被上层ViewGroup拦截。
介绍了所有有关的Touch方法,那我们根据上面的图来了解一下流程:
当一个Touch事件(触摸事件为例)到达根节点,即Acitivty的ViewGroup时,它会依次下发,下发的过程是调用子View(ViewGroup)的dispatchTouchEvent方法实现的。简单来说,就是ViewGroup遍历它包含着的子View,调用每个View的dispatchTouchEvent方法,而当子View为ViewGroup时,又会通过调用ViwGroup的dispatchTouchEvent方法继续调用其内部的View的dispatchTouchEvent方法。上述例子中的消息下发顺序是这样的:1-2-5-6-7-3-4。dispatchTouchEvent方法只负责事件的分发,它拥有boolean类型的返回值,当返回为true时,顺序下发会中断。在上述例子中如果5的dispatchTouchEvent返回结果为true,那么6-7-3-4将都接收不到本次Touch事件。这个就是dispatchTouchEvent事件的分发,其它方法的流程也是这样类似的。
案例
我们来写一个案例来加深我们的理解,自定义一个View和ViewGroup来看看它们之间事件是怎么进行分发的。自定义的View:
package com.test.touch; import android.content.Context; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.widget.Button; /** * Created by fuweiwei on 2016/1/20. */ public class MyButton extends Button { private static final String TAG="MyButton"; public MyButton(Context context) { super(context); } public MyButton(Context context, AttributeSet attrs) { super(context, attrs); } public MyButton(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override public boolean onTouchEvent(MotionEvent event) { int action = event.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: Log.v(TAG, "onTouchEvent;----> ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.v(TAG, "onTouchEvent;----> ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.v(TAG, "onTouchEvent;----> ACTION_UP"); break; default: break; } return super.onTouchEvent(event); } @Override public boolean dispatchTouchEvent(MotionEvent event) { Log.v(TAG, "dispatchTouchEvent"); return super.dispatchTouchEvent(event); } }
自定义的ViewGroup:
package com.test.touch; import android.content.Context; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.widget.LinearLayout; /** * Created by fuweiwei on 2016/1/20. */ public class MyLinearLayout extends LinearLayout { private static final String TAG="MyLinearLayout"; public MyLinearLayout(Context context) { super(context); } public MyLinearLayout(Context context, AttributeSet attrs) { super(context, attrs); } public MyLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { Log.v(TAG, "onInterceptTouchEvent"); return super.onInterceptTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent event) { int action = event.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: Log.v(TAG, "onTouchEvent;----> ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.v(TAG, "onTouchEvent;----> ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.v(TAG, "onTouchEvent;----> ACTION_UP"); break; default: break; } return super.onTouchEvent(event); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { Log.v(TAG, "dispatchTouchEvent"); return super.dispatchTouchEvent(ev); } @Override public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { Log.v(TAG, "requestDisallowInterceptTouchEvent"); super.requestDisallowInterceptTouchEvent(disallowIntercept); } }
我们覆写了View和ViewGroup的所有Touch事件方法,然后我们分别设置监听事件:
package com.test.touch; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.View; public class MainActivity extends AppCompatActivity { private MyButton mBtn; private MyLinearLayout mLin; private static final String TAG="MyButton"; private static final String TAG1="MyLinearLayout"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mLin = (MyLinearLayout) findViewById(R.id.lin); mLin.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Log.v(TAG1, "OnClick"); } }); mBtn = (MyButton) findViewById(R.id.btn); mBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Log.v(TAG, "OnClick"); } }); /* mBtn.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { Log.v(TAG, "onTouch"); return false; } }); mBtn.setOnLongClickListener(new View.OnLongClickListener() { @Override public boolean onLongClick(View v) { Log.v(TAG, "OnLong"); return false; } });*/ } }当我们点击button按钮时,后台输出的log日志:
01-21 13:48:43.790 18642-18642/com.test.touch V/MyLinearLayout: dispatchTouchEvent: 01-21 13:48:43.800 18642-18642/com.test.touch V/MyLinearLayout: onInterceptTouchEvent: 01-21 13:48:43.800 18642-18642/com.test.touch V/MyButton: dispatchTouchEvent 01-21 13:48:43.800 18642-18642/com.test.touch V/MyButton: onTouchEvent;----> ACTION_DOWN 01-21 13:48:43.870 18642-18642/com.test.touch V/MyLinearLayout: dispatchTouchEvent: 01-21 13:48:43.870 18642-18642/com.test.touch V/MyLinearLayout: onInterceptTouchEvent: 01-21 13:48:43.870 18642-18642/com.test.touch V/MyButton: dispatchTouchEvent 01-21 13:48:43.880 18642-18642/com.test.touch V/MyButton: onTouchEvent;----> ACTION_UP 01-21 13:48:43.880 18642-18642/com.test.touch V/MyButton: OnClick
我们可以清楚的看到事件分发的流程:
MyLinearLayout的dispatchTouchEvent
-> MyLinearLayout的onInterceptTouchEvent -> MyButton的dispatchTouchEvent ->Mybutton的onTouchEvent
可以看出,在View上触发事件,最先捕获到事件的为View所在的ViewGroup,然后才会到View自身~,响应View的点击事件。而MyLinearLayout的点击事件没有响应,是因为Mybutton处理了这个点击事件。
当我们把MyButton中的dispatchTouchEvent()方法返回false时:
我们再点击button时,log日志输出:
01-21 13:56:39.050 6422-6422/com.test.touch V/MyLinearLayout: dispatchTouchEvent 01-21 13:56:39.050 6422-6422/com.test.touch V/MyLinearLayout: onInterceptTouchEvent 01-21 13:56:39.050 6422-6422/com.test.touch V/MyButton: dispatchTouchEvent 01-21 13:56:39.050 6422-6422/com.test.touch V/MyLinearLayout: onTouchEvent;----> ACTION_DOWN 01-21 13:56:39.110 6422-6422/com.test.touch V/MyLinearLayout: dispatchTouchEvent 01-21 13:56:39.110 6422-6422/com.test.touch V/MyLinearLayout: onTouchEvent;----> ACTION_UP 01-21 13:56:39.110 6422-6422/com.test.touch V/MyLinearLayout: OnClick
这时没有执行MyButton的onTouchEvent
事件,没有我们没有吧时间传递给MyButton,所有由上层的View也就是MyLinearLayout执行onTouchEvent
响应点击事件。
当我们把MyLinearLayout中onInterceptTouchEvent
返回true时,拦截时间,效果也是一样的:
我们再点击button时,log日志输出:
01-21 14:03:14.670 15599-15599/com.test.touch V/MyLinearLayout: dispatchTouchEvent 01-21 14:03:14.670 15599-15599/com.test.touch V/MyLinearLayout: onInterceptTouchEvent 01-21 14:03:14.670 15599-15599/com.test.touch V/MyLinearLayout: onTouchEvent;----> ACTION_DOWN 01-21 14:03:14.730 15599-15599/com.test.touch V/MyLinearLayout: dispatchTouchEvent 01-21 14:03:14.730 15599-15599/com.test.touch V/MyLinearLayout: onTouchEvent;----> ACTION_UP 01-21 14:03:14.740 15599-15599/com.test.touch V/MyLinearLayout: OnClick这时没有执行MyButton的任何Touch方法,因为被MyLinearLayout拦截了,自己处理了。
通过案例是不是很清晰了,我们可以多多试试其他的操作,可以加深我们对事件分发直接的理解。下面有我测试的源码,代码很简单。这里向大家推荐下鸿洋大神的博客:http://blog.csdn.net/lmj623565791/article/details/39102591,他讲的很深,我也是学习了他的然后自己总结了,方便以后学习。
源码下载
相关文章推荐
- androidstudio 优化gradle编译效率
- AndroidAnnotations使用详解
- Android各版本代号、版本号、API/NDK级别、发布时间及市场份额
- Android中通过代码改变系统文件都写权限
- Android编程调节屏幕亮度(背景灯)及保持背景灯常亮的方法
- gradle DSL method not found: android()
- Android四大基本组件介绍与生命周期
- 优雅的项目配置--常用库和版本管理
- Meteor - Android 打包
- 7种形式的Android Dialog使用举例
- Android项目简介
- Android 回调机制
- Android布局中gravity和layout_gravity的区别
- 开篇
- android Studio 快捷键
- Android Activity与Fragment 数据交互
- Android EditText常见方法总结
- Android 开发中常见的公共方法总结
- Android 自定义 spinner (背景、字体颜色)
- Android Fragment