前段时间做项目的时候,碰见listview上的button点击总是没有效果的问题,以及一些自定义组合空间点击木有相应,或者是响应的结果不对。当时只是在网上搜了些解决的办法,很乱。嘿嘿,现在想起来真的的好傻。最近拜读了两篇大神的文章讲解android事件分发机制的,自己mark一下,防止遗忘,同时方便以后查找。
事件分发主要分为两部分:view的事件分发和viewgroup的事件分发。在探讨事件分发机制之前,先需要搞清楚android两个基础控件view和viewgroup,以及它们之间的关系:view是没有子控件的,像button,textview都是view控件。而viewgroup继承自view,是可以存在子控件的。也就是说viewgroup就是一组view或者是viewroup的集合,它是所有页面布局的父类(eg linearlayout,relativelayout)
1.view的事件分发: dsipatchTouchEvent 方法:事件分发 和 onTouchEvent方法:事件消费
先做一个小例子,显示下事件分发和消费的过程:
自定义一个CustomButton,并重写dsipatchTouchEvent 和onTouchEvent:
- package com.example.motioneventdispatchtest;
- import android.content.Context;
- import android.util.AttributeSet;
- import android.util.Log;
- import android.view.MotionEvent;
- import android.widget.Button;
- public class CustomButton extends Button {
- private static final String TAG = "MotionEventDispatch";
- public CustomButton(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
- // 位于textview里面
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- switch (event.getAction()) {
- case MotionEvent.ACTION_DOWN:
- Log.i(TAG, "CustomButton-onTouchEvent-ACTION_DOWN");
- break;
- case MotionEvent.ACTION_UP:
- Log.i(TAG, "CustomButton-onTouchEvent-ACTION_UP");
- break;
- default:
- break;
- }
- return super.onTouchEvent(event);
- }
- // 位于view里面
- @Override
- public boolean dispatchTouchEvent(MotionEvent event) {
- switch (event.getAction()) {
- case MotionEvent.ACTION_DOWN:
- Log.i(TAG, "CustomButton-dispatchTouchEvent-ACTION_DOWN");
- break;
- case MotionEvent.ACTION_UP:
- Log.i(TAG, "CustomButton-dispatchTouchEvent-ACTION_UP");
- break;
- default:
- break;
- }
- return super.dispatchTouchEvent(event);
- }
- }
然后在MainActivity中监听注册click监听和touch监听,并且重写activity的dsipatchTouchEvent 和onTouchEvent:
- package com.example.motioneventdispatchtest;
- import android.app.Activity;
- import android.os.Bundle;
- import android.util.Log;
- import android.view.Menu;
- import android.view.MotionEvent;
- import android.view.View;
- public class MainActivity extends Activity {
- private static final String TAG = "MotionEventDispatch";
- private CustomButton button;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- button = (CustomButton)findViewById(R.id.button1);
- button.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View arg0) {
- Log.i(TAG, "CustomButton--onClick");
- }
- });
- button.setOnTouchListener(new View.OnTouchListener() {
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- switch (event.getAction()) {
- case MotionEvent.ACTION_DOWN:
- Log.i(TAG, "CustomButton-onTouch-ACTION_DOWN");
- break;
- case MotionEvent.ACTION_UP:
- Log.i(TAG, "CustomButton-onTouch-ACTION_UP");
- break;
- default:
- break;
- }
- return false;
- }
- });
- }
- @Override
- public boolean dispatchTouchEvent(MotionEvent ev) {
- switch (ev.getAction()) {
- case MotionEvent.ACTION_DOWN:
- Log.i(TAG, "MainActivity-dispatchTouchEvent-ACTION_DOWN");
- break;
- case MotionEvent.ACTION_UP:
- Log.i(TAG, "MainActivity-dispatchTouchEvent-ACTION_UP");
- break;
- default:
- break;
- }
- return super.dispatchTouchEvent(ev);
- }
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- switch (event.getAction()) {
- case MotionEvent.ACTION_DOWN:
- Log.i(TAG, "MainActivity-onTouchEvent-ACTION_DOWN");
- break;
- case MotionEvent.ACTION_UP:
- Log.i(TAG, "MainActivity-onTouchEvent-ACTION_UP");
- break;
- default:
- break;
- }
- return super.onTouchEvent(event);
- }
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- // Inflate the menu; this adds items to the action bar if it is present.
- getMenuInflater().inflate(R.menu.activity_main, menu);
- return true;
- }
- }
布局文件:
- <RelativeLayout 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"
- tools:context=".MainActivity" >
- <com.example.motioneventdispatchtest.CustomButton
- android:id="@+id/button1"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentTop="true"
- android:layout_centerHorizontal="true"
- android:layout_marginTop="171dp" />
- </RelativeLayout>
点击button之后,log如下:
- 05-22 00:15:28.905: I/MotionEventDispatch(22044): MainActivity-dispatchTouchEvent-ACTION_DOWN
- 05-22 00:15:28.908: I/MotionEventDispatch(22044): CustomButton-dispatchTouchEvent-ACTION_DOWN
- 05-22 00:15:28.908: I/MotionEventDispatch(22044): CustomButton-onTouch-ACTION_DOWN
- 05-22 00:15:28.909: I/MotionEventDispatch(22044): CustomButton-onTouchEvent-ACTION_DOWN
- 05-22 00:15:28.969: I/MotionEventDispatch(22044): MainActivity-dispatchTouchEvent-ACTION_UP
- 05-22 00:15:28.969: I/MotionEventDispatch(22044): CustomButton-dispatchTouchEvent-ACTION_UP
- 05-22 00:15:28.971: I/MotionEventDispatch(22044): CustomButton-onTouch-ACTION_UP
- 05-22 00:15:28.971: I/MotionEventDispatch(22044): CustomButton-onTouchEvent-ACTION_UP
- 05-22 00:15:28.975: I/MotionEventDispatch(22044): CustomButton--onClick
过程如下:touch down事件首先由activity的dsipatchTouchEvent 进行分发,接着道CustomButton的dsipatchTouchEvent 进行分发,然后执行CustomButton的onTouch,接着是onTouchEvent。up的时候顺序是相同的,最后执行了onTouchEvent里面的onclick方法。结果为什么会是这样的呢,下面从源代码的角度进行分析:
- public boolean dispatchTouchEvent(MotionEvent event) {
- if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
- mOnTouchListener.onTouch(this, event)) {
- return true;
- }
- return onTouchEvent(event);
- }
mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && mOnTouchListener.onTouch(this, event)如果这三个条件都为真,就返回true,否则的话就去执行onTouchEvent(event)并返回结果。
第一个条件:mOnTouchListener != null,只要控件注册了touch事件,mOnTouchListener 就会被赋值不为空,代码为证:
- public void setOnTouchListener(OnTouchListener l) {
- mOnTouchListener = l;
- }
第二个条件: (mViewFlags & ENABLED_MASK) == ENABLED,一般的控件都是enabled的吧,所以这个也是true咯
第三个条件:mOnTouchListener.onTouch(this, event),这里是回调touch注册事件里面的onTouch方法,如果onTouch返回true则dsipatchTouchEvent 也会返回true;如果onTouch方法返回false,则会再去执行onTouchEvent方法。
这里可以看出dsipatchTouchEvent 中onTouch和onTouchEvent方法都会被执行,最先执行的是onTouc,所以也就解释了为什么打印出来的log中onTouch比onclick要先出现咯,现在也仅仅是解释了这个。既然oclick事件被执行了,那么就肯定是在onTouchEvent方法里面被执行的,嘿嘿。
- public boolean onTouchEvent(MotionEvent event) {
- final int viewFlags = mViewFlags;
- if ((viewFlags & ENABLED_MASK) == DISABLED) {
- // A disabled view that is clickable still consumes the touch
- // events, it just doesn't respond to them.
- return (((viewFlags & CLICKABLE) == CLICKABLE ||
- (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
- }
- if (mTouchDelegate != null) {
- if (mTouchDelegate.onTouchEvent(event)) {
- return true;
- }
- }
- if (((viewFlags & CLICKABLE) == CLICKABLE ||
- (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
- switch (event.getAction()) {
- case MotionEvent.ACTION_UP:
- boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;
- if ((mPrivateFlags & 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 (!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) {
- mPrivateFlags |= PRESSED;
- refreshDrawableState();
- postDelayed(mUnsetPressedState,
- ViewConfiguration.getPressedStateDuration());
- } else if (!post(mUnsetPressedState)) {
- // If the post failed, unpress right now
- mUnsetPressedState.run();
- }
- removeTapCallback();
- }
- break;
- case MotionEvent.ACTION_DOWN:
- if (mPendingCheckForTap == null) {
- mPendingCheckForTap = new CheckForTap();
- }
- mPrivateFlags |= PREPRESSED;
- mHasPerformedLongPress = false;
- postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
- break;
- case MotionEvent.ACTION_CANCEL:
- mPrivateFlags &= ~PRESSED;
- refreshDrawableState();
- removeTapCallback();
- break;
- case MotionEvent.ACTION_MOVE:
- final int x = (int) event.getX();
- final int y = (int) event.getY();
- // Be lenient about moving outside of buttons
- int slop = mTouchSlop;
- if ((x < 0 - slop) || (x >= getWidth() + slop) ||
- (y < 0 - slop) || (y >= getHeight() + slop)) {
- // Outside button
- removeTapCallback();
- if ((mPrivateFlags & PRESSED) != 0) {
- // Remove any future long press/tap checks
- removeLongPressCallback();
- // Need to switch from pressed to not pressed
- mPrivateFlags &= ~PRESSED;
- refreshDrawableState();
- }
- }
- break;
- }
- return true;
- }
- return false;
- }
代码最终会进入performClick方法中,代码如下:
- public boolean performClick() {
- sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
- if (mOnClickListener != null) {
- playSoundEffect(SoundEffectConstants.CLICK);
- mOnClickListener.onClick(this);
- return true;
- }
- return false;
- }
这里只要mOnClickListener不为空,就会触发onClick事件,而mOnClickListener则会在setOnClickListener当中被赋值。
- public void setOnClickListener(OnClickListener l) {
- if (!isClickable()) {
- setClickable(true);
- }
- mOnClickListener = l;
- }
现在对上了吧,就是在onTouchEvent方法里面执行了注册的onclick监听,所以整个流程是通了。
现在来总结一下为什么上面打印的log是那样子的:touch事件的入口是dsipatchTouchEvent ,首先执行注册的onTouch监听,如果返回的结果是false,就会接着执行onTouchEvent。在执行onTouchEvent的时候,会执行onClick监听,这样就解释清楚了打印的顺序。在dsipatchTouchEvent 进行事件分发的时候,一旦返回true,就表示该事件已经被消费了处理了,不会再继续往下传了。如果onTouch返回true,那么它dsipatchTouchEvent 就会返回true,表示事件已被处理。不会去执行onTouchEvent了,也就更不会去处理onclick监听了。这里我们改一下上面的例子,看下打印出来的结果哈:
- 05-22 01:25:19.918: I/MotionEventDispatch(25626): MainActivity-dispatchTouchEvent-ACTION_DOWN
- 05-22 01:25:19.919: I/MotionEventDispatch(25626): CustomButton-dispatchTouchEvent-ACTION_DOWN
- 05-22 01:25:19.919: I/MotionEventDispatch(25626): CustomButton-onTouch-ACTION_DOWN
- 05-22 01:25:19.996: I/MotionEventDispatch(25626): MainActivity-dispatchTouchEvent-ACTION_UP
- 05-22 01:25:19.996: I/MotionEventDispatch(25626): CustomButton-dispatchTouchEvent-ACTION_UP
- 05-22 01:25:19.996: I/MotionEventDispatch(25626): CustomButton-onTouch-ACTION_UP
是不是onTouchEvent就没有执行了哇。
这里有一个很重要的知识点,是在一个大牛的文章上看到的,受益匪浅,在这里引用一下,像郭霖大侠致敬:
http://blog.csdn.net/guolin_blog/article/details/9097463
- 就是touch事件的层级传递。我们都知道如果给一个控件注册了touch事件,每次点击它的时候都会触发一系列的ACTION_DOWN,ACTION_MOVE,ACTION_UP等事件。这里需要注意,如果你在执行ACTION_DOWN的时候返回了false,后面一系列其它的action就不会再得到执行了。简单的说,就是当dispatchTouchEvent在进行事件分发的时候,只有前一个action返回true,才会触发后一个action。
- 说到这里,很多的朋友肯定要有巨大的疑问了。这不是在自相矛盾吗?前面的例子中,明明在onTouch事件里面返回了false,ACTION_DOWN和ACTION_UP不是都得到执行了吗?其实你只是被假象所迷惑了,让我们仔细分析一下,在前面的例子当中,我们到底返回的是什么。
- 参考着我们前面分析的源码,首先在onTouch事件里返回了false,就一定会进入到onTouchEvent方法中,然后我们来看一下onTouchEvent方法的细节。由于我们点击了按钮,就会进入到第14行这个if判断的内部,然后你会发现,不管当前的action是什么,最终都一定会走到第89行,返回一个true。
- 是不是有一种被欺骗的感觉?明明在onTouch事件里返回了false,系统还是在onTouchEvent方法中帮你返回了true。就因为这个原因,才使得前面的例子中ACTION_UP可以得到执行。
- 那我们可以换一个控件,将按钮替换成ImageView,然后给它也注册一个touch事件,并返回false。如下所示:
- [java] view plaincopy
- imageView.setOnTouchListener(new OnTouchListener() {
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- Log.d("TAG", "onTouch execute, action " + event.getAction());
- return false;
- }
- });
- 运行一下程序,点击ImageView,你会发现结果如下:
- 在ACTION_DOWN执行完后,后面的一系列action都不会得到执行了。这又是为什么呢?因为ImageView和按钮不同,它是默认不可点击的,因此在onTouchEvent的第14行判断时无法进入到if的内部,直接跳到第91行返回了false,也就导致后面其它的action都无法执行了。
2.viewGroup的事件分发: dsipatchTouchEvent 方法:事件分发 和 onTouchEvent方法:事件消费 onInterceptTouchevent:事件拦截
相比较view而言,多了一个onInterceptTouchevent函数,那么它的作用是什么勒,看过另一篇文章,觉得它的比喻比较恰当和形象:onInterceptTouchevent就相当于viewgroup的一个管家,家庭秘书。为什么view没有呢,因为它没有子控件,用不着啊。一个touch事件来了,onInterceptTouchevent函数就负责判断决定是viewgroup自己消费处理呢,还是传递给它的孩子进行消费处理。
还是先弄一个demo吧,首先自定义一个layout:
- package com.example.motioneventdispatchtest;
- import android.content.Context;
- import android.util.AttributeSet;
- import android.util.Log;
- import android.view.MotionEvent;
- import android.widget.LinearLayout;
- public class CustomLayout extends LinearLayout {
- private final static String TAG = "MotionEventDispatch";
- public CustomLayout(Context context, AttributeSet attrs) {
- super(context, attrs);
- // TODO Auto-generated constructor stub
- }
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- switch (event.getAction()) {
- case MotionEvent.ACTION_DOWN:
- Log.i(TAG, "CustomLayout-onTouchEvent-ACTION_DOWN");
- break;
- case MotionEvent.ACTION_UP:
- Log.i(TAG, "CustomLayout-onTouchEvent-ACTION_UP");
- break;
- default:
- break;
- }
- return super.onTouchEvent(event);
- }
- @Override
- public boolean dispatchTouchEvent(MotionEvent ev) {
- switch (ev.getAction()) {
- case MotionEvent.ACTION_DOWN:
- Log.i(TAG, "CustomLayout-dispatchTouchEvent-ACTION_DOWN");
- break;
- case MotionEvent.ACTION_UP:
- Log.i(TAG, "CustomLayout-dispatchTouchEvent-ACTION_UP");
- break;
- default:
- break;
- }
- return super.dispatchTouchEvent(ev);
- }
- @Override
- public boolean onInterceptTouchEvent(MotionEvent ev) {
- switch (ev.getAction()) {
- case MotionEvent.ACTION_DOWN:
- Log.i(TAG, "CustomLayout-onInterceptTouchEvent-ACTION_DOWN");
- break;
- case MotionEvent.ACTION_UP:
- Log.i(TAG, "CustomLayout-onInterceptTouchEvent-ACTION_UP");
- break;
- default:
- break;
- }
- return super.onInterceptTouchEvent(ev);
- }
- }
- <RelativeLayout 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"
- tools:context=".MainActivity" >
- <com.example.motioneventdispatchtest.CustomLayout
- android:id="@+id/linearlayout_test"
- android:layout_width="200dip"
- android:layout_height="200dip" >
- <com.example.motioneventdispatchtest.CustomButton
- android:id="@+id/button1"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentTop="true"
- android:layout_centerHorizontal="true"
- android:layout_marginTop="171dp" />
- </com.example.motioneventdispatchtest.CustomLayout>
- </RelativeLayout>
然后修改MainActivity,添加自定义layout的click和touch事件:
- package com.example.motioneventdispatchtest;
- import android.app.Activity;
- import android.os.Bundle;
- import android.util.Log;
- import android.view.Menu;
- import android.view.MotionEvent;
- import android.view.View;
- public class MainActivity extends Activity {
- private static final String TAG = "MotionEventDispatch";
- private CustomButton button;
- private CustomLayout layout;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- button = (CustomButton) findViewById(R.id.button1);
- layout = (CustomLayout) findViewById(R.id.linearlayout_test);
- button.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View arg0) {
- Log.i(TAG, "CustomButton--onClick");
- }
- });
- button.setOnTouchListener(new View.OnTouchListener() {
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- switch (event.getAction()) {
- case MotionEvent.ACTION_DOWN:
- Log.i(TAG, "CustomButton-onTouch-ACTION_DOWN");
- break;
- case MotionEvent.ACTION_UP:
- Log.i(TAG, "CustomButton-onTouch-ACTION_UP");
- break;
- default:
- break;
- }
- return false;
- }
- });
- layout.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- Log.i(TAG, "CustomLayout---onClick");
- }
- });
- layout.setOnTouchListener(new View.OnTouchListener() {
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- switch (event.getAction()) {
- case MotionEvent.ACTION_DOWN:
- Log.i(TAG, "CustomLayout-onTouch-ACTION_DOWN");
- break;
- case MotionEvent.ACTION_UP:
- Log.i(TAG, "CustomLayout-onTouch-ACTION_UP");
- break;
- default:
- break;
- }
- return false;
- }
- });
- }
- @Override
- public boolean dispatchTouchEvent(MotionEvent ev) {
- switch (ev.getAction()) {
- case MotionEvent.ACTION_DOWN:
- Log.i(TAG, "MainActivity-dispatchTouchEvent-ACTION_DOWN");
- break;
- case MotionEvent.ACTION_UP:
- Log.i(TAG, "MainActivity-dispatchTouchEvent-ACTION_UP");
- break;
- default:
- break;
- }
- return super.dispatchTouchEvent(ev);
- }
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- switch (event.getAction()) {
- case MotionEvent.ACTION_DOWN:
- Log.i(TAG, "MainActivity-onTouchEvent-ACTION_DOWN");
- break;
- case MotionEvent.ACTION_UP:
- Log.i(TAG, "MainActivity-onTouchEvent-ACTION_UP");
- break;
- default:
- break;
- }
- return super.onTouchEvent(event);
- }
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- // Inflate the menu; this adds items to the action bar if it is present.
- getMenuInflater().inflate(R.menu.activity_main, menu);
- return true;
- }
- }
点击button的打印的log结果如下:
- 05-22 02:03:36.967: I/MotionEventDispatch(3045): MainActivity-dispatchTouchEvent-ACTION_DOWN
- 05-22 02:03:36.967: I/MotionEventDispatch(3045): CustomLayout-dispatchTouchEvent-ACTION_DOWN
- 05-22 02:03:36.967: I/MotionEventDispatch(3045): CustomLayout-onInterceptTouchEvent-ACTION_DOWN
- 05-22 02:03:36.967: I/MotionEventDispatch(3045): CustomButton-dispatchTouchEvent-ACTION_DOWN
- 05-22 02:03:36.967: I/MotionEventDispatch(3045): CustomButton-onTouch-ACTION_DOWN
- 05-22 02:03:36.967: I/MotionEventDispatch(3045): CustomButton-onTouchEvent-ACTION_DOWN
- 05-22 02:03:37.035: I/MotionEventDispatch(3045): MainActivity-dispatchTouchEvent-ACTION_UP
- 05-22 02:03:37.035: I/MotionEventDispatch(3045): CustomLayout-dispatchTouchEvent-ACTION_UP
- 05-22 02:03:37.036: I/MotionEventDispatch(3045): CustomLayout-onInterceptTouchEvent-ACTION_UP
- 05-22 02:03:37.036: I/MotionEventDispatch(3045): CustomButton-dispatchTouchEvent-ACTION_UP
- 05-22 02:03:37.036: I/MotionEventDispatch(3045): CustomButton-onTouch-ACTION_UP
- 05-22 02:03:37.036: I/MotionEventDispatch(3045): CustomButton-onTouchEvent-ACTION_UP
- 05-22 02:03:37.037: I/MotionEventDispatch(3045): CustomButton--onClick
上面的log打印结果显示首先调用viewgroup的dispatchTouchEvent,然后执行viewgroup的onInterceptTouchEvent拦截事件,最后执行的是子控件view的disPatchTouchEvent,剩下就和view的事件分发逻辑是一样的咯,但是为什么会是这样子的呢?
viewgroup的dispatchTouchEvent源码:
- public boolean dispatchTouchEvent(MotionEvent ev) {
- final int action = ev.getAction();
- final float xf = ev.getX();
- final float yf = ev.getY();
- final float scrolledXFloat = xf + mScrollX;
- final float scrolledYFloat = yf + mScrollY;
- final Rect frame = mTempRect;
- boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
- if (action == MotionEvent.ACTION_DOWN) {
- if (mMotionTarget != null) {
- mMotionTarget = null;
- }
- if (disallowIntercept || !onInterceptTouchEvent(ev)) {
- ev.setAction(MotionEvent.ACTION_DOWN);
- final int scrolledXInt = (int) scrolledXFloat;
- final int scrolledYInt = (int) scrolledYFloat;
- final View[] children = mChildren;
- final int count = mChildrenCount;
- for (int i = count - 1; i >= 0; i--) {
- final View child = children[i];
- if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
- || child.getAnimation() != null) {
- child.getHitRect(frame);
- if (frame.contains(scrolledXInt, scrolledYInt)) {
- final float xc = scrolledXFloat - child.mLeft;
- final float yc = scrolledYFloat - child.mTop;
- ev.setLocation(xc, yc);
- child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
- if (child.dispatchTouchEvent(ev)) {
- mMotionTarget = child;
- return true;
- }
- }
- }
- }
- }
- }
- boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||
- (action == MotionEvent.ACTION_CANCEL);
- if (isUpOrCancel) {
- mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
- }
- final View target = mMotionTarget;
- if (target == null) {
- ev.setLocation(xf, yf);
- if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
- ev.setAction(MotionEvent.ACTION_CANCEL);
- mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
- }
- return super.dispatchTouchEvent(ev);
- }
- if (!disallowIntercept && onInterceptTouchEvent(ev)) {
- final float xc = scrolledXFloat - (float) target.mLeft;
- final float yc = scrolledYFloat - (float) target.mTop;
- mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
- ev.setAction(MotionEvent.ACTION_CANCEL);
- ev.setLocation(xc, yc);
- if (!target.dispatchTouchEvent(ev)) {
- }
- mMotionTarget = null;
- return true;
- }
- if (isUpOrCancel) {
- mMotionTarget = null;
- }
- final float xc = scrolledXFloat - (float) target.mLeft;
- final float yc = scrolledYFloat - (float) target.mTop;
- ev.setLocation(xc, yc);
- if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
- ev.setAction(MotionEvent.ACTION_CANCEL);
- target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
- mMotionTarget = null;
- }
- return target.dispatchTouchEvent(ev);
- }
onInterceptTouchEvent源码;
- /**
- * Implement this method to intercept all touch screen motion events. This
- * allows you to watch events as they are dispatched to your children, and
- * take ownership of the current gesture at any point.
- *
- * <p>Using this function takes some care, as it has a fairly complicated
- * interaction with {@link View#onTouchEvent(MotionEvent)
- * View.onTouchEvent(MotionEvent)}, and using it requires implementing
- * that method as well as this one in the correct way. Events will be
- * received in the following order:
- *
- * <ol>
- * <li> You will receive the down event here.
- * <li> The down event will be handled either by a child of this view
- * group, or given to your own onTouchEvent() method to handle; this means
- * you should implement onTouchEvent() to return true, so you will
- * continue to see the rest of the gesture (instead of looking for
- * a parent view to handle it). Also, by returning true from
- * onTouchEvent(), you will not receive any following
- * events in onInterceptTouchEvent() and all touch processing must
- * happen in onTouchEvent() like normal.
- * <li> For as long as you return false from this function, each following
- * event (up to and including the final up) will be delivered first here
- * and then to the target's onTouchEvent().
- * <li> If you return true from here, you will not receive any
- * following events: the target view will receive the same event but
- * with the action {@link MotionEvent#ACTION_CANCEL}, and all further
- * events will be delivered to your onTouchEvent() method and no longer
- * appear here.
- * </ol>
- *
- * @param ev The motion event being dispatched down the hierarchy.
- * @return Return true to steal motion events from the children and have
- * them dispatched to this ViewGroup through onTouchEvent().
- * The current target will receive an ACTION_CANCEL event, and no further
- * messages will be delivered here.
- */
- public boolean onInterceptTouchEvent(MotionEvent ev) {
- return false;
- }
源码解析,郭霖大牛的解析太好了,我就直接mark了:
- 这个方法代码比较长,我们只挑重点看。首先在第13行可以看到一个条件判断,如果disallowIntercept和!onInterceptTouchEvent(ev)两者有一个为true,就会进入到这个条件判断中。disallowIntercept是指是否禁用掉事件拦截的功能,默认是false,也可以通过调用requestDisallowInterceptTouchEvent方法对这个值进行修改。那么当第一个值为false的时候就会完全依赖第二个值来决定是否可以进入到条件判断的内部,第二个值是什么呢?竟然就是对onInterceptTouchEvent方法的返回值取反!也就是说如果我们在onInterceptTouchEvent方法中返回false,就会让第二个值为true,从而进入到条件判断的内部,如果我们在onInterceptTouchEvent方法中返回true,就会让第二个值为false,从而跳出了这个条件判断。
- 那我们重点来看下条件判断的内部是怎么实现的。在第19行通过一个for循环,遍历了当前ViewGroup下的所有子View,然后在第24行判断当前遍历的View是不是正在点击的View,如果是的话就会进入到该条件判断的内部,然后在第29行调用了该View的dispatchTouchEvent,之后的流程就和view的事件分发是一样的咯
总结一下:
1.默认的onInterceptTouchEvent总是返回false的,就是不拦截touch事件,直接分发给了子控件。所以假如我们自定义了组合控件,譬如image+文字的组合控件,并且在activity里面注册监听期待点击它的时候会产生响应,那么我们就需要重写onInterceptTouchEvent了让它返回true,将事件拦截下来。
2.如果触摸的时候,我们只想出发ontouch监听,想屏蔽onclick监听的话,就需要在ontouch里面返回true就可以了
3.android事件分发是先传递到viewgroup,然后才传递到view的
4.子view如果将传递的事件消费处理掉,viewgroup当中是接收不到任何事件的
5.简单来讲,dispatchTouchEvent方法是为了onTouch监听的,onTouchEvent是为了onClick监听的。如果ontouch监听返回false,事件会传递到onTouchEvent当中触发onClick,如果是true的话就不会继续往下传递了。