您的位置:首页 > 移动开发 > Android开发

Android Touch事件传递原理分析

2016-09-18 20:15 357 查看
一、Touch事件分析

1.Touch事件类型

Touch事件被封装成MotionEvent,用户当前的touch事件主要类型有:

ACTION_DOWN: 表示用户开始触摸

ACTION_MOVE: 表示用户在移动(手指或者其他)

ACTION_UP:表示用户抬起了手指

ACTION_CANCEL:表示手势被取消了

ACTION_OUTSIDE: 表示用户触碰超出了正常的UI边界.

ACTION_POINTER_DOWN:有一个非主要的手指按下了.

ACTION_POINTER_UP:一个非主要的手指抬起来了

ACTION_HOVER_ENTER:

ACTION_HOVER_MOVE:

ACTION_HOVER_EXIT:

上面三个类型一般用于鼠标事件,鼠标在不点击的情况下,可以进入和离开View的区域

2.Touch事件元数据

touch事件的元数据主要包括:touch的位置、手指的个数、touch事件的时间。

一个touch手势被定义为以ACTION_DOWN开始和以 ACTION_UP结束。

3.事件传递流程:

Android 中与 Touch 事件相关的方法包括:dispatchTouchEvent(MotionEvent ev)、onInterceptTouchEvent(MotionEvent ev)、onTouchEvent(MotionEvent ev);能够响应这些方法的控件包括:ViewGroup、View、Activity。方法与控件的对应关系如下:

Touch 事件相关方法 方法功能 ViewGroup View Activity

public boolean dispatchTouchEvent(MotionEvent ev) 事件分发 Yes Yes Yes

public boolean onInterceptTouchEvent(MotionEvent ev) 事件拦截 Yes No No

public boolean onTouchEvent(MotionEvent ev) 事件响应 Yes Yes Yes

从这张表格我们可以看出,Activity没有拦截事件,View(这里指的是View的最小单元,如ImageView,非继承于ViewGroup)本身不带拦截事件,只含有事件的分发与响应功能,而ViewGroup则继承与View,在父类View的基础上增加了拦截事件功能。

下面为上述三个事件作简单解释:

(1)事件分发:public boolean dispatchTouchEvent(MotionEvent ev)

Touch 事件发生时 Activity 的 dispatchTouchEvent(MotionEvent ev) 方法会以隧道方式(从根元素依次往下传递直到最内层子元素或在中间某一元素中由于某一条件停止传递)将事件传递给最外层 View 的 dispatchTouchEvent(MotionEvent ev) 方法,并由该 View 的 dispatchTouchEvent(MotionEvent ev) 方法对事件进行分发。dispatchTouchEvent 的事件分发逻辑如下:


①如果 return true,事件会分发给当前 View 并由 dispatchTouchEvent 方法进行消费,同时事件会停止向下传递;

②如果 return false,事件分发分为两种情况:

如果当前 View 获取的事件直接来自 Activity,则会将事件返回给 Activity 的 onTouchEvent 进行消费;

如果当前 View 获取的事件来自外层父控件,则会将事件返回给父 View 的 onTouchEvent 进行消费。

③如果返回系统默认的 super.dispatchTouchEvent(ev),事件会自动的分发给当前 ViewGroup 的 onInterceptTouchEvent 方法,假如当前View是最小单元,则分发给OnTouchEvent方法。

(2)事件拦截:public boolean onInterceptTouchEvent(MotionEvent ev)

在外层 View 的 dispatchTouchEvent(MotionEvent ev) 方法返回系统默认的 super.dispatchTouchEvent(ev) 情况下,事件会自动的分发给当前 View 的 onInterceptTouchEvent 方法。onInterceptTouchEvent 的事件拦截逻辑如下:


①如果 onInterceptTouchEvent 返回 true,则表示将事件进行拦截,并将拦截到的事件交由当前 View 的 onTouchEvent 进行处理;

②如果 onInterceptTouchEvent 返回 false,则表示将事件放行,当前 View 上的事件会被传递到子 View 上,再由子 View 的 dispatchTouchEvent 来开始这个事件的分发;

③如果 onInterceptTouchEvent 返回 super.onInterceptTouchEvent(ev),事件默认不会被拦截

(3)事件响应:public boolean onTouchEvent(MotionEvent ev)

当View中的dispatchTouchEvent(MotionEvent ev)被调用并且View中没有设置onTouchListener或者设置了onTouchListener但是返回值为false 时onTouchEvent 会被调用。onTouchEvent 的事件响应逻辑如下:


如果事件传递到当前 View 的 onTouchEvent 方法,而该方法返回了 false,那么这个事件会从当前 View 向上传递,并且都是由上层 View 的 onTouchEvent 来接收,如果传递到上面的 onTouchEvent 也返回 false,这个事件就会“消失”,而且接收不到下一次事件。

如果返回了 true 则会接收并消费该事件。

如果返回 super.onTouchEvent(ev) 默认处理事件的逻辑和返回 false 时相同。

4,事件流走向图



图分为3层,从上往下依次是Activity、ViewGroup、View

事件从左上角那个白色箭头开始,由Activity的dispatchTouchEvent做分发

箭头的上面字代表方法返回值,(return true、return false、return super.xxxxx(),super 的意思是调用父类实现。

dispatchTouchEvent和 onTouchEvent的框里有个【true—->消费】的字,表示的意思是如果方法返回true,那么代表事件就此消费,不会继续往别的地方传了,事件终止。

目前所有的图的事件是针对ACTION_DOWN的,对于ACTION_MOVE和ACTION_UP我们最后做分析。

之前图中的Activity 的dispatchTouchEvent 有误(图已修复),只有return super.dispatchTouchEvent(ev) 才是往下走,返回true 或者 false 事件就被消费了(终止传递)。

如果事件不被中断,整个事件流向是一个类U型图



如果我们没有对控件里面的方法进行重写或更改返回值,而直接用super调用父类的默认实现,那么整个事件流向应该是从Activity—->ViewGroup—>View 从上往下调用dispatchTouchEvent方法,一直到叶子节点(View)的时候,再由View—>ViewGroup—>Activity从下往上调用onTouchEvent方法。

二、Touch事件传递实例

1.相关代码

布局文件如下:

<com.wu.demo.touchevent.widget.TouchEventParent 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:background="@android:color/darker_gray"
android:gravity="center"
tools:context=".MainActivity">

<com.wu.demo.touchevent.widget.TouchEventChild
android:layout_width="200dp"
android:layout_height="200dp"
android:background="@android:color/holo_blue_dark" />

</com.wu.touchevent.widget.TouchEventParent>


布局由两个控件组成,其中灰色背景为TouchEventParent 继承于ViewGroup,重写了OntouchEvent,onInterceptTouchEvent以及dispatchTouchEvent,其实现代码如下:

public class TouchEventParent extends LinearLayout {
public TouchEventParent(Context context) {
super(context);
}

public TouchEventParent(Context context, AttributeSet attrs) {
super(context, attrs);
}

public TouchEventParent(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}

public TouchEventParent(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}

@Override
public boolean onTouchEvent(MotionEvent event) {
boolean result = super.onTouchEvent(event);
Log.d("wu", "TouchEventParent && onTouchEvent ---->" + TouchEventUtils.getTouchAction(event.getAction()) + " && result = " +
f7ad
result);
return result;
}

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean result = super.onInterceptTouchEvent(ev);
Log.i("wu", "TouchEventParent && onInterceptTouchEvent ---->" + TouchEventUtils.getTouchAction(ev.getAction()) + " && result = " + result);
return result;
}

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean result = super.dispatchTouchEvent(ev);
Log.e("wu", "TouchEventParent && dispatchTouchEvent ---->" + TouchEventUtils.getTouchAction(ev.getAction()) + " && result = " + result);
return result;
}
}


TouchEventChild则直接继承于View,是View的最小单元,重写了OntouchEvent,dispatchTouchEvent,其实现代码如下:

public class TouchEventChild extends View {
public TouchEventChild(Context context) {
super(context);
}

public TouchEventChild(Context context, AttributeSet attrs) {
super(context, attrs);
}

public TouchEventChild(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}

public TouchEventChild(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}

@Override
public boolean onTouchEvent(MotionEvent event) {
boolean result = super.onTouchEvent(event);
Log.d("wu", "TouchEventChild && onTouchEvent ---->" + TouchEventUtils.getTouchAction(event.getAction()) + " && result = " + result);
return result;
}

@Override
public boolean dispatchTouchEvent(MotionEvent event) {
boolean result = super.dispatchTouchEvent(event);
Log.e("wu", "TouchEventChild && dispatchTouchEvent ---->" + TouchEventUtils.getTouchAction(event.getAction()) + " && result = " + result);
return result;
}
}


最后是Activity的实现,由于布局中所有的时间都从Activity中开始的,因此这里同样的重写了dispatchTouchEvent,onTouchEvent,代码如下:

public class MainActivity extends Activity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}

@Override
public boolean onTouchEvent(MotionEvent event) {
boolean result = super.onTouchEvent(event);
Log.d("wu", "MainActivity && onTouchEvent ---->" + TouchEventUtils.getTouchAction(event.getAction()) + " && result = " + result);
return result;
}

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean result = super.dispatchTouchEvent(ev);
Log.e("wu", "MainActivity && dispatchTouchEvent ---->" + TouchEventUtils.getTouchAction(ev.getAction()) + " && result = " + result);
return result;
}
}


布局如下:



二,比较典型的情况分析

case 1:

拦截条件:

控件名称 dispatchTouchEvent 返回值 onInterceptTouchEvent 返回值 onTouchEvent 返回值

TouchEventActivity super.dispatchTouchEvent(ev) — super.onTouchEvent(ev)

TouchEventParent super.dispatchTouchEvent(ev) super.onInterceptTouchEvent(ev) super.onTouchEvent(ev)

TouchEventChild super.dispatchTouchEvent(ev) super.onInterceptTouchEvent(ev) super.onTouchEvent(ev)

结果:

E/wu (26802): MainActivity && dispatchTouchEvent —->ACTION_DOWN

E/wu (26802): TouchEventParent && dispatchTouchEvent —->ACTION_DOWN

I/wu (26802): TouchEventParent && onInterceptTouchEvent —->ACTION_DOWN

E/wu (26802): TouchEventChild && dispatchTouchEvent —->ACTION_DOWN

D/wu (26802): TouchEventChild && onTouchEvent —->ACTION_DOWN

D/wu (26802): TouchEventParent && onTouchEvent —->ACTION_DOWN

D/wu (26802): MainActivity && onTouchEvent —->ACTION_DOWN

E/wu (26802): MainActivity && dispatchTouchEvent —->ACTION_MOVE

D/wu (26802): MainActivity && onTouchEvent —->ACTION_MOVE

E/wu (26802): MainActivity && dispatchTouchEvent —->ACTION_UP

D/wu (26802): MainActivity && onTouchEvent —->ACTION_UP

结果分析:

ACTION_DOWN事件通过MainActivity dispatchTouchEvent向下分发接着接到事件的是TouchEventParent的dispatchTouchEvent然后触发自身的onInterceptTouchEvent并从结果可看到,当onInterceptTouchEvent返回super.dispatchTouchEvent实际是返回false,即没有对其子View进行拦截。接着是TouchEventChild 的dispatchTouchEvent由于TouchEventChild非ViewGroup,没有onInterceptTouchEvent(),所以把事件直接传给了自身onTouchEvent 然而TouchEventChild TouchEventParent的onTouchEvent都没有把事件消耗,最后穿回给MainActivity onTouchEvent()处理。由于TouchEventChild ,TouchEventParent都没有消耗事件,因此下次事件ACTION_MOVE ACTION_UP,不再向TouchEventParent,TouchEventChild分发。

case 2:

拦截条件:

控件名称 dispatchTouchEvent 返回值 onInterceptTouchEvent 返回值 onTouchEvent 返回值

TouchEventActivity super.dispatchTouchEvent(ev) — super.onTouchEvent(ev)

TouchEventParent super.dispatchTouchEvent(ev) super.onInterceptTouchEvent(ev) super.onTouchEvent(ev)

TouchEventChild super.dispatchTouchEvent(ev) super.onInterceptTouchEvent(ev) true

结果:

E/wu (27079): MainActivity && dispatchTouchEvent —->ACTION_DOWN

E/wu (27079): TouchEventParent && dispatchTouchEvent —->ACTION_DOWN

I/wu (27079): TouchEventParent && onInterceptTouchEvent —->ACTION_DOWN

E/wu (27079): TouchEventChild && dispatchTouchEvent —->ACTION_DOWN

D/wu (27079): TouchEventChild && onTouchEvent —->ACTION_DOWN

E/wu (27079): MainActivity && dispatchTouchEvent —->ACTION_MOVE

E/wu (27079): TouchEventParent && dispatchTouchEvent —->ACTION_MOVE

I/wu (27079): TouchEventParent && onInterceptTouchEvent —->ACTION_MOVE

E/wu (27079): TouchEventChild && dispatchTouchEvent —->ACTION_MOVE

D/wu (27079): TouchEventChild && onTouchEvent —->ACTION_MOVE

E/wu (27079): MainActivity && dispatchTouchEvent —->ACTION_UP

E/wu (27079): TouchEventParent && dispatchTouchEvent —->ACTION_UP

I/wu (27079): TouchEventParent && onInterceptTouchEvent —->ACTION_UP

E/wu (27079): TouchEventChild && dispatchTouchEvent —->ACTION_UP

D/wu (27079): TouchEventChild && onTouchEvent —->ACTION_UP

结果分析:

从log中可以看出这个case ACTION_DOWN事件往下传递流程与case1一致,只不过在TouchEventChild 的onTouchEvent ()中return了true,即把事件消耗了,因此事件不再往上传,TouchEventParent 、TouchEventActivity不在回调onTouchEvent (),下次事件(ACTION_MOVE ACTION_UP)时,不像case1一样直接在TouchEventActivity消耗,而是再次传到TouchEventChild 中,交由TouchEventChild处理。

case3:

拦截条件:

控件名称 dispatchTouchEvent 返回值 onInterceptTouchEvent 返回值 onTouchEvent 返回值

TouchEventActivity super.dispatchTouchEvent(ev) — super.onTouchEvent(ev)

TouchEventParent true super.onInterceptTouchEvent(ev) super.onTouchEvent(ev)

TouchEventChild super.dispatchTouchEvent(ev) super.onInterceptTouchEvent(ev) super.onTouchEvent(ev)

结果:

E/wu ( 755): MainActivity && dispatchTouchEvent —->ACTION_DOWN

E/wu ( 755): TouchEventParent && dispatchTouchEvent —->ACTION_DOWN

E/wu ( 755): MainActivity && dispatchTouchEvent —->ACTION_MOVE

E/wu ( 755): TouchEventParent && dispatchTouchEvent —->ACTION_MOVE

E/wu ( 755): MainActivity && dispatchTouchEvent —->ACTION_UP

E/wu ( 755): TouchEventParent && dispatchTouchEvent —->ACTION_UP

结果分析:

从log中可以看出事件从MainActivity dispatchTouchEvent 开发分发,到TouchEventParent的dispatchTouchEvent()结束。首先在TouchEventParent的dispatchTouchEvent 中没有调用super.dispatchTouchEvent(ev) ,则其子控件不会受到任何的的事件。其次,return true表明其消耗了事件,因此MainActivity的onTouchEvent()不会被回调。同时不难发现假如在TouchEventParent 的dispatchTouchEvent()直接返回false,则事件最终会传到MainActivity onTouchEvent,并且下一次事件不再传到TouchEventParent。

case4:

拦截条件:

控件名称 dispatchTouchEvent 返回值 onInterceptTouchEvent 返回值 onTouchEvent 返回值

TouchEventActivity super.dispatchTouchEvent(ev) — super.onTouchEvent(ev)

TouchEventParent super.dispatchTouchEvent(ev) true super.onTouchEvent(ev)

TouchEventChild super.dispatchTouchEvent(ev) super.onInterceptTouchEvent(ev) super.onTouchEvent(ev)

结果:

E/wu ( 1832): MainActivity && dispatchTouchEvent —->ACTION_DOWN

E/wu ( 1832): TouchEventParent && dispatchTouchEvent —->ACTION_DOWN

I/wu ( 1832): TouchEventParent && onInterceptTouchEvent —->ACTION_DOWN

D/wu ( 1832): TouchEventParent && onTouchEvent —->ACTION_DOWN

D/wu ( 1832): MainActivity && onTouchEvent —->ACTION_DOWN

E/wu ( 1832): MainActivity && dispatchTouchEvent —->ACTION_MOVE

D/wu ( 1832): MainActivity && onTouchEvent —->ACTION_MOVE

E/wu ( 1832): MainActivity && dispatchTouchEvent —->ACTION_UP

D/wu ( 1832): MainActivity && onTouchEvent —->ACTION_UP

结果分析:

与case1相比,区别在于super.onInterceptTouchEvent(ev)被改为直接retrurn true,这表明TouchEventParent 要对TouchEventChild要进行事件拦截,拦截后事件直接传到自身的onTouchEvent中,其子View TouchEventChild则不会收到事件,又因为TouchEventParent 的onTouchEvent最终没有消耗掉事件,因此最事件传到MainActivity的onTouchEvent中,下次事件不再往TouchEventParent分发,而是直接在MainActivity中处理。另外不难发现假如这个时候把TouchEventParent onInterceptTouchEvent 的true改为false,结果则跟case1一致。

case5:

拦截条件:

控件名称 dispatchTouchEvent 返回值 onInterceptTouchEvent 返回值 onTouchEvent 返回值

TouchEventActivity super.dispatchTouchEvent(ev) — super.onTouchEvent(ev)

TouchEventParent super.dispatchTouchEvent(ev) if (action == ACTION_MOVE ) true super.onTouchEvent(ev)

TouchEventChild super.dispatchTouchEvent(ev) super.onInterceptTouchEvent(ev) true

结果:

E/wu (24169): MainActivity && dispatchTouchEvent —->ACTION_DOWN

E/wu (24169): TouchEventParent && dispatchTouchEvent —->ACTION_DOWN

I/wu (24169): TouchEventParent && onInterceptTouchEvent —->ACTION_DOWN

E/wu (24169): TouchEventChild && dispatchTouchEvent —->ACTION_DOWN

D/wu (24169): TouchEventChild && onTouchEvent —->ACTION_DOWN

E/wu (24169): MainActivity && dispatchTouchEvent —->ACTION_MOVE

E/wu (24169): TouchEventParent && dispatchTouchEvent —->ACTION_MOVE

I/wu (24169): TouchEventParent && onInterceptTouchEvent —->ACTION_MOVE

E/wu (24169): TouchEventChild && dispatchTouchEvent —->ACTION_CANCEL

D/wu (24169): TouchEventChild && onTouchEvent —->ACTION_CANCEL

E/wu (24169): MainActivity && dispatchTouchEvent —->ACTION_UP

E/wu (24169): TouchEventParent && dispatchTouchEvent —->ACTION_UP

D/wu (24169): TouchEventParent && onTouchEvent —->ACTION_UP

D/wu (24169): MainActivity && onTouchEvent —->ACTION_UP

结果分析:

与case4相比,TouchEventParent.onInterceptTouchEvent()中添加了 if (action == ACTION_MOVE )的条件,TouchEventChild的onTouchEvent 中直接返回true。这样的结果是:当action == ACTION_DOWN时,TouchEventChild.onTouchEvent()能正常消费了事件,紧接着进行ACTION_MOVE,此时TouchEventParent.onInterceptTouchEvent()返回true,对事件进行拦截。TouchEventChild的dispatchTouchEvent()以及onTouchEvent()继续接收到事件,但是此时事件的action已经被置为ACTION_CANCEL,此事件同样结束于TouchChild的onTouchEvent(),但接下来的ACTION_UP事件不再传递到TouchChild,而是经过TouchEventParent 的dispatchTouchEvent(),直接分发给自身的onTouchEvent (),而TouchEventParent 的onTouchEvent()没有消费事件,最终传回MainActivity 的onTouchEvent()。

从上面五个case我们可以知道:
1.每次事件的都是从Activity的dispatchTouchEvent 开始,假如中途没有被消耗,最终会传回Activity的onTouchEvent,好比一个递归的过程
2.dispatchTouchEvent onTouchEvent都有消费事件能力,一旦事件被消费(return true),则不会继续往下传递。
3.onInterceptTouchEvent 能阻止事件继续往子View传递,但本身不具有消费事件能力。截取事件后,随即把事件传递到本身的onTouchEvent中
4.一个touch手势中ViewGroup 或者View假如没有把当前事件消费掉,那么它将不会接收到后面的事件。
5.在一个手势的过程中(非ACTION_DOWN),假如子View受到Parent的拦截,那么子View的将接受到CANCEL事件,并且此后不再收到事件,事件交由parent的onTouchEvent()处理。


内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  android