重写控件的时候,总体来说有2种情况,一个是继承View机器子类(不包括ViewGroup及其子类),如果需要处理手势,则重写View的onTouchEvent();另一个是继承ViewGroup及其子类的,如果需要处理手势,则需要重写onInterceptTouchEvent()和onTouchEvent()。
面试的时候曾被问过这两个方法的调用顺序,当时也只是知道onInterceptTouchEvent在前,具体的执行过程,相互的影响却不知道.今天写了一个小demo详细研究了一下这两个方法之间的关系.
首先上代码:
主activity:InterceptTouchStudyActivity
- package com.touchstudy;
- import android.app.Activity;
- import android.os.Bundle;
- import android.widget.TextView;
- public class InterceptTouchStudyActivity extends Activity {
- TextView tv;
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- }
- }
布局文件main.xml
- <?xml version="1.0" encoding="utf-8"?>
- <com.touchstudy.LayoutView1 xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:orientation="vertical" >
- <com.touchstudy.LayoutView2
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:gravity="center"
- android:orientation="vertical" >
- <com.touchstudy.MyTextView
- android:id="@+id/tv"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:background="#FFFFFF"
- android:text="AB"
- android:textColor="#0000FF"
- android:textSize="40sp"
- android:textStyle="bold" />
- </com.touchstudy.LayoutView2>
- </com.touchstudy.LayoutView1>
类LayoutView1
- package com.touchstudy;
- import android.content.Context;
- import android.util.AttributeSet;
- import android.util.Log;
- import android.view.MotionEvent;
- import android.widget.LinearLayout;
- public class LayoutView1 extends LinearLayout {
- private final String TAG = "LayoutView1";
- public LayoutView1(Context context, AttributeSet attrs) {
- super(context, attrs);
- Log.d(TAG, TAG);
- }
- @Override
- public boolean onInterceptTouchEvent(MotionEvent ev) {
- int action = ev.getAction();
- switch (action) {
- case MotionEvent.ACTION_DOWN:
- Log.d(TAG, "onInterceptTouchEvent1 action:ACTION_DOWN");
- // return true;
- break;
- case MotionEvent.ACTION_MOVE:
- Log.d(TAG, "onInterceptTouchEvent1 action:ACTION_MOVE");
- break;
- case MotionEvent.ACTION_UP:
- Log.d(TAG, "onInterceptTouchEvent1 action:ACTION_UP");
- break;
- case MotionEvent.ACTION_CANCEL:
- Log.d(TAG, "onInterceptTouchEvent1 action:ACTION_CANCEL");
- break;
- }
- boolean b = false;
- Log.d(TAG, "onInterceptTouchEvent1 return:"+b);
- return b;
- }
- @Override
- public boolean onTouchEvent(MotionEvent ev) {
- int action = ev.getAction();
- switch (action) {
- case MotionEvent.ACTION_DOWN:
- Log.d(TAG, "onTouchEvent1 action:ACTION_DOWN");
- break;
- case MotionEvent.ACTION_MOVE:
- Log.d(TAG, "onTouchEvent1 action:ACTION_MOVE");
- break;
- case MotionEvent.ACTION_UP:
- Log.d(TAG, "onTouchEvent1 action:ACTION_UP");
- break;
- case MotionEvent.ACTION_CANCEL:
- Log.d(TAG, "onTouchEvent1 action:ACTION_CANCEL");
- break;
- }
- boolean b = false;
- Log.d(TAG, "onTouchEvent1 return:"+b);
- return b;
- }
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
- // TODO Auto-generated method stub
- super.onLayout(changed, l, t, r, b);
- }
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- // TODO Auto-generated method stub
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- }
- }
类LayoutView2
- package com.touchstudy;
- import android.content.Context;
- import android.util.AttributeSet;
- import android.util.Log;
- import android.view.MotionEvent;
- import android.widget.LinearLayout;
- public class LayoutView2 extends LinearLayout {
- private final String TAG = "LayoutView2";
- public LayoutView2(Context context, AttributeSet attrs) {
- super(context, attrs);
- Log.d(TAG, TAG);
- }
- @Override
- public boolean onInterceptTouchEvent(MotionEvent ev) {
- int action = ev.getAction();
- switch (action) {
- case MotionEvent.ACTION_DOWN:
- Log.d(TAG, "onInterceptTouchEvent2 action:ACTION_DOWN");
- break;
- case MotionEvent.ACTION_MOVE:
- Log.d(TAG, "onInterceptTouchEvent2 action:ACTION_MOVE");
- break;
- case MotionEvent.ACTION_UP:
- Log.d(TAG, "onInterceptTouchEvent2 action:ACTION_UP");
- break;
- case MotionEvent.ACTION_CANCEL:
- Log.d(TAG, "onInterceptTouchEvent2 action:ACTION_CANCEL");
- break;
- }
- boolean b = false;
- Log.d(TAG, "onInterceptTouchEvent2 return:"+b);
- return b;
- }
- @Override
- public boolean onTouchEvent(MotionEvent ev) {
- int action = ev.getAction();
- switch (action) {
- case MotionEvent.ACTION_DOWN:
- Log.d(TAG, "onTouchEvent2 action:ACTION_DOWN");
- break;
- case MotionEvent.ACTION_MOVE:
- Log.d(TAG, "onTouchEvent2 action:ACTION_MOVE");
- break;
- case MotionEvent.ACTION_UP:
- Log.d(TAG, "onTouchEvent2 action:ACTION_UP");
- break;
- case MotionEvent.ACTION_CANCEL:
- Log.d(TAG, "onTouchEvent2 action:ACTION_CANCEL");
- break;
- }
- boolean b = false;
- Log.d(TAG, "onTouchEvent2 return:"+b);
- return b;
- }
- }
类MyTextView
- package com.touchstudy;
- import android.content.Context;
- import android.util.AttributeSet;
- import android.util.Log;
- import android.view.MotionEvent;
- import android.view.View;
- import android.widget.TextView;
- public class MyTextView extends TextView {
- private final String TAG = "MyTextView";
- public MyTextView(Context context, AttributeSet attrs) {
- super(context, attrs);
- Log.d(TAG, TAG);
- }
- @Override
- public boolean onTouchEvent(MotionEvent ev) {
- int action = ev.getAction();
- switch (action) {
- case MotionEvent.ACTION_DOWN:
- Log.d(TAG, "onTouchEvent_TextView action:ACTION_DOWN");
- break;
- case MotionEvent.ACTION_MOVE:
- Log.d(TAG, "onTouchEvent_TextView action:ACTION_MOVE");
- break;
- case MotionEvent.ACTION_UP:
- Log.d(TAG, "onTouchEvent_TextView action:ACTION_UP");
- break;
- case MotionEvent.ACTION_CANCEL:
- Log.d(TAG, "onTouchEvent_TextView action:ACTION_CANCEL");
- break;
- }
- boolean b = false;
- Log.d(TAG, "onTouchEvent_TextView return:"+b);
- return b;
- }
- public void onClick(View v) {
- Log.d(TAG, "onClick");
- }
- public boolean onLongClick(View v) {
- Log.d(TAG, "onLongClick");
- return false;
- }
- }
onInterceptTouchEvent()是ViewGroup的一个方法,目的是在系统向该ViewGroup及其各个childView触发onTouchEvent()之前对相关事件进行一次拦截,由于ViewGroup会包含若干childView,因此需要能够统一监控各种touch事件的机会,因此纯粹的不能包含子view的控件是没有这个方法的,如LinearLayout就有,TextView就没有。
关于返回值的问题,如果return true,那么表示该方法消费了此次事件,如果return false,那么表示该方法并未处理完全,该事件仍然需要以某种方式传递下去继续等待处理,需要说明的是,如果 onTouchEvent针对down事件返回了false,那么之后的move和up事件即使没有被拦截,也是接收不到的,或者说识别不了.
onInterceptTouchEvent()使用也很简单,如果在ViewGroup里覆写了该方法,那么就可以对各种touch事件加以拦截。但是如何拦截,是否所有的touch事件都需要拦截则是比较复杂的,touch事件在onInterceptTouchEvent()和onTouchEvent以及各个childView间的传递机制完全取决于onInterceptTouchEvent()和onTouchEvent()的返回值。并且,针对down事件处理的返回值直接影响到后续move和up事件的接收和传递。
总结一下,基本的规则是:
1. 对于一个事件,如果没有被任何view拦截的话(所有方法都返回false),全程的顺序是
LayoutView1->LayoutView2->MyTextView依次调用onInterceptTouchEvent()
然后MyTextView->LayoutView2->LayoutView1依次调用onTouchEvent()
2. onInterceptTouchEvent()只负责拦截不拦截,onTouchEvent()只负责处理不处理,两者只要返回true,该事件就停止向后传递.
3. 如果该ViewGroup的onInterceptTouchEvent()在接收到down事件处理完成之后return true,那么后续的move, up等事件将不再传递给onInterceptTouchEvent(),而是和down事件一样传递给该ViewGroup的onTouchEvent()处理.
4. 如果最终需要处理事件的view的onTouchEvent()返回了false,那么该事件将被传递至其上一层次的view的onTouchEvent()处理。
5. 如果最终需要处理事件的view 的onTouchEvent()返回了true,则上一层次的view收不到该事件,且后续事件将可以继续传递给该view的onTouchEvent()处理.
下面开始在代码中试验.
1.onInterceptTouchEvent()处理事件均返回false,onTouchEvent()处理事件均返回true
这是最常见的情况,onInterceptTouchEvent并没有做任何改变事件传递时序的操作,效果上和没有覆写该方法是一样的。可以看到,各种事件的传递本身是自底向上的,次序是:LayoutView1->LayoutView2->MyTextView。事件在MyTextView的OnTouch中被处理,停止了向父组件传递.
2.LayoutView1的onInterceptTouchEvent()处理事件返回true,MyTextView的onTouchEvent()处理事件返回true
由于LayoutView1在拦截第一次事件时return true,所以后续的事件(包括第一次的down)将由LayoutView1本身处理,事件不再传递下去。
3.LayoutView1,LayoutView2的onInterceptTouchEvent()处理事件返回false,MyTextView的onTouchEvent()处理事件返回false,LayoutView2的onTouchEvent()处理事件返回true
由于MyTextView在onTouchEvent()中return false,down事件被传递给其父view,即LayoutView2的onTouchEvent()方法处理,由于在LayoutView2的onTouchEvent()中return true,所以down事件传递并没有上传到LayoutView1。注意,后续的move和up事件均被传递给LayoutView2的onTouchEvent()处理,而没有传递给MyTextView。