【安卓笔记】touch事件的分发和消费机制
16lz
2021-01-25
Android 中与 Touch 事件相关的方法包括: dispatchTouchEvent(MotionEvent ev) 、 onInterceptTouchEvent(MotionEvent ev) 、 onTouchEvent(MotionEvent ev);能够响应这些方法的控件包括:ViewGroup、View、Activity。继承ViewGroup的大多是容器控件,如LinearLayout等,而继承View的大部分是显示控件比如TextView,ImageView等(当然,ViewGroup本身是继承View的),显示控件没有 onInterceptTouchEvent。 来看一些例子。 情形1: 只有activity,没有子控件: 代码如下:
可以看到:总是先执行dispatchTouchEvent,再执行onTouchEvent.。
情形2: 将上面代码 dispatchTouchEvent的返回值改为true。 日志:
可以看到,只执行了dispatchTouchEvent,而没有执行onTouchEvent。说明在activity中dispatchTouchEvent先于onTouchEvent执行,如果将dispatchTouchEvent返回值置为true,表示事件被消费了,不再传递。
情形3: 加入一个子控件 以自定义Button为例:
执行流程是首先由activity捕获到ACTION_DWON事件,然后调用activity的dispatchTouchEvent,接着绕开activity的onTouchEvent直接将事件传递给子控件,调用MyButton的dispatchTouchEvent,在之后调用该控件的onTouchEvent,ACTION_UP事件也是一样的流程。
情形4: 跟情形2类似,将情形3的activity的DispatchTouchEvent的返回值改为true,点击按钮,很显然,touch事件将不会被分发给Button,所以点击按钮日志是这样的:
情形5: 将情形3的myButton的 DispatchTouchEvent的返回值改为true,点击按钮,很显然,当touch事件传递到button时,先被dispatchTouchEvent捕获,由于返回true,所以事件被消费,便不往下面传递,所以Button的onTouchEvent方法不被调用。 日志信息:
情形6: 将情形3的myButton的onTouchEvent的返回值改为false。 日志:
touch事件传到button时,因为onTouchEvent返回值为false,所以继续交由activity的onTouchEvent方法去执行,紧接着,ACTION_UP的动作将不再传递给button,直接由activity捕获了。 情形7: 给Activity的button按钮增加onTouchListener和onClickListener
首先touch事件由activity捕获,调用activity的dispatchTouchEvent,紧接着调用Button的dispatchTouchEvent继续分发touch事件,接着并没有调用button的onTouchEvent,而是先调用了onTouch方法,这是因为button按钮注册了onTouchListener的缘故,待onTouch事件处理完之后,由于返回值为false,所以touch事件传递给了button的onTouchEvent。接着ACTION_UP事件也是类似的过程,但当Button的onTouchEvent结束后,还调用了Onclick方法。
情形8: 在情形7的代码中将onTouch方法的返回值该true。 相信大家已经猜出来了,改为true之后touch事件将不被button的onTouchEvent捕获而是直接被消费了,从日志也可以看出:
但是比较奇怪的是,onClick方法也没用被执行,我们猜测onClick方法是在button的onTouchEvent方法中被执行的。事实也确实如此: 在view的onTouchEvent方法中有这样一段逻辑:
可以看到,由于加了一层容器控件,所以activity执行完dispatchTouchEvent之后将touch事件分发给容器控件MyLinearLayout,紧接着并不是直接将touch事件传递给button,而是先执行了 onInterceptTouchEvent,这个方法返回false,并没有拦截touch事件,所以接下来会将touch事件传递给button。
情形10: 将MyLinearLayout的 onInterceptTouchEvent方法返回值置为true,拦截touch事件,使其不再向下传递,点击按钮,查看日志:
可以看到,touch事件再被拦截之后就不再传递给button了,而是被MyLinearLayout的onTouchEvent接收,接着由MainActivity的onTouchEvent接收并消费,ACTION_UP事件将直接由Activity处理。
总结: 1.事件传递的两种方式:
3.touch事件分析: 事件分发:public boolean dispatchTouchEvent(MotionEvent ev)
Touch 事件发生时 Activity 的 dispatchTouchEvent(MotionEvent ev) 方法会以隧道方式(从根元素依次往下传递直到最内层子元素或在中间某一元素中由于某一条件停止传递)将事件传递给最外层 View 的 dispatchTouchEvent(MotionEvent ev) 方法,并由该 View 的 dispatchTouchEvent(MotionEvent ev) 方法对事件进行分发。dispatchTouchEvent 的事件分发逻辑如下:
事件拦截:public boolean onInterceptTouchEvent(MotionEvent ev)
在外层 View 的 dispatchTouchEvent(MotionEvent ev) 方法返回系统默认的 super.dispatchTouchEvent(ev) 情况下,事件会自动的分发给当前 View 的 onInterceptTouchEvent 方法。onInterceptTouchEvent 的事件拦截逻辑如下:
在 dispatchTouchEvent 返回 super.dispatchTouchEvent(ev) 并且 onInterceptTouchEvent 返回 true 或返回 super.onInterceptTouchEvent(ev) 的情况下 onTouchEvent 会被调用。onTouchEvent 的事件响应逻辑如下:
package com.example.toucheventdemo;import android.app.Activity;import android.os.Bundle;import android.util.Log;import android.view.MotionEvent;public class MainActivity extends Activity{ private static final String TAG = "MainActivity"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: Log.i(TAG,"dispatchTouchEvent--ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.i(TAG,"dispatchTouchEvent--ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.i(TAG,"dispatchTouchEvent--ACTION_UP"); break; } return super.dispatchTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: Log.i(TAG,"onTouchEvent--ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.i(TAG,"onTouchEvent--ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.i(TAG,"onTouchEvent--ACTION_UP"); break; } return super.onTouchEvent(event); }}日志信息:
可以看到:总是先执行dispatchTouchEvent,再执行onTouchEvent.。
情形2: 将上面代码 dispatchTouchEvent的返回值改为true。 日志:
可以看到,只执行了dispatchTouchEvent,而没有执行onTouchEvent。说明在activity中dispatchTouchEvent先于onTouchEvent执行,如果将dispatchTouchEvent返回值置为true,表示事件被消费了,不再传递。
情形3: 加入一个子控件 以自定义Button为例:
package com.example.toucheventdemo;import android.content.Context;import android.util.AttributeSet;import android.util.Log;import android.view.MotionEvent;import android.widget.Button;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); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: Log.i(TAG,"dispatchTouchEvent--ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.i(TAG,"dispatchTouchEvent--ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.i(TAG,"dispatchTouchEvent--ACTION_UP"); break; } return super.dispatchTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: Log.i(TAG,"onTouchEvent--ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.i(TAG,"onTouchEvent--ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.i(TAG,"onTouchEvent--ACTION_UP"); break; } return super.onTouchEvent(event); }}mainActivity代码如下:
package com.example.toucheventdemo;import android.app.Activity;import android.os.Bundle;import android.util.Log;import android.view.MotionEvent;import android.view.View;import android.view.View.OnClickListener;public class MainActivity extends Activity{ private static final String TAG = "MainActivity"; private MyButton but = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); but = (MyButton) findViewById(R.id.but); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: Log.i(TAG,"dispatchTouchEvent--ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.i(TAG,"dispatchTouchEvent--ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.i(TAG,"dispatchTouchEvent--ACTION_UP"); break; } return super.dispatchTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: Log.i(TAG,"onTouchEvent--ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.i(TAG,"onTouchEvent--ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.i(TAG,"onTouchEvent--ACTION_UP"); break; } return super.onTouchEvent(event); }}此时点击Button按钮,查看日志:
执行流程是首先由activity捕获到ACTION_DWON事件,然后调用activity的dispatchTouchEvent,接着绕开activity的onTouchEvent直接将事件传递给子控件,调用MyButton的dispatchTouchEvent,在之后调用该控件的onTouchEvent,ACTION_UP事件也是一样的流程。
情形4: 跟情形2类似,将情形3的activity的DispatchTouchEvent的返回值改为true,点击按钮,很显然,touch事件将不会被分发给Button,所以点击按钮日志是这样的:
情形5: 将情形3的myButton的 DispatchTouchEvent的返回值改为true,点击按钮,很显然,当touch事件传递到button时,先被dispatchTouchEvent捕获,由于返回true,所以事件被消费,便不往下面传递,所以Button的onTouchEvent方法不被调用。 日志信息:
情形6: 将情形3的myButton的onTouchEvent的返回值改为false。 日志:
touch事件传到button时,因为onTouchEvent返回值为false,所以继续交由activity的onTouchEvent方法去执行,紧接着,ACTION_UP的动作将不再传递给button,直接由activity捕获了。 情形7: 给Activity的button按钮增加onTouchListener和onClickListener
package com.example.toucheventdemo;import android.app.Activity;import android.os.Bundle;import android.util.Log;import android.view.MotionEvent;import android.view.View;import android.view.View.OnClickListener;import android.view.View.OnTouchListener;public class MainActivity extends Activity implements OnClickListener,OnTouchListener{ private static final String TAG = "MainActivity"; private MyButton but = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); but = (MyButton) findViewById(R.id.but); but.setOnClickListener(this); but.setOnTouchListener(this); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: Log.i(TAG,"dispatchTouchEvent--ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.i(TAG,"dispatchTouchEvent--ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.i(TAG,"dispatchTouchEvent--ACTION_UP"); break; } return super.dispatchTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: Log.i(TAG,"onTouchEvent--ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.i(TAG,"onTouchEvent--ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.i(TAG,"onTouchEvent--ACTION_UP"); } return super.onTouchEvent(event); } @Override public void onClick(View v) { Log.i("MyButton","ONCLICK"); } @Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: Log.i("MyButton","onTouch--ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.i("MyButton","onTouch--ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.i("MyButton","onTouch--ACTION_UP"); break; } return false; }}现在点击按钮日志打印如下信息:
首先touch事件由activity捕获,调用activity的dispatchTouchEvent,紧接着调用Button的dispatchTouchEvent继续分发touch事件,接着并没有调用button的onTouchEvent,而是先调用了onTouch方法,这是因为button按钮注册了onTouchListener的缘故,待onTouch事件处理完之后,由于返回值为false,所以touch事件传递给了button的onTouchEvent。接着ACTION_UP事件也是类似的过程,但当Button的onTouchEvent结束后,还调用了Onclick方法。
情形8: 在情形7的代码中将onTouch方法的返回值该true。 相信大家已经猜出来了,改为true之后touch事件将不被button的onTouchEvent捕获而是直接被消费了,从日志也可以看出:
但是比较奇怪的是,onClick方法也没用被执行,我们猜测onClick方法是在button的onTouchEvent方法中被执行的。事实也确实如此: 在view的onTouchEvent方法中有这样一段逻辑:
case MotionEvent.ACTION_UP: boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0; if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) { // take focus if we don't have it already and we should in // touch mode. boolean focusTaken = false; if (isFocusable() && isFocusableInTouchMode() && !isFocused()) { focusTaken = requestFocus(); } if (prepressed) { // The button is being released before we actually // showed it as pressed. Make it show the pressed // state now (before scheduling the click) to ensure // the user sees it. setPressed(true); } if (!mHasPerformedLongPress) { // This is a tap, so remove the longpress check removeLongPressCallback(); // Only perform take click actions if we were in the pressed state if (!focusTaken) { // Use a Runnable and post this rather than calling // performClick directly. This lets other visual state // of the view update before click actions start. if (mPerformClick == null) { mPerformClick = new PerformClick(); } if (!post(mPerformClick)) { performClick(); } } } if (mUnsetPressedState == null) { mUnsetPressedState = new UnsetPressedState(); } if (prepressed) { postDelayed(mUnsetPressedState, ViewConfiguration.getPressedStateDuration()); } else if (!post(mUnsetPressedState)) { // If the post failed, unpress right now mUnsetPressedState.run(); } removeTapCallback(); } break;在ACTION_UP分支上执行了click操作,具体由performClick方法执行:
public boolean performClick() { sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); ListenerInfo li = mListenerInfo; if (li != null && li.mOnClickListener != null) { playSoundEffect(SoundEffectConstants.CLICK); li.mOnClickListener.onClick(this); return true; } return false; }情形9: 以上是在activity中加入显示控件(TextView,Button等),下面在activity中加入容器控件(LinearLayout等),此类控件继承ViewGroup,除了拥有dispatchTouchEvent和ontouchEvent之外,还有onInterceptTouchEvent方法,这个方法用于拦截touch事件,默认返回false,表示不拦截。 下面我们自己实现一个容器控件,并复写onTouchEvent,dispatchTouchEvent和 onInterceptTouchEvent:
package com.example.toucheventdemo;import android.content.Context;import android.util.AttributeSet;import android.util.Log;import android.view.MotionEvent;import android.widget.LinearLayout;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); } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: Log.i(TAG,"onTouchEvent--ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.i(TAG,"onTouchEvent--ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.i(TAG,"onTouchEvent--ACTION_UP"); } return super.onTouchEvent(event); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: Log.i(TAG,"dispatchTouchEvent--ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.i(TAG,"dispatchTouchEvent--ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.i(TAG,"dispatchTouchEvent--ACTION_UP"); } return super.dispatchTouchEvent(ev); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: Log.i(TAG,"onInterceptTouchEvent--ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.i(TAG,"onInterceptTouchEvent--ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.i(TAG,"onInterceptTouchEvent--ACTION_UP"); } return super.onInterceptTouchEvent(ev); } }此时再点击按钮,查看日志:
可以看到,由于加了一层容器控件,所以activity执行完dispatchTouchEvent之后将touch事件分发给容器控件MyLinearLayout,紧接着并不是直接将touch事件传递给button,而是先执行了 onInterceptTouchEvent,这个方法返回false,并没有拦截touch事件,所以接下来会将touch事件传递给button。
情形10: 将MyLinearLayout的 onInterceptTouchEvent方法返回值置为true,拦截touch事件,使其不再向下传递,点击按钮,查看日志:
可以看到,touch事件再被拦截之后就不再传递给button了,而是被MyLinearLayout的onTouchEvent接收,接着由MainActivity的onTouchEvent接收并消费,ACTION_UP事件将直接由Activity处理。
总结: 1.事件传递的两种方式:
- 隧道方式:从根元素依次往下传递直到最内层子元素或在中间某一元素中由于某一条件停止传递。
- 冒泡方式:从最内层子元素依次往外传递直到根元素或在中间某一元素中由于某一条件停止传递。
3.touch事件分析: 事件分发: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 进行消费。
事件拦截: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),处理逻辑与返回false相同。
在 dispatchTouchEvent 返回 super.dispatchTouchEvent(ev) 并且 onInterceptTouchEvent 返回 true 或返回 super.onInterceptTouchEvent(ev) 的情况下 onTouchEvent 会被调用。onTouchEvent 的事件响应逻辑如下:
- 如果事件传递到当前 View 的 onTouchEvent 方法,而该方法返回了 false,那么这个事件会从当前 View 向上传递,并且都是由上层 View 的 onTouchEvent 来接收,如果传递到上面的 onTouchEvent 也返回 false,这个事件就会“消失”,而且接收不到下一次事件。
- 如果返回了 true 则会接收并消费该事件。
- 如果返回 super.onTouchEvent(ev) 默认处理事件的逻辑和返回 false 时相同。
更多相关文章
- Android(安卓)Fragment的生命周期详解
- Android(安卓)TextView中显示图片
- Android(安卓)完全隐藏状态栏方法
- Android开发Android(安卓)studio之gradle打包apk重命名的方法
- Android(安卓)多媒体框架(2)—— android_media_MediaCodec.cpp源
- Android之使用bindService启动服务
- Android端获取视频第一帧的方法
- 第一行代码阅读笔记---详解分析第一个Android程序
- Android绘制一个三角形并且可动态改变颜色