1500字范文,内容丰富有趣,写作好帮手!
1500字范文 > 【源码分析】Android触摸事件的分发拦截

【源码分析】Android触摸事件的分发拦截

时间:2022-02-08 23:09:14

相关推荐

【源码分析】Android触摸事件的分发拦截

Android中View的分发拦截机制是一块重要的内容,网上也有很多大神进行过相关的分析。

在这篇文章里我将以自己的理解尽量全面地分析整个流程,有些分析结果是很多文章没有提及的。

整个分析过程将通过demo与源码进行,做到有理有据。

###demo结构

这个demo的地址在这儿

首先要知道,ViewGroup的相关方法有dispatchTouchEventonInterceptTouchEventonTouchEventrequestDisallowInterceptTouchEvent

View的相关方法有dispatchTouchEventonTouchEvent,一般来说,这些方法返回true表示已经消耗了触摸事件,不会再向下分发事件,返回false则相反。

demo中需要重写相关方法并添加日志输出,便于我们观察并理清正确的过程。

自定义ViewGroupA 继承FrameLayout如下:

/*** Author: Sbingo* Date: /6/28*/public class ViewGroupA extends FrameLayout{Logger myLogger = Logger.getLogger("Sbingo ViewGroupA");public ViewGroupA(Context context) {super(context);}public ViewGroupA(Context context, AttributeSet attrs) {super(context, attrs);}public ViewGroupA(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);}@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {myLogger.log(Level.INFO, "dispatchTouchEvent");return super.dispatchTouchEvent(ev);}@Overridepublic void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {myLogger.log(Level.INFO, "requestDisallowInterceptTouchEvent");super.requestDisallowInterceptTouchEvent(disallowIntercept);}@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {myLogger.log(Level.INFO, "onInterceptTouchEvent");return super.onInterceptTouchEvent(ev);}@Overridepublic boolean onTouchEvent(MotionEvent event) {myLogger.log(Level.INFO, "onTouchEvent");return super.onTouchEvent(event);}}

类似地,自定义ViewGroupB 继承FrameLayout,添加相关打印日志。

接着自定义MyView继承TextView如下:

/*** Author: Sbingo* Date: /6/28*/public class MyView extends android.support.v7.widget.AppCompatTextView {Logger myLogger = Logger.getLogger("Sbingo MyView");public MyView(Context context) {super(context);}public MyView(Context context, @Nullable AttributeSet attrs) {super(context, attrs);}public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);}@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {myLogger.log(Level.INFO, "dispatchTouchEvent");return super.dispatchTouchEvent(ev);}@Overridepublic boolean onTouchEvent(MotionEvent event) {myLogger.log(Level.INFO, "onTouchEvent:" + event.getAction());return true;}}

给它们定义如下的布局层次:

<com.sbingo.viewsample.ViewGroupAandroid:id="@+id/vga"android:layout_width="match_parent"android:layout_height="match_parent"android:background="@android:color/black"><com.sbingo.viewsample.ViewGroupBandroid:id="@+id/vgb"android:layout_width="300dp"android:layout_height="400dp"android:layout_gravity="center"android:background="@color/colorPrimary"tools:layout_editor_absoluteX="0dp"tools:layout_editor_absoluteY="0dp"><com.sbingo.viewsample.MyViewandroid:id="@+id/my_view"android:layout_width="150dp"android:layout_height="200dp"android:layout_gravity="center"android:background="@color/colorAccent" /></com.sbingo.viewsample.ViewGroupB></com.sbingo.viewsample.ViewGroupA>

布局很简单,MyView在ViewGroupB正中间,ViewGroupB在ViewGroupA正中间。

一切准备就绪,开始玩起来!

###事实来说话,log作用大

点击MyView06-28 11:33:32.947 24137-24137/com.sbingo.viewsample I/Sbingo ViewGroupA: dispatchTouchEvent06-28 11:33:32.948 24137-24137/com.sbingo.viewsample I/Sbingo ViewGroupA: onInterceptTouchEvent06-28 11:33:32.948 24137-24137/com.sbingo.viewsample I/Sbingo ViewGroupB: dispatchTouchEvent06-28 11:33:32.948 24137-24137/com.sbingo.viewsample I/Sbingo ViewGroupB: onInterceptTouchEvent06-28 11:33:32.948 24137-24137/com.sbingo.viewsample I/Sbingo MyView: dispatchTouchEvent06-28 11:33:32.948 24137-24137/com.sbingo.viewsample I/Sbingo MyView: onTouchEvent06-28 11:33:32.949 24137-24137/com.sbingo.viewsample I/Sbingo ViewGroupB: onTouchEvent06-28 11:33:32.949 24137-24137/com.sbingo.viewsample I/Sbingo ViewGroupA: onTouchEvent

触摸事件到达MyView后没有被消耗,又向上传递

点击ViewGroupB06-28 11:33:48.721 24137-24137/com.sbingo.viewsample I/Sbingo ViewGroupA: dispatchTouchEvent06-28 11:33:48.721 24137-24137/com.sbingo.viewsample I/Sbingo ViewGroupA: onInterceptTouchEvent06-28 11:33:48.721 24137-24137/com.sbingo.viewsample I/Sbingo ViewGroupB: dispatchTouchEvent06-28 11:33:48.721 24137-24137/com.sbingo.viewsample I/Sbingo ViewGroupB: onInterceptTouchEvent06-28 11:33:48.721 24137-24137/com.sbingo.viewsample I/Sbingo ViewGroupB: onTouchEvent06-28 11:33:48.721 24137-24137/com.sbingo.viewsample I/Sbingo ViewGroupA: onTouchEvent

如果点击的是ViewGroupB,ViewGroupB就会拦截事件,但因为也没有消耗事件,事件又向上传递

点击ViewGroupA06-28 11:35:04.516 24137-24137/com.sbingo.viewsample I/Sbingo ViewGroupA: dispatchTouchEvent06-28 11:35:04.517 24137-24137/com.sbingo.viewsample I/Sbingo ViewGroupA: onInterceptTouchEvent06-28 11:35:04.517 24137-24137/com.sbingo.viewsample I/Sbingo ViewGroupA: onTouchEvent

如果点击的是ViewGroupA,ViewGroupA就直接拦截了触摸事件。

这3种输出是最基本的分发流程,相信大部分开发是没有疑问的。

点击MyView,MyView消耗事件06-28 11:49:05.194 13115-13115/com.sbingo.viewsample I/Sbingo ViewGroupA: dispatchTouchEvent06-28 11:49:05.194 13115-13115/com.sbingo.viewsample I/Sbingo ViewGroupA: onInterceptTouchEvent06-28 11:49:05.194 13115-13115/com.sbingo.viewsample I/Sbingo ViewGroupB: dispatchTouchEvent06-28 11:49:05.195 13115-13115/com.sbingo.viewsample I/Sbingo ViewGroupB: onInterceptTouchEvent06-28 11:49:05.195 13115-13115/com.sbingo.viewsample I/Sbingo MyView: dispatchTouchEvent06-28 11:49:05.195 13115-13115/com.sbingo.viewsample I/Sbingo MyView: onTouchEvent:006-28 11:49:05.327 13115-13115/com.sbingo.viewsample I/Sbingo ViewGroupA: dispatchTouchEvent06-28 11:49:05.327 13115-13115/com.sbingo.viewsample I/Sbingo ViewGroupA: onInterceptTouchEvent06-28 11:49:05.328 13115-13115/com.sbingo.viewsample I/Sbingo ViewGroupB: dispatchTouchEvent06-28 11:49:05.328 13115-13115/com.sbingo.viewsample I/Sbingo ViewGroupB: onInterceptTouchEvent06-28 11:49:05.328 13115-13115/com.sbingo.viewsample I/Sbingo MyView: dispatchTouchEvent06-28 11:49:05.328 13115-13115/com.sbingo.viewsample I/Sbingo MyView: onTouchEvent:1

MyView消耗触摸事件后,事件不再向上传递,之后该事件的所有动作都传到MyView这里,其中可能包含多次的move(示例中没有这种情况)。

点击MyView,ViewGroupB拦截06-28 11:48:15.129 12066-12066/com.sbingo.viewsample I/Sbingo ViewGroupA: dispatchTouchEvent06-28 11:48:15.129 12066-12066/com.sbingo.viewsample I/Sbingo ViewGroupA: onInterceptTouchEvent06-28 11:48:15.129 12066-12066/com.sbingo.viewsample I/Sbingo ViewGroupB: dispatchTouchEvent06-28 11:48:15.129 12066-12066/com.sbingo.viewsample I/Sbingo ViewGroupB: onInterceptTouchEvent06-28 11:48:15.130 12066-12066/com.sbingo.viewsample I/Sbingo ViewGroupB: onTouchEvent06-28 11:48:15.130 12066-12066/com.sbingo.viewsample I/Sbingo ViewGroupA: onTouchEvent

这和直接点击ViewGroupB的输出一样。

现在为ViewGroupB和MyView设置OnTouchListener和OnClickListener,并添加打印日志。

点击MyView,MyView在onTouchEvent消耗事件06-28 16:28:24.140 6251-6251/com.sbingo.viewsample I/Sbingo ViewGroupA: dispatchTouchEvent06-28 16:28:24.140 6251-6251/com.sbingo.viewsample I/Sbingo ViewGroupA: onInterceptTouchEvent06-28 16:28:24.140 6251-6251/com.sbingo.viewsample I/Sbingo ViewGroupB: dispatchTouchEvent06-28 16:28:24.141 6251-6251/com.sbingo.viewsample I/Sbingo ViewGroupB: onInterceptTouchEvent06-28 16:28:24.141 6251-6251/com.sbingo.viewsample I/Sbingo MyView: dispatchTouchEvent06-28 16:28:24.141 6251-6251/com.sbingo.viewsample I/Sbingo MyView: onTouch06-28 16:28:24.141 6251-6251/com.sbingo.viewsample I/Sbingo MyView: onTouchEvent:006-28 16:28:24.238 6251-6251/com.sbingo.viewsample I/Sbingo ViewGroupA: dispatchTouchEvent06-28 16:28:24.238 6251-6251/com.sbingo.viewsample I/Sbingo ViewGroupA: onInterceptTouchEvent06-28 16:28:24.238 6251-6251/com.sbingo.viewsample I/Sbingo ViewGroupB: dispatchTouchEvent06-28 16:28:24.238 6251-6251/com.sbingo.viewsample I/Sbingo ViewGroupB: onInterceptTouchEvent06-28 16:28:24.238 6251-6251/com.sbingo.viewsample I/Sbingo MyView: dispatchTouchEvent06-28 16:28:24.238 6251-6251/com.sbingo.viewsample I/Sbingo MyView: onTouch06-28 16:28:24.239 6251-6251/com.sbingo.viewsample I/Sbingo MyView: onTouchEvent:1点击MyView,MyView不消耗事件06-28 16:39:52.048 17495-17495/com.sbingo.viewsample I/Sbingo ViewGroupA: dispatchTouchEvent06-28 16:39:52.049 17495-17495/com.sbingo.viewsample I/Sbingo ViewGroupA: onInterceptTouchEvent06-28 16:39:52.049 17495-17495/com.sbingo.viewsample I/Sbingo ViewGroupB: dispatchTouchEvent06-28 16:39:52.049 17495-17495/com.sbingo.viewsample I/Sbingo ViewGroupB: onInterceptTouchEvent06-28 16:39:52.049 17495-17495/com.sbingo.viewsample I/Sbingo MyView: dispatchTouchEvent06-28 16:39:52.049 17495-17495/com.sbingo.viewsample I/Sbingo MyView: onTouch06-28 16:39:52.049 17495-17495/com.sbingo.viewsample I/Sbingo MyView: onTouchEvent:006-28 16:39:52.136 17495-17495/com.sbingo.viewsample I/Sbingo ViewGroupA: dispatchTouchEvent06-28 16:39:52.137 17495-17495/com.sbingo.viewsample I/Sbingo ViewGroupA: onInterceptTouchEvent06-28 16:39:52.137 17495-17495/com.sbingo.viewsample I/Sbingo ViewGroupB: dispatchTouchEvent06-28 16:39:52.137 17495-17495/com.sbingo.viewsample I/Sbingo ViewGroupB: onInterceptTouchEvent06-28 16:39:52.137 17495-17495/com.sbingo.viewsample I/Sbingo MyView: dispatchTouchEvent06-28 16:39:52.137 17495-17495/com.sbingo.viewsample I/Sbingo MyView: onTouch06-28 16:39:52.137 17495-17495/com.sbingo.viewsample I/Sbingo MyView: onTouchEvent:106-28 16:39:52.162 17495-17495/com.sbingo.viewsample I/Sbingo MyView: onClick

可以发现

1.onTouch方法先于onTouchEvent方法执行

2.当MyView不消耗触摸事件时,onClick方法才得到执行,否则不会进入onClick方法。

3.不管MyView是否消耗事件,事件都没有向上传递

点击MyView,MyView的onTouch方法消耗事件07-07 11:08:42.158 21627-21627/com.sbingo.viewsample I/Sbingo ViewGroupA: dispatchTouchEvent07-07 11:08:42.158 21627-21627/com.sbingo.viewsample I/Sbingo ViewGroupA: onInterceptTouchEvent07-07 11:08:42.158 21627-21627/com.sbingo.viewsample I/Sbingo ViewGroupB: dispatchTouchEvent07-07 11:08:42.158 21627-21627/com.sbingo.viewsample I/Sbingo ViewGroupB: onInterceptTouchEvent07-07 11:08:42.159 21627-21627/com.sbingo.viewsample I/Sbingo MyView: dispatchTouchEvent07-07 11:08:42.159 21627-21627/com.sbingo.viewsample I/Sbingo MyView: onTouch07-07 11:08:42.255 21627-21627/com.sbingo.viewsample I/Sbingo ViewGroupA: dispatchTouchEvent07-07 11:08:42.255 21627-21627/com.sbingo.viewsample I/Sbingo ViewGroupA: onInterceptTouchEvent07-07 11:08:42.255 21627-21627/com.sbingo.viewsample I/Sbingo ViewGroupB: dispatchTouchEvent07-07 11:08:42.255 21627-21627/com.sbingo.viewsample I/Sbingo ViewGroupB: onInterceptTouchEvent07-07 11:08:42.256 21627-21627/com.sbingo.viewsample I/Sbingo MyView: dispatchTouchEvent07-07 11:08:42.256 21627-21627/com.sbingo.viewsample I/Sbingo MyView: onTouch

刚才已经发现onTouch方法先于onTouchEvent方法执行,现在onTouch方法消耗事件后,onTouchEvent方法便不会执行,之后该事件的所有动作都传到MyView的onTouch方法。

点击MyView,ViewGroupB拦截06-28 16:44:51.593 20418-20418/com.sbingo.viewsample I/Sbingo ViewGroupA: dispatchTouchEvent06-28 16:44:51.594 20418-20418/com.sbingo.viewsample I/Sbingo ViewGroupA: onInterceptTouchEvent06-28 16:44:51.594 20418-20418/com.sbingo.viewsample I/Sbingo ViewGroupB: dispatchTouchEvent06-28 16:44:51.594 20418-20418/com.sbingo.viewsample I/Sbingo ViewGroupB: onInterceptTouchEvent06-28 16:44:51.594 20418-20418/com.sbingo.viewsample I/Sbingo ViewGroupB: onTouch06-28 16:44:51.594 20418-20418/com.sbingo.viewsample I/Sbingo ViewGroupB: onTouchEvent06-28 16:44:51.691 20418-20418/com.sbingo.viewsample I/Sbingo ViewGroupA: dispatchTouchEvent06-28 16:44:51.691 20418-20418/com.sbingo.viewsample I/Sbingo ViewGroupA: onInterceptTouchEvent06-28 16:44:51.692 20418-20418/com.sbingo.viewsample I/Sbingo ViewGroupB: dispatchTouchEvent06-28 16:44:51.692 20418-20418/com.sbingo.viewsample I/Sbingo ViewGroupB: onTouch06-28 16:44:51.692 20418-20418/com.sbingo.viewsample I/Sbingo ViewGroupB: onTouchEvent06-28 16:44:51.698 20418-20418/com.sbingo.viewsample I/Sbingo ViewGroupB: onClick

当ViewGroupB拦截事件后,之后对于该事件的所有动作ViewGroupB的dispatchTouchEvent方法将返回true,

ViewGroupB的onInterceptTouchEvent方法将不再执行。

去除MyView的OnClickListener,点击MyView,MyView不消耗事件07-07 11:29:06.078 7138-7138/com.sbingo.viewsample I/Sbingo ViewGroupA: dispatchTouchEvent07-07 11:29:06.078 7138-7138/com.sbingo.viewsample I/Sbingo ViewGroupA: onInterceptTouchEvent07-07 11:29:06.078 7138-7138/com.sbingo.viewsample I/Sbingo ViewGroupB: dispatchTouchEvent07-07 11:29:06.078 7138-7138/com.sbingo.viewsample I/Sbingo ViewGroupB: onInterceptTouchEvent07-07 11:29:06.079 7138-7138/com.sbingo.viewsample I/Sbingo MyView: dispatchTouchEvent07-07 11:29:06.079 7138-7138/com.sbingo.viewsample I/Sbingo MyView: onTouch07-07 11:29:06.079 7138-7138/com.sbingo.viewsample I/Sbingo MyView: onTouchEvent:007-07 11:29:06.079 7138-7138/com.sbingo.viewsample I/Sbingo ViewGroupB: onTouch07-07 11:29:06.079 7138-7138/com.sbingo.viewsample I/Sbingo ViewGroupB: onTouchEvent07-07 11:29:06.125 7138-7138/com.sbingo.viewsample I/Sbingo ViewGroupA: dispatchTouchEvent07-07 11:29:06.126 7138-7138/com.sbingo.viewsample I/Sbingo ViewGroupA: onInterceptTouchEvent07-07 11:29:06.126 7138-7138/com.sbingo.viewsample I/Sbingo ViewGroupB: dispatchTouchEvent07-07 11:29:06.126 7138-7138/com.sbingo.viewsample I/Sbingo ViewGroupB: onTouch07-07 11:29:06.126 7138-7138/com.sbingo.viewsample I/Sbingo ViewGroupB: onTouchEvent07-07 11:29:06.144 7138-7138/com.sbingo.viewsample I/Sbingo ViewGroupB: onClick接着去除ViewGroupB的OnClickListener,点击MyView,MyView不消耗事件07-07 11:42:36.186 17561-17561/com.sbingo.viewsample I/Sbingo ViewGroupA: dispatchTouchEvent07-07 11:42:36.186 17561-17561/com.sbingo.viewsample I/Sbingo ViewGroupA: onInterceptTouchEvent07-07 11:42:36.187 17561-17561/com.sbingo.viewsample I/Sbingo ViewGroupB: dispatchTouchEvent07-07 11:42:36.187 17561-17561/com.sbingo.viewsample I/Sbingo ViewGroupB: onInterceptTouchEvent07-07 11:42:36.187 17561-17561/com.sbingo.viewsample I/Sbingo MyView: dispatchTouchEvent07-07 11:42:36.187 17561-17561/com.sbingo.viewsample I/Sbingo MyView: onTouch07-07 11:42:36.187 17561-17561/com.sbingo.viewsample I/Sbingo MyView: onTouchEvent:007-07 11:42:36.187 17561-17561/com.sbingo.viewsample I/Sbingo ViewGroupB: onTouch07-07 11:42:36.187 17561-17561/com.sbingo.viewsample I/Sbingo ViewGroupB: onTouchEvent07-07 11:42:36.187 17561-17561/com.sbingo.viewsample I/Sbingo ViewGroupA: onTouchEvent

可以发现

当没有OnClickListener时,触摸事件就会向上传递,否则即使不消耗事件也不会;向上传递时其实也是先到onTouch再到onTouchEvent方法。

到这里,我们已经基本了解了分发流程,对于如何拦截也都知道了。

那么如果子View不想被拦截,如何实现反拦截呢?

ViewGroup有一个requestDisallowInterceptTouchEvent`方法,是专门用来反拦截的,传入true表示子View希望反拦截,false表示由父布局决定是否拦截。

我们来试一试这个方法:

app启动时调用ViewGroupB的requestDisallowInterceptTouchEvent(true)方法,点击MyView,ViewGroupB拦截07-07 13:22:20.082 12098-12098/com.sbingo.viewsample I/Sbingo ViewGroupB: requestDisallowInterceptTouchEvent07-07 13:22:20.082 12098-12098/com.sbingo.viewsample I/Sbingo ViewGroupA: requestDisallowInterceptTouchEvent07-07 13:22:41.056 12098-12098/com.sbingo.viewsample I/Sbingo ViewGroupA: dispatchTouchEvent07-07 13:22:41.056 12098-12098/com.sbingo.viewsample I/Sbingo ViewGroupA: onInterceptTouchEvent07-07 13:22:41.057 12098-12098/com.sbingo.viewsample I/Sbingo ViewGroupB: dispatchTouchEvent07-07 13:22:41.057 12098-12098/com.sbingo.viewsample I/Sbingo ViewGroupB: onInterceptTouchEvent07-07 13:22:41.057 12098-12098/com.sbingo.viewsample I/Sbingo ViewGroupB: onTouch07-07 13:22:41.057 12098-12098/com.sbingo.viewsample I/Sbingo ViewGroupB: onTouchEvent07-07 13:22:41.057 12098-12098/com.sbingo.viewsample I/Sbingo ViewGroupA: onTouchEvent

当调用ViewGroupB的requestDisallowInterceptTouchEvent(true)方法时,

会自动向上调用ViewGroupA的requestDisallowInterceptTouchEvent方法,

之后在ViewGroupA的onInterceptTouchEvent方法中返回了true进行拦截,拦截成功了。

可见此时requestDisallowInterceptTouchEvent(true)并没有起作用。

这次因为

1.每次点击时,都会重置。所以我们在启动时的设置没有作用。

2.ACTION_DOWN不能被拦截,否则后续事件都不会向下传递。

ViewGroupB只在ACTION_DOWN时不拦截,其余情况拦截,点击MyView07-07 13:36:44.715 25397-25397/com.sbingo.viewsample I/Sbingo ViewGroupB: requestDisallowInterceptTouchEvent07-07 13:36:44.715 25397-25397/com.sbingo.viewsample I/Sbingo ViewGroupA: requestDisallowInterceptTouchEvent07-07 13:36:49.350 25397-25397/com.sbingo.viewsample I/Sbingo ViewGroupA: dispatchTouchEvent07-07 13:36:49.350 25397-25397/com.sbingo.viewsample I/Sbingo ViewGroupA: onInterceptTouchEvent07-07 13:36:49.350 25397-25397/com.sbingo.viewsample I/Sbingo ViewGroupB: dispatchTouchEvent07-07 13:36:49.351 25397-25397/com.sbingo.viewsample I/Sbingo ViewGroupB: onInterceptTouchEvent07-07 13:36:49.352 25397-25397/com.sbingo.viewsample I/Sbingo MyView: dispatchTouchEvent07-07 13:36:49.352 25397-25397/com.sbingo.viewsample I/Sbingo MyView: onTouch07-07 13:36:49.352 25397-25397/com.sbingo.viewsample I/Sbingo MyView: onTouchEvent:007-07 13:36:49.435 25397-25397/com.sbingo.viewsample I/Sbingo ViewGroupA: dispatchTouchEvent07-07 13:36:49.435 25397-25397/com.sbingo.viewsample I/Sbingo ViewGroupA: onInterceptTouchEvent07-07 13:36:49.435 25397-25397/com.sbingo.viewsample I/Sbingo ViewGroupB: dispatchTouchEvent07-07 13:36:49.436 25397-25397/com.sbingo.viewsample I/Sbingo ViewGroupB: onInterceptTouchEvent07-07 13:36:49.436 25397-25397/com.sbingo.viewsample I/Sbingo MyView: dispatchTouchEvent07-07 13:36:49.436 25397-25397/com.sbingo.viewsample I/Sbingo MyView: onTouch07-07 13:36:49.436 25397-25397/com.sbingo.viewsample I/Sbingo MyView: onTouchEvent:3

MyView只收到了ACTION_DOWN和ACTION_CANCEL两个动作。

接下来演示如何正确地实现反拦截:

ViewGroupB只在ACTION_DOWN时不拦截,其余情况拦截,重写MyView的dispatchTouchEvent方法如下:@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {myLogger.log(Level.INFO, "dispatchTouchEvent");switch (ev.getAction()) {case MotionEvent.ACTION_DOWN:getParent().requestDisallowInterceptTouchEvent(true);break;default:}return super.dispatchTouchEvent(ev);}点击MyView,MyView消耗事件07-07 14:13:50.178 26911-26911/com.sbingo.viewsample I/Sbingo ViewGroupA: dispatchTouchEvent07-07 14:13:50.179 26911-26911/com.sbingo.viewsample I/Sbingo ViewGroupA: onInterceptTouchEvent07-07 14:13:50.179 26911-26911/com.sbingo.viewsample I/Sbingo ViewGroupB: dispatchTouchEvent07-07 14:13:50.179 26911-26911/com.sbingo.viewsample I/Sbingo ViewGroupB: onInterceptTouchEvent07-07 14:13:50.179 26911-26911/com.sbingo.viewsample I/Sbingo MyView: dispatchTouchEvent07-07 14:13:50.179 26911-26911/com.sbingo.viewsample I/Sbingo ViewGroupB: requestDisallowInterceptTouchEvent07-07 14:13:50.179 26911-26911/com.sbingo.viewsample I/Sbingo ViewGroupA: requestDisallowInterceptTouchEvent07-07 14:13:50.179 26911-26911/com.sbingo.viewsample I/Sbingo MyView: onTouch07-07 14:13:50.179 26911-26911/com.sbingo.viewsample I/Sbingo MyView: onTouchEvent:007-07 14:13:50.251 26911-26911/com.sbingo.viewsample I/Sbingo ViewGroupA: dispatchTouchEvent07-07 14:13:50.251 26911-26911/com.sbingo.viewsample I/Sbingo ViewGroupB: dispatchTouchEvent07-07 14:13:50.251 26911-26911/com.sbingo.viewsample I/Sbingo MyView: dispatchTouchEvent07-07 14:13:50.251 26911-26911/com.sbingo.viewsample I/Sbingo MyView: onTouch07-07 14:13:50.251 26911-26911/com.sbingo.viewsample I/Sbingo MyView: onTouchEvent:1

这里我们更换了调用requestDisallowInterceptTouchEvent方法的时机,

调用getParent().requestDisallowInterceptTouchEvent(true)后,

之后对于该事件的所有动作,ViewGroupA和ViewGroupB的onInterceptTouchEvent方法都没有执行,

直接执行了子View的dispatchTouchEvent方法, 反拦截成功。

至此,所有可能的流程基本都已分析过。

至于为什么这样就能反拦截成功,请接着往下看。

###源码分析

对于以上的分析结论,基本是没有疑问的。

主要困惑应该在于requestDisallowInterceptTouchEvent方法的使用上,我们着重讲一下这个方法。先看一下它的源码:

@Overridepublic void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {// We're already in this state, assume our ancestors are tooreturn;}if (disallowIntercept) {mGroupFlags |= FLAG_DISALLOW_INTERCEPT;} else {mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;}// Pass it up to our parentif (mParent != null) {mParent.requestDisallowInterceptTouchEvent(disallowIntercept);}}

这个方法对mGroupFlags进行位运算,并依次调用父布局的requestDisallowInterceptTouchEvent方法,这和之前的log输出相同。

@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {......final int action = ev.getAction();final int actionMasked = action & MotionEvent.ACTION_MASK;// Handle an initial down.if (actionMasked == MotionEvent.ACTION_DOWN) {// Throw away all previous state when starting a new touch gesture.// The framework may have dropped the up or cancel event for the previous gesture// due to an app switch, ANR, or some other state change.cancelAndClearTouchTargets(ev);resetTouchState();}// Check for interception.final boolean intercepted;if (actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null) {final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;if (!disallowIntercept) {intercepted = onInterceptTouchEvent(ev);ev.setAction(action); // restore action in case it was changed} else {intercepted = false;}} else {// There are no touch targets and this action is not an initial down// so this view group continues to intercept touches.intercepted = true;}......}

dispatchTouchEvent方法中,如果触摸动作是ACTION_DOWN,第15行就会调用resetTouchState方法:

/*** Resets all touch state in preparation for a new cycle.*/private void resetTouchState() {clearTouchTargets();resetCancelNextUpFlag(this);mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;mNestedScrollAxes = SCROLL_AXIS_NONE;}

这个重置方法的第7行又对mGroupFlags进行了位运算,再回到dispatchTouchEvent方法,看第22行的判断,使得ACTION_DOWN情况下第24行的onInterceptTouchEvent总是得到执行。

也就是说,ACTION_DOWN时,会重置触摸状态,onInterceptTouchEvent方法必定会执行。

所以之前在app启动时调用requestDisallowInterceptTouchEvent(true)方法,反拦截无效;在MyView收到ACTION_DOWN时调用requestDisallowInterceptTouchEvent(true)方法就有效,之后的动作就跳过onInterceptTouchEvent方法,直接分发给了MyView的dispatchTouchEvent方法。

第24、27、32行3处的intercepted赋值分别表示以下3种情况:

ACTION_DOWN或允许拦截、

不允许拦截(就是反拦截情况)

已经决定拦截且不是ACTION_DOWN动作。

说了这么久的拦截,拦截方法是怎么样的呢?来看一下:

public boolean onInterceptTouchEvent(MotionEvent ev) {if (ev.isFromSource(InputDevice.SOURCE_MOUSE)&& ev.getAction() == MotionEvent.ACTION_DOWN&& ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)&& isOnScrollbarThumb(ev.getX(), ev.getY())) {return true;}return false;}

可以看到,一般情况下是不会拦截的。

###总结

根据上面的所有分析,可以画出如下的事件分发拦截流程图:

这张图涵盖了大部分的情况,有助于我们理解和记忆。

除了这张图以外,关于事件分发拦截,我还有以下总结:

ViewGroup比View多了两个拦截相关的方法。有很多方法可以获取触摸事件,可以根据业务场景在合适的时机获取并实现业务逻辑。父布局拦截可以一直拦截,也可以根据业务边界动态改变。子View反拦截也可以分为静态和动态实现,但要注意反拦截方法的调用时机。核心要点就是熟悉整个流程,在合适的时机和View层次实现业务逻辑。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。