Android 触摸事件 点击事件的分发机制 详解

最近发现团队里有些员工在做一些自定义控件的时候感觉比较吃力。尤其是做触摸事件这种东西的时候。很多人对机制并不理解。因为百度出来的东西都太理论化了。确实不好理解。

今天带大家坐几个小demo。帮助理解一下。

先从简单的view 的事件分发机制开始解释。

我们首先自定义一个工程

package com.example.testtouch;

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;
import android.widget.ImageView;
import android.widget.TextView;

public class MainActivity extends Activity {

    private TextView tv;
    private ImageView iv;

    @Override
    protected void onCreate(Bundle savedsInstanceState) {
        super.onCreate(savedsInstanceState);
        setContentView(R.layout.activity_main);
        tv = (TextView) this.findViewById(R.id.tv);
        iv = (ImageView) this.findViewById(R.id.iv);
        iv.setOnTouchListener(new OnTouchListener() {

            @Override
            public boolean onTouch(View v, MotionEvent event) {
                if (event.getAction() == MotionEvent.ACTION_DOWN) {
                    Log.v("test", "iv event down ==" + MotionEvent.ACTION_DOWN);
                }
                if (event.getAction() == MotionEvent.ACTION_UP) {
                    Log.v("test", "iv event up ==" + MotionEvent.ACTION_UP);
                }
                return false;
            }
        });
        tv.setOnTouchListener(new OnTouchListener() {

            @Override
            public boolean onTouch(View v, MotionEvent event) {
                // TODO Auto-generated method stub
                if (event.getAction() == MotionEvent.ACTION_DOWN) {
                    Log.v("test", "tv event down ==" + MotionEvent.ACTION_DOWN);
                }
                if (event.getAction() == MotionEvent.ACTION_UP) {
                    Log.v("test", "tv event up ==" + MotionEvent.ACTION_UP);
                }
                return false;
            }
        });

        tv.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub
                Log.v("test", "on click");
            }
        });
    }
}

然后运行起来以后 点击一下那个textview。

输出日志如下:

10-31 02:54:40.456: V/test(1430): tv event down ==0
10-31 02:54:40.523: V/test(1430): tv event up ==1
10-31 02:54:40.526: V/test(1430): on click

可以看出来 ontouch事件是在onclick事件以前被调用的。

如果我们把 ontouch事件的返回值 更改为true的话。那么就会发现 onclick 事件就不会执行了。

可以理解成 如果ontouch事件 返回的值为true的话 那么剩下的触摸操作全部都被拦截了。

换句话说 只要这个方法返回值 为true 那么剩下的操作就全部都没有了。

我们可以看源代码 去验证一下。首先我们得知道一个前提。 那就是view的事件分发都是由 dispatchTouchEvent

这个方法来控制的,这个方法 在view的类里面定义 我们去看一下源代码。

/**
     * Pass the touch screen motion event down to the target view, or this
     * view if it is the target.
     *
     * @param event The motion event to be dispatched.
     * @return True if the event was handled by the view, false otherwise.
     */
    public boolean dispatchTouchEvent(MotionEvent event) {
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(event, 0);
        }

        if (onFilterTouchEventForSecurity(event)) {
            //noinspection SimplifiableIfStatement
            if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
                    mOnTouchListener.onTouch(this, event)) {
                return true;
            }

            if (onTouchEvent(event)) {
                return true;
            }
        }

        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
        }
        return false;
    }

主要看这个部分

if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
mOnTouchListener.onTouch(this, event)) {
return true;
}

if (onTouchEvent(event)) {
return true;
}

如果mOnTouchListener  这个不为空 并且 (mViewFlags & ENABLED_MASK) == ENABLED(代表这个控件可以被点击)

并且mOnTouchListener.onTouch(this, event) 这个也返回true  那么dispatchTouchEvent就返回true了。

否则就执行 onTouchEvent(event) 这个方法 并且返回true。

分析完这个 就比较好理解了,这边是先调用的OnTouchListener.onTouch(this, event)  这个事件,所以我们能看到log输出 ontouch事件

是在onclick时间之前的。并且这个方法 如果返回false  才会走到onTouchEvent 这个事件里面去。如果返回true 那么dispatchTouchEvent

这个函数就直接返回了。onTouchEvent  就永远不会得到执行。这也就解释了为什么我们如果把onTouch事件的值设置成true ,onClick方法

就不会得到执行。

当然也可以看出来 oncLIClick事件是在onTouchEvent里执行的。哪我们继续看源代码onTouchEvent 是怎么做的。

  1 /**
  2      * Implement this method to handle touch screen motion events.
  3      *
  4      * @param event The motion event.
  5      * @return True if the event was handled, false otherwise.
  6      */
  7     public boolean onTouchEvent(MotionEvent event) {
  8         final int viewFlags = mViewFlags;
  9
 10         if ((viewFlags & ENABLED_MASK) == DISABLED) {
 11             if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PRESSED) != 0) {
 12                 mPrivateFlags &= ~PRESSED;
 13                 refreshDrawableState();
 14             }
 15             // A disabled view that is clickable still consumes the touch
 16             // events, it just doesn‘t respond to them.
 17             return (((viewFlags & CLICKABLE) == CLICKABLE ||
 18                     (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
 19         }
 20
 21         if (mTouchDelegate != null) {
 22             if (mTouchDelegate.onTouchEvent(event)) {
 23                 return true;
 24             }
 25         }
 26
 27         if (((viewFlags & CLICKABLE) == CLICKABLE ||
 28                 (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
 29             switch (event.getAction()) {
 30                 case MotionEvent.ACTION_UP:
 31                     boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;
 32                     if ((mPrivateFlags & PRESSED) != 0 || prepressed) {
 33                         // take focus if we don‘t have it already and we should in
 34                         // touch mode.
 35                         boolean focusTaken = false;
 36                         if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
 37                             focusTaken = requestFocus();
 38                         }
 39
 40                         if (prepressed) {
 41                             // The button is being released before we actually
 42                             // showed it as pressed.  Make it show the pressed
 43                             // state now (before scheduling the click) to ensure
 44                             // the user sees it.
 45                             mPrivateFlags |= PRESSED;
 46                             refreshDrawableState();
 47                        }
 48
 49                         if (!mHasPerformedLongPress) {
 50                             // This is a tap, so remove the longpress check
 51                             removeLongPressCallback();
 52
 53                             // Only perform take click actions if we were in the pressed state
 54                             if (!focusTaken) {
 55                                 // Use a Runnable and post this rather than calling
 56                                 // performClick directly. This lets other visual state
 57                                 // of the view update before click actions start.
 58                                 if (mPerformClick == null) {
 59                                     mPerformClick = new PerformClick();
 60                                 }
 61                                 if (!post(mPerformClick)) {
 62                                     performClick();
 63                                 }
 64                             }
 65                         }
 66
 67                         if (mUnsetPressedState == null) {
 68                             mUnsetPressedState = new UnsetPressedState();
 69                         }
 70
 71                         if (prepressed) {
 72                             postDelayed(mUnsetPressedState,
 73                                     ViewConfiguration.getPressedStateDuration());
 74                         } else if (!post(mUnsetPressedState)) {
 75                             // If the post failed, unpress right now
 76                             mUnsetPressedState.run();
 77                         }
 78                         removeTapCallback();
 79                     }
 80                     break;
 81
 82                 case MotionEvent.ACTION_DOWN:
 83                     mHasPerformedLongPress = false;
 84
 85                     if (performButtonActionOnTouchDown(event)) {
 86                         break;
 87                     }
 88
 89                     // Walk up the hierarchy to determine if we‘re inside a scrolling container.
 90                     boolean isInScrollingContainer = isInScrollingContainer();
 91
 92                     // For views inside a scrolling container, delay the pressed feedback for
 93                     // a short period in case this is a scroll.
 94                     if (isInScrollingContainer) {
 95                         mPrivateFlags |= PREPRESSED;
 96                         if (mPendingCheckForTap == null) {
 97                             mPendingCheckForTap = new CheckForTap();
 98                         }
 99                         postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
100                     } else {
101                         // Not inside a scrolling container, so show the feedback right away
102                         mPrivateFlags |= PRESSED;
103                         refreshDrawableState();
104                         checkForLongClick(0);
105                     }
106                     break;
107
108                 case MotionEvent.ACTION_CANCEL:
109                     mPrivateFlags &= ~PRESSED;
110                     refreshDrawableState();
111                     removeTapCallback();
112                     break;
113
114                 case MotionEvent.ACTION_MOVE:
115                     final int x = (int) event.getX();
116                     final int y = (int) event.getY();
117
118                     // Be lenient about moving outside of buttons
119                     if (!pointInView(x, y, mTouchSlop)) {
120                         // Outside button
121                         removeTapCallback();
122                         if ((mPrivateFlags & PRESSED) != 0) {
123                             // Remove any future long press/tap checks
124                             removeLongPressCallback();
125
126                             // Need to switch from pressed to not pressed
127                             mPrivateFlags &= ~PRESSED;
128                             refreshDrawableState();
129                         }
130                     }
131                     break;
132             }
133             return true;
134         }
135
136         return false;
137     }

看62行 有一个   performClick() 我们去看看这个方法。

 /**
     * Call this view‘s OnClickListener, if it is defined.
     *
     * @return True there was an assigned OnClickListener that was called, false
     *         otherwise is returned.
     */
    public boolean performClick() {
        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);

        if (mOnClickListener != null) {
            playSoundEffect(SoundEffectConstants.CLICK);
            mOnClickListener.onClick(this);
            return true;
        }

        return false;
    }

看到没,我们的点击事件回调就是在这里做的!

回过头来 我们再看看 onTouchEvent 那个switch语句,我们会发现 最终 他们的返回值 都是true!!! 无论是什么事件 都是return true。

然后回过头来 看  dispatchTouchEvent 这个方法。

前面两行代码。

if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}

大家都知道 触摸事件是由dispatchTouchEvent  这个函数去进行分发的。无论是什么触摸事件,都是手指先点下去 也就是 action down事件。action down的值是0.

所以你看 这边的代码意思就是如果action down的返回事件是true 那就分发。如果action down的返回onTouchEvent都是false的话 哪后面的事件就全部拦截了。

可以理解成。对于dispatchTouchEvent 来说 如果ACTION_DOWN事件返回true,就说明它需要处理这个事件,就让它接收所有的触屏事件,否则,说明它不用处理,也就不让它接收后续的触屏事件了。

时间: 2024-10-03 06:13:19

Android 触摸事件 点击事件的分发机制 详解的相关文章

Android事件分发机制详解(1)----探究View的事件分发

探究View的事件分发 在Activity中,只有一个按钮,注册一个点击事件 [java] view plaincopy button.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Log.d("TAG", "onClick execute"); } }); 如果在需要一个触摸事件 [java] view plaincopy button.setO

Android事件分发机制详解(2)----分析ViewGruop的事件分发

首先,我们需要 知道什么是ViewGroup,它和普通的View有什么区别? ViewGroup就是一组View的集合,它包含很多子View和ViewGroup,是Android 所有布局的父类或间接父类. 但ViewGroup也是一个View,只不过比起View,它可以包含子View和定义布局参数的功能. 现在,通过一个Demo演示Android中ViewGroup的事件分发机制. 首先我们来自定义一个布局,命名为MyLayout,继承自LinearLayout,如下 所示: public c

Cocos2d-X研究之v3.x 事件分发机制详解

事件分发机制 新事件分发机制:在2.x 版本事件处理时,将要触发的事件交给代理(delegate)处理,再通过实现代理里面的onTouchBegan等方法接收事件,最后完成事件的响应.而在新的事件分发机制中,只需通过创建一个事件监听器-用来实现各种触发后的逻辑,然后添加到事件分发器_eventDispatcher,所有事件监听器由这个分发器统一管理,即可完成事件响应.请参考更多3.0资料... 事件监听器有以下几种: 触摸事件 (EventListenerTouch) 键盘响应事件 (Event

android 事件分发机制详解(OnTouchListener,OnClick)

昨天做东西做到触摸事件冲突,以前也经常碰到事件冲突,想到要研究一下Android的事件冲突机制,于是从昨天开始到今天整整一天时间都要了解这方面的知识,这才懂了安卓的触摸和点击事件的机制.探究如下: 首先重写三个View布局,用来做测试: package com.example.yzj.android_8_10; import android.content.Context; import android.util.AttributeSet; import android.util.Log; im

Android 触摸事件 点击事件的分发机制 详解三---责任链模式

前面两节  我们讲述了 android 点击事件的分发流程.其实大家可以细细体会一下,这个分发的过程 始终是从顶层到下层.一层一层的按照顺序进行. 当然了,传到哪一层停止,我们可以通过重写某些方法来完成. 这个地方 android的开发人员很好的利用了 责任链模式来完成这边代码的编写. 下面我们就来讲一下 责任链模式到底是什么.以及如何运用. 大家知道 一个软件公司的基本架构就是 程序员----leader---project manager---boss 这种基础架构. 我们一般都会有team

Android 触摸事件 点击事件的分发机制 详解二

现在我们来看看 事件分发的流程.view group 怎么传递给view的. 首先自定义一个layout 1 package com.example.testtouch; 2 3 import android.content.Context; 4 import android.util.AttributeSet; 5 import android.util.Log; 6 import android.view.MotionEvent; 7 import android.widget.Linear

Android事件分发机制详解:史上最全面、最易懂

前言 Android事件分发机制是每个Android开发者必须了解的基础知识 网上有大量关于Android事件分发机制的文章,但存在一些问题:内容不全.思路不清晰.无源码分析.简单问题复杂化等等 今天,我将全面总结Android的事件分发机制,我能保证这是市面上的最全面.最清晰.最易懂的 本文秉着"结论先行.详细分析在后"的原则,即先让大家感性认识,再通过理性分析从而理解问题: 所以,请各位读者先记住结论,再往下继续看分析: 文章较长,阅读需要较长时间,建议收藏等充足时间再进行阅读 目

Android事件分发机制详解

我们通过一个示例来分析Touch事件的分发过程. 示例: 布局文件: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id=&

Android 事件分发机制详解

更多内容请参照我的个人站点: http://stackvoid.com/ 网上很多关于Android事件分发机制的解释,大多数描述的都不够清晰,没有吧来龙去脉搞清楚,本文将带你从Touch事件产生到Touch事件被消费这一全过程作全面的剖析. 产生Touch事件 这部分牵扯到硬件和Linux内核部分:我们简单讲述一下这部分内容,如果有兴趣的话可以参考这篇文章. 传递Touch事件 触摸事件是由Linux内核的一个Input子系统来管理的(InputManager),Linux子系统会在/dev/