View的事件分发机制
来源:互联网 发布:二次元动态桌面软件 编辑:程序博客网 时间:2024/05/16 14:31
View的事件分发机制,也称为View的事件拦截机制,在说事件分发机制之前,需要对MotionEvent对象就行分析,也就是点击事件,MotionEvent是手指接触屏幕后所产生的一系列事件,典型的事件类型有如下几种:
ACTION_DOWN——手指刚接触屏幕;
ACTION_MOVE——手指在屏幕上移动:
ACTION_UP——手指在屏幕上松开的一瞬间。
点击屏幕后松开,事件顺序 DOWN->UP
点击屏幕滑动一会再松开,事件顺序为DOWN->MOVE->.....MOVE->UP
所谓点击事件的分发过程,其实就是对MotionEvent事件的分发过程,即当一个MotionEvent产生了以后,系统需要把这个事件传递给一个具体的View,而这个传递的过程就是分发过程。分发过程由三个很重要的方法来共同完成;分别是dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent。
public boolean dispatchTouchEvent(MotionEvent event)
用来进行事件的分发。从这个方法的名称就能看出来它的含义。如果事件能够传递给当前View,那么此方法一定会被调用,返回结果受当前View的onTouchEvent和下级View的dispatchTouchEvent方法的影响,表示是否消耗当前事件。
public boolean onInterceptTouchEvent(MotionEvent ev)
在上述方法内部调用,用来判断是否拦截某个事件,如果当前View拦截了某个事件,那么在同一个事件序列中,此方法不会被再次调用,返回结果表示是否拦截当前事件。
public boolean onTouchEvent(MotionEvent event)
在dispatchTouchEvent方法内部调用,用来处理点击事件,返回结果表示是否消耗当前事件,如果不消耗,则在同一个事件序列中,当前View无法再次接收到事件。
我们知道View结构是树形结构,也就是说,View可以放在ViewGroup里面,通过不同的组合来实现不同的样式。
现在假设一个情况:
一个View放在ViewGroup1中,这个ViewGroup1又放在另外一个ViewGroup2中,现在来分析点击事件的分发过程。
布局如上图所示。
ViewGroup1最外层的ViewGroup ViewGroup2中间的ViewGroup。View最底层的View。
他们代码都很简单,都只是重写了时间拦截核处理的几个方法。
代码贴上:
package com.qian;import android.annotation.SuppressLint;import android.content.Context;import android.util.AttributeSet;import android.util.Log;import android.view.MotionEvent;import android.widget.LinearLayout;public class ViewGroup1 extends LinearLayout{private String tag = "ViewGroup1";@SuppressLint("NewApi") public ViewGroup1(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);}public ViewGroup1(Context context, AttributeSet attrs) {super(context, attrs);}public ViewGroup1(Context context) {super(context);}@Overridepublic boolean onTouchEvent(MotionEvent event) {Log.i(tag, "onTouchEvent");return super.onTouchEvent(event);}@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {Log.i(tag, "onInterceptTouchEvent");return super.onInterceptTouchEvent(ev);}@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {Log.i(tag, "dispatchTouchEvent");return super.dispatchTouchEvent(ev);}}
package com.qian;import android.annotation.SuppressLint;import android.content.Context;import android.util.AttributeSet;import android.util.Log;import android.view.MotionEvent;import android.widget.LinearLayout;public class ViewGroup2 extends LinearLayout{private String tag = "ViewGroup2";@SuppressLint("NewApi") public ViewGroup2(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);}public ViewGroup2(Context context, AttributeSet attrs) {super(context, attrs);}public ViewGroup2(Context context) {super(context);}@Overridepublic boolean onTouchEvent(MotionEvent event) {Log.i(tag, "onTouchEvent");return super.onTouchEvent(event);}@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {Log.i(tag, "onInterceptTouchEvent");return super.onInterceptTouchEvent(ev);}@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {Log.i(tag, "dispatchTouchEvent");return super.dispatchTouchEvent(ev);}}
最后View1的代码:
package com.qian;import android.content.Context;import android.util.AttributeSet;import android.util.Log;import android.view.MotionEvent;import android.widget.TextView;public class View1 extends TextView{private String tag = "View1";public View1(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);}public View1(Context context, AttributeSet attrs) {super(context, attrs);}public View1(Context context) {super(context);}@Overridepublic boolean onTouchEvent(MotionEvent event) {Log.i(tag , "onTouchEvent");return super.onTouchEvent(event);}@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {Log.i(tag , "dispatchTouchEvent");return super.dispatchTouchEvent(ev);}}
layout布局:
<RelativeLayout xmlns: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" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context=".MainActivity" > <com.qian.ViewGroup1 android:layout_width="match_parent" android:layout_height="match_parent" android:background="#000000" ><com.qian.ViewGroup2 android:layout_width="200dp" android:layout_height="200dp" android:background="#ff0000" android:layout_gravity="center" android:gravity="center" > <com.qian.View1 android:layout_width="100dp" android:layout_height="100dp" android:background="#00ff00" android:layout_gravity="center" android:gravity="center" /></com.qian.ViewGroup2> </com.qian.ViewGroup1> </RelativeLayout>布局效果如图所示:
从上面的代码可以看出,ViewGroup级别比View的级别要高一点,比View多了方法。onInterceptTouchEvent,这个方法看名字就能猜到是事件拦截机制的核心方法,由于View中已经不能再放子元素了,所以它没有这个方法。
上面的代码只是添加了一些Log而已,布局改了一下北京颜色,主要是方便观察。现在点击一下中间的View1所在的绿色的区域,打印的Log如下:
从打印的Log可以得出事件分发或者说事件传递的顺序为,从dispatchTouchEvent和onInterceptTouchEvent方法看:
ViewGroup1——>ViewGroup2——>View1
而事件的处理顺序可以从onTouchEvent方法看出:
View1——>ViewGroup2——>ViewGroup1
事件传递的返回值表示 true,拦截 false 不拦截继续往下传递 默认为false
事件处理的返回值也类似, true 处理了 不用审核了 false 给上级处理 默认值false
事件传递过程,这里暂时先不管dispatchTouchEvent这个方法,这个方法一般不太会改这个,所以这里主要关注onInterceptTouchEvent方法,它的返回值就表示是否拦截事件。所以可以可以把上面的整个事件过程整理为下面一张图:
下面再改一下代码,加深理解.
在ViewGroup1中修改onInterceptTouchEvent,返回true
@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {Log.i(tag, "onInterceptTouchEvent");return true;//return super.onInterceptTouchEvent(ev);}
再来看一下Log:
跟我们设想的一样,ViewGroup1拦截了事件,那么ViewGroup2和View1就不能收到事件。所以Log中只能看到ViewGroup1的Log。ViewGroup1拦截事件的过程如下图所示:
同样,我们修改ViewGroup2的onInterceptTouchEvent,返回true ViewGroup1改回默认的false。打印的Log如下:
可以看到,这次是ViewGroup2拦截了事件。ViewGroup2拦截事件的过程如下图所示:
这样对事件的分发,拦截应该就比较清楚了,下面再看看对事件的处理,也就是onTouchEvent方法。这里最底层的View1,每次处理完任务也就是事件,都需要向上级报告,需要上级的确认,所以事件处理需要返回false。那么如果返回true,又是怎么样的呢?直接看Log打印。
@Overridepublic boolean onTouchEvent(MotionEvent event) {Log.i(tag , "onTouchEvent");return true;//return super.onTouchEvent(event);}
此时View1处理事件的过程如下:
如果如果ViewGroup的2onTouchEvent方法true。
总结一下,用一段伪代码
@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) { boolean isConsume = false; if(onInterceptTouchEvent(ev)) { isConsume = onTouchEvent(ev); } else { isConsume = dispatchTouchEvent(ev); } return isConsume; }
github源代码:https://github.com/dandelion133/viewDispatch
顺便提一下onTouchListener,当一个View需要处理事件时,如果它设置了onTouchListener,那么onTouchListener中的onTouch方法会被调用,这时事件如何处理还要看onTouch的返回值,如果onTouch返回false,当前View的onTouch方法会被调用,反之不会被调用,由此可见,onTouchListener的优先级比onTouchEvent优先级要高。
再说onClickListener,在onTouchEvent中,如果当前View设置了onClickListener,那么他的onClick方法会被调用,前提是onTouchEvent被调用,平时经常用得onClickListener,优先级最低,处于事件传递的尾端。
当一个点击事件产生后,它的传递过程遵循如下顺序:
Activity——>Window——>View
即事件总是先传递给Activity,Activity再传递给Window,最后Window再传递给顶级View。顶级View接受事件后,就会按照事件分发机制去分发事件。考虑一种情况,如果一个View的onTouchEvent返回false,那么它的父容器的onTouchEvent方法就会被调用,一次类推,如果所有元素的onTouchEvent都返回false,那么最终这个事件会传递给Activity去处理。这就是Activity为什么也有onTouchEvent方法和dispatchTouchEvent方法,但是它没有onInterceptTouchEvent方法,因为它处在整个事件传递的最高层,不需要onInterceptTouchEvent拦截事件这个方法。
总结一些View的分发机制的结论。
1)同一个事件序列是指触摸屏幕的那一刻起,到手指离开屏幕的那一刻结束,在这个过程中所产生的一系列事件,这个事件徐柳以down事件开始,中间含有数量不定的move事件,最终以up事件结束。
2)正常情况下,一个事件序列只能被一个View兰家且消耗,原因参考3),因为一旦一个元素拦截了某些事件,那么同一个事件序列的所有事件都会交给它处理,但不是绝对的,可以强制。
3)某个View一旦绝对拦截,那么这个一个事件序列都只能由它来处理,并且它的onInterceptTouchEvent方法不会被再次调用。
4)某个View一旦开始处理事件,如果不消耗down,onTouchEvent返回false,那么这个一个事件序列都不会交给它来处理,并且事件将重新交给它的父容器去处理。
5)如果所有的View都不处理事件,这个事件最终会小时。
6)ViewGroup默认不拦截任何事件。
7)View没有onInterceptTouchEvent方法,也就是不拦截事件,只要有时间传递给它,它的onTouchEvent方法就会被调用。
8)View的onTouchEvent默认都会消耗事件,也就是onTouchEvent方法返回true。除非它是不可点击的(clickable和longclickable同时为false)。View的longclickable默认为false,clickable要看情况,比如Button默认是true,也就是Button默认会消耗事件,onTouchEvent方法返回true。但是TextView不是不可点击的,onTouchEvent方法返回false
这里说明一下为什么最上面View1采用继承TextView,因为TextView不可点击,方便演示。
9)View的enable属性不影响onTouchEvent的默认返回值。哪怕一个View是disable状态的。主要clickable和longclickable有一个为true,那么它的onTouchEvent方法就会返回true。
10)onClick会发生的前提是当前View是可点击的,并且它收到了down和up事件。
11)事件传递过程是由外向内的,即事件总是先传递给父元素,然后再由父元素传递给子View,但是子元素可以通过requestDisallowTouchEvent方法干扰父元素的分发过程,但是down事件除外。
- View 的事件分发机制
- View的事件分发机制。
- View的事件分发机制
- View的事件分发机制
- view的事件分发机制
- View的事件分发机制
- View的事件分发机制
- View的事件分发机制
- view的事件分发机制
- View的事件分发机制
- View的事件分发机制
- View事件的分发机制
- View的事件分发机制
- View的事件分发机制
- View的事件分发机制
- View 的事件分发机制
- View的事件分发机制
- View的事件分发机制
- Eclipse中SVN的安装步骤(两种)和使用方法
- java核心机制之JVM
- jQuery.extend() 函数详解
- <OJ_Sicily>Longest Common Subsequence
- linux nc命令常用用法
- View的事件分发机制
- MySql-日志详解
- 在浏览器中输入www.baidu.com后执行的全部过程
- Acdream 1214 Nice Patterns Strike Back (矩阵乘法 + 状态压缩)
- iOS_高效开发之道
- 初识Swift
- iOS开发:==、isEqual与isEqualToString判断是否相等
- 苹果公司6月1日后发布的应用必须支持IPv6-Only网络的解决办法(底层socket连接的IPv6支持方案)
- Halcon MFC混合编程-图像显示随鼠标滚动放大缩小