最近在看android开发艺术一书,因此对android 事件机制有更深层次的理解,在此记录一遍
本篇不涉及过多源码,主要分析整个事件流程,下一篇会从源码角度进行分析,所以本篇是下一篇的基础。
探讨Android事件传递机制前,务必明确android的两大基础控件类型:View和ViewGroup。
View即普通的控件,没有子布局的,如Button、TextView. ViewGroup继承自View;
ViewGroup控件,有子控件,如Linearlayout、Listview等。
而事件即MotionEvent,最重要的有3个:
(1)MotionEvent.ACTION_DOWN 按下View,是所有事件的开始
(2)MotionEvent.ACTION_MOVE 滑动事件
(3)MotionEvent.ACTION_UP 事件的结束与ACTION_DOWN 对应
(一)两类控件的主要事件函数
1.对应View类控件事件主要相关函数有两个:
public boolean dispatchTouchEvent(MotionEvent ev) 这个方法用来分发TouchEvent
public boolean onTouchEvent(MotionEvent ev) 这个方法用来处理TouchEvent
2.对应的groupView类控件主要相关函数有三个:
public boolean dispatchTouchEvent(MotionEvent ev) 这个方法用来分发TouchEvent
public boolean onInterceptTouchEvent(MotionEvent ev) 这个方法用来拦截TouchEvent
public boolean onTouchEvent(MotionEvent ev) 这个方法用来处理TouchEvent
因此,由此可知View控件比groupView类的控件少了一个onInterceptTouchEvent(MotionEvent ev)的拦截方法,这是因为View类没有子控件,没有拦截向子类传递的事件的说法,而groupView有子类,可判断是否拦截向子类传递的事件。
3.对MotionEvent说明一下:
参数MotionEvent为手机屏幕触摸事件封装类的对象,其中封装了该事件的所有信息,例如触摸的位置、触摸的类型以及触摸的时间等。该对象会在用户触摸手机屏幕时被创建。
4.下面对三个事件主要方法说明一下:
手机屏幕事件的处理方法onTouchEvent。该方法在View类中的定义,并且所有的View子类全部重写了该方法,应用程序可以通过该方法处理手机屏幕的触摸事件。该方法的签名如下所示。
public boolean onTouchEvent(MotionEvent event) {
return super.onTouchEvent(event);
}
返回值:该方法的返回值机理与键盘响应事件的相同,同样是当已经完整地处理了该事件且不希望其他回调方法再次处理时返回true,否则返回false
public boolean onInterceptTouchEvent(MotionEvent ev) {
return super.onInterceptTouchEvent(ev);
}
onInterceptTouchEvent()用于处理事件并改变事件的传递方向,而决定传递方向的是返回值。
返回为false时事件会传递给子控件的dispatchTouchEvent(),再由子类的dispatchTouchEvent()对事件进行分发
返回为true时事件会传递给当前控件的onTouchEvent(),而不在传递给子控件,这就是所谓的Intercept(截断)。
而且一旦拦截了事件,此方法不会被当前view(即viewgroup)再次调用。
public boolean dispatchTouchEvent(MotionEvent ev) {
return super.dispatchTouchEvent(ev);
}
dispatchTouchEvent()主要负责对onTouchEvent事件进行分发,本身不处理onTouchEvent事件。而分发的方式由其返回值决定
返回值为true时,则将事件交给当前view本身的onTouchEvent处理。
返回值为false时,则将事件交给当前view本身的onInterceptTouchEvent()处理,再由其决定事件传递的方向
弄清楚这个三个方法后,我们再来初步分析一下事件的传递机制。
(二)初步了解触摸事件的传递顺序(如果对窗口的创建过程有了解就更好理解些)
一个触摸事件首先是在硬件层面触发,然后逐层传递到软件直至我们的app,前面的细节一般来说不用了解,我们讨论的事件入口从Activity开始。
触摸事件的传递顺序是:
而触摸事件的处理顺序则刚好相反(onTouchEvent):
总体概况一下就是:
当TouchEvent发生时,首先Activity将TouchEvent通过getWindow().superDispatchTouchEvent(event)把事件分发到当前活动窗口(PhoneWindow),之后是顶级窗口的DecorView,调用了DecorView的dispatchTouchEvent,DecorView继承自ViewGroup,所以这里实际上就进入了ViewGroup层面的dispatchTouchEvent,然后由dispatchTouchEvent 方法进行分发,如果dispatchTouchEvent返回true ,则交给当前这个view的onTouchEvent处理,如果dispatchTouchEvent返回 false 则交给当前这个 view 的 interceptTouchEvent 方法来决定是否要拦截这个事件,如果 interceptTouchEvent 返回 true ,也就是拦截掉了,则交给当前这个view的onTouchEvent来处理,如果 interceptTouchEvent 返回 false ,那么就继续传递给子view ,由子 view 的 dispatchTouchEvent 再来开始这个事件的分发。如果事件传递到某一层的子 view 的 onTouchEvent 上了,这个方法返回了 false ,那么这个事件会从这个 view 往上传递,而且都是 onTouchEvent 来接收。而如果传递到最上面的 onTouchEvent 也返回 false 的话,这个事件就会“消失”, 而且接收不到下一次事件。(这指的是 down 到 up 之间的一系列事件)。Touchevent 中,返回值是 true ,则说明消耗掉了这个事件。
这样说可能还有点抽象,这里我们来换种思路:
假如点击事件是一个难题,这个难题最终被上级领导分给了一个工程师主管去处理(事件分发),然而主管很忙不想处理(interceptTouchEvent返回false不拦截),主管把任务分给工程师,结果工程师没有搞定(onTouchEvent返回了false),那么只能交给工程师-主管处理(上级的onTouchEvent被调用),如果主管也没搞定,那么只能交给再往上的上级去解决,就这样一层层往上抛。所以事件传递处理大概也是这样的流程。
(三)初步了解触摸事件的传递顺序后,我们通过实例来进一步分析上面事件传递过程
首先创建一个EventButton类继承Button:
package com.zejian.android_event; import android.content.Context; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.widget.Button; /** * @author : zejian * @time : 2016年1月7日 下午10:10:34 * @email : [email protected] * @description : */ public class EventButton extends Button { public EventButton(Context context) { super(context); } public EventButton(Context context, AttributeSet attrs) { super(context, attrs); } public EventButton(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { String str = "ACTION_DOWN"; switch (ev.getAction()){ case MotionEvent.ACTION_DOWN: str = "ACTION_DOWN"; break ; case MotionEvent.ACTION_MOVE: str = "ACTION_MOVE"; break; case MotionEvent.ACTION_UP: str = "ACTION_UP"; break; } Log.v("wzj", "=====View-----=====dispatchTouchEvent---========"+str) ; return super.dispatchTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent event) { String str = "ACTION_DOWN"; switch (event.getAction()){ case MotionEvent.ACTION_DOWN: str = "ACTION_DOWN"; break ; case MotionEvent.ACTION_MOVE: str = "ACTION_MOVE"; break; case MotionEvent.ACTION_UP: str = "ACTION_UP"; break; } Log.v("wzj","=====View-----=====onTouchEvent---------========"+str) ; // return super.onTouchEvent(event); return true; } }在这个类中并没有复杂的逻辑,我们仅仅重写了dispatchTouchEvent()方法和onTouchEvent()方法。
接着我们再创建一个GroupViewEvent继承于LinearLayout:
<span style="font-size:14px;">package com.zejian.android_event; import android.annotation.SuppressLint; import android.content.Context; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.widget.LinearLayout; /** * @author : zejian * @time : 2016年1月7日 下午10:11:45 * @email : [email protected] * @description : */ public class GroupViewLayout extends LinearLayout { public GroupViewLayout(Context context) { super(context); } public GroupViewLayout(Context context, AttributeSet attrs) { super(context, attrs); } @SuppressLint("NewApi") public GroupViewLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { boolean handle = false; String str = "ACTION_DOWN"; switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: str = "ACTION_DOWN"; // handle = true ; break; case MotionEvent.ACTION_MOVE: str = "ACTION_MOVE"; // handle = true ; break; case MotionEvent.ACTION_UP: str = "ACTION_UP"; handle = true; break; } Log.v("wzj", "=====ViewGroup=====onInterceptTouchEvent========" + str); // if (handle) { // return handle; // } return super.onInterceptTouchEvent(ev); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { boolean handle = false; String str = "ACTION_DOWN"; switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: str = "ACTION_DOWN"; // handle = true ; break; case MotionEvent.ACTION_MOVE: str = "ACTION_MOVE"; // handle = true ; break; case MotionEvent.ACTION_UP: str = "ACTION_UP"; // handle = true ; break; } Log.v("wzj", "=====ViewGroup=====dispatchTouchEvent---========" + str); // if(handle){ // return handle ; // } return super.dispatchTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent event) { String str = "ACTION_DOWN"; boolean handle = false; switch (event.getAction()) { case MotionEvent.ACTION_DOWN: str = "ACTION_DOWN"; handle = true; break; case MotionEvent.ACTION_MOVE: str = "ACTION_MOVE"; // handle = true ; break; case MotionEvent.ACTION_UP: str = "ACTION_UP"; // handle = true ; break; } Log.v("wzj", "=====ViewGroup=====onTouchEvent---------========" + str); // if(handle){ // return handle ; // } return super.onTouchEvent(event); } }</span>
这是一个布局类,将存放上面两个自定义的EventButton和EventTextView的控件;
xml布局文件如下:
<com.zejian.android_event.GroupViewLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:layout_gravity="center_horizontal" > <com.zejian.android_event.EventButton android:layout_width="120dp" android:layout_height="60dp" android:text="eventBut"/> </com.zejian.android_event.GroupViewLayout>
最后就是MainActivity类了:
package com.zejian.android_event; import android.app.Activity; import android.os.Bundle; import android.util.Log; import android.view.MotionEvent; public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } /** * 主要负责对ontouch事件进行分发,本身不处理ontouch事件。而分发的方式由其返回值决定 * 返回值: * 返回值为true时,则将事件交给当前view本身的onTouchEvent处理。 * 返回值为false时,则将事件交给当前view本身的onInterceptTouchEvent()处理, * 再由其决定事件传递的方向 */ @Override public boolean dispatchTouchEvent(MotionEvent ev) { String tag = "ACTION_DOWN"; switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: tag = "ACTION_DOWN"; break; case MotionEvent.ACTION_MOVE: tag = "ACTION_MOVE"; break; case MotionEvent.ACTION_UP: tag = "ACTION_UP"; break; } Log.v("wzj","=====Activity =====dispatchTouchEvent---========"+tag) ; return super.dispatchTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent event) { String tag = "ACTION_DOWN"; boolean handle = false ; switch (event.getAction()){ case MotionEvent.ACTION_DOWN: tag = "ACTION_DOWN"; break ; case MotionEvent.ACTION_MOVE: tag = "ACTION_MOVE"; handle = true ; break; case MotionEvent.ACTION_UP: tag = "ACTION_UP"; break; } Log.v("wzj","=====Activity =====onTouchEvent---------========"+tag ) ; return super.onTouchEvent(event); } }
Activity 的 dispatchTouchEvent的事件分发给ViewGroup(暂时忽略窗口PhoneWindow和DecorView的传递),当dispatchTouchEvent返回false时,ViewGroup再通过事件拦截onInterceptTouchEvent(只有 ViewGroup 才有此方法)来判断,是否拦截事件,如果返回false也就是没有拦截,则将事件分发给 子View 的分发机制dispatchTouchEvent;子View 获取到事件后,就开始调用 onTouchEvent 方法,如果 View 的 onTouchEvent 返回false,即未把事件消费掉,则把事件有传递给 父类ViewGroup 的 onTouchEvent,同理,ViewGroup 未把事件消费掉,继续往下级传到了 Activity 的 onTouchEvent 事件处理中。(当然这里只是分析ACTION_DOWM事件,剩余ACTION_MOVE 和ACTION_UP也是类似的情况),为了更好的理解下面我给出图解:
通过上图,我们可以得出以下几点结论:
1、Android 事件传递是层级传递的;
2、dispatchTouchEvent从底层向上层传递,而onTouchEvent刚好相反;
3、onInterceptTouchEvent返回为 true 时,将执行同层级的onTouchEvent,而dispatchTouchEvent和onTouchEvent返回 true 时,将终止事件的传递。
4.如果事件在view最终都未被消费(所谓的消费是指view未处理该事件)的话,那么将交给activity处理
好了说完了上面,我们再来提一个细节性的问题:上面最后两行的信息log,然后大家再来看一组调试信息:
事实上这是代码中ViewGroup 和 View的onTouchEvent事件在action 为ACTION_DOWN时返回false也就是未消费掉事件,因此最终都会返回给Activity,所以这里可以看到只有在 ACTION_DOWN 的时候是按照我们分析的流程图来执行,而 ACTION_MOVE、ACTION_UP 都只是执行了 Activity 的dispatchTouchEvent和onTouchEvent,但这又是为什么?事实上,上面那张图的流程是没有问题的,只是有些事件被拦截或消费掉。
ViewGroup 和 View的dispatchTouchEvent分发事件在 action 为ACTION_DOWN时没有被拦截且onTouchEvent的处理事件在 action 为ACTION_DOWN时未被消费,当 action 为ACTION_MOVE或ACTION_UP时,那么ViewGroup 和 View的dispatchTouchEvent和onTouchEvent将不会再次被调用,这就好比,上级把难题交给工程师处理,结果工程师没搞定,那么短期内这个问题就不会再次交给这个工程师处理,这样很好理解把,最终领导自己(activity)处理这一序列难题,因此dispatchTouchEvent和onTouchEvent只有在 activity 被调用。记住ACTION_DOWN,ACTION_MOVE,ACTION_UP一般都是一个序列操作。
下面来看一组log信息,(EventButton)View的onTouchEvent事件在action 为ACTION_DOWN时返回true也就是消费掉事件:
因此,现在我们可以看到无论是ACTION_DOWN或者ACTION_MOVE还是ACTION_UP都是按上面那么图的顺序走的,唯一不同的是在子类View的时候onTouchEvent就已经把事件消费了,所以无需再沿路返回给父类,因此Activity或者GroupView自然也不会调用onTouchEvent方法;相信现在对事件的传递都已经有比较清晰的了解了,好了,事件机制入门篇就到此完结了。下一篇我们将从源码的角度入手分析事件监听机制的整个流程。