示例
自定义一个MyCustomView
public class MyCustomView extends View { private String TAG = "MyButton"; public MyCustomView(Context context) { super(context); } public MyCustomView(Context context, AttributeSet attrs) { super(context, attrs); } public MyCustomView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override public boolean dispatchTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: Log.e(TAG, "dispatchTouchEvent----->>ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.e(TAG, "dispatchTouchEvent----->>ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.e(TAG, "dispatchTouchEvent----->>ACTION_UP"); break; } return super.dispatchTouchEvent(event); } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: Log.e(TAG, "onTouchEvent----->>ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.e(TAG, "onTouchEvent----->>ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.e(TAG, "onTouchEvent----->>ACTION_UP"); break; } return super.onTouchEvent(event); } }
重写dispatchTouchEvent和onTouchEvent方法,添加三种触摸事件的打印日志。
MainActivity调用如下:
public class MainActivity extends Activity { private MyCustomView button; private String TAG = "MainActivity"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); button = (MyCustomView) findViewById(R.id.button); Log.e(TAG, "the view is clickable " + button.isClickable()); button.setClickable(true); button.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: Log.e(TAG, "onTouch------->>ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.e(TAG, "onTouch------->>ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.e(TAG, "onTouch------->>ACTION_UP"); break; } return false; } }); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Log.e(TAG, "onClick------->>onClick"); } }); } }
点击自定义MyCustomView,结果打印如下:
07-29 11:03:51.714 20278-20278/com.xjp.toucheventdemo E/MainActivity﹕ the view is clickable false 07-29 11:03:54.877 20278-20278/com.xjp.toucheventdemo E/MyButton﹕ dispatchTouchEvent----->>ACTION_DOWN 07-29 11:03:54.878 20278-20278/com.xjp.toucheventdemo E/MainActivity﹕ onTouch------->>ACTION_DOWN 07-29 11:03:54.878 20278-20278/com.xjp.toucheventdemo E/MyButton﹕ onTouchEvent----->>ACTION_DOWN 07-29 11:03:54.929 20278-20278/com.xjp.toucheventdemo E/MyButton﹕ dispatchTouchEvent----->>ACTION_MOVE 07-29 11:03:54.929 20278-20278/com.xjp.toucheventdemo E/MainActivity﹕ onTouch------->>ACTION_MOVE 07-29 11:03:54.929 20278-20278/com.xjp.toucheventdemo E/MyButton﹕ onTouchEvent----->>ACTION_MOVE 07-29 11:03:54.929 20278-20278/com.xjp.toucheventdemo E/MyButton﹕ dispatchTouchEvent----->>ACTION_UP 07-29 11:03:54.931 20278-20278/com.xjp.toucheventdemo E/MainActivity﹕ onTouch------->>ACTION_UP 07-29 11:03:54.931 20278-20278/com.xjp.toucheventdemo E/MyButton﹕ onTouchEvent----->>ACTION_UP 07-29 11:03:54.936 20278-20278/com.xjp.toucheventdemo E/MainActivity﹕ onClick------->>onClick
分析:有上面的打印可以看出
- 触摸事件主要有三种且执行顺序为:ACTION_DOWN,ACTION_MOVE,ACTION_UP。也就是先执行ACTION_DOWN按下的行为,按下之后手指可能会移动,移动时就出发了ACTION_MOVE行为,当手指抬起时,触发了ACTION_UP行为,至此触摸事件顺序执行结束。当然触摸事件不止这三种行为,但是我们这里主要分析这三种。
- 触摸事件过程执行的方法顺序为:dispatchTouchEvent,onTouch,onTouchEvent。最后执行了onClick点击事件。也就是顺序应该为:dispatchTouchEvent–>>onTouch–>>onTouchEvent–>>onClick
- onClick点击事件是在触摸事件ACTION_UP执行完之后才执行。
为什么会有以上三种现象和情况出现?现在我们只能从打印日志中看到结果,但是并不知道其内部原因,为了窥探其内部原因,read the fuck code 。基于Android2.0源码分析View。View的触摸事件分发是从View#dispatchTouchEvent方法开始执行的。至于为什么从这里,以后再说。
View#dispatchTouchEvent触摸事件分发
public boolean dispatchTouchEvent(MotionEvent event) { if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && mOnTouchListener.onTouch(this, event)) { return true; } return onTouchEvent(event); }
分析:方法实现很简单,当满足if条件就返回true退出方法,条件不满足时,才去执行onTouchEvent方法且返回该方法的返回值。
1.那么什么情况下满足mOnTouchListener != null条件呢?查看View源码发现调用如下方法时:
public void setOnTouchListener(OnTouchListener l) { mOnTouchListener = l; }
当开发者给相应的View设置了View#setOnTouchListener触摸事件之后,mOnTouchListener != null条件就成立。
2.View默认都是enabled状态,所以第二个条件成立。
3.当前两个条件都成立了,执行第三个条件接口方法mOnTouchListener.onTouch(this, event)。根据该方法的返回值来决定if条件是否成立。该方法在开发者设置View#setOnTouchListener触摸事件实现,当onTouch方法返回false时,dispatchTouchEvent方法就会执行onTouchEvent方法,否则不执行onTouchEvent方法。
总结:
1.onTouch接口方法的返回值决定是否执行onTouchEvent方法。
2.只要onTouch接口方法返回值为true,dispatchTouchEvent方法一定返回true,否则根据onTouchEvent方法返回值决定dispatchTouchEvent返回值。
View#onTouchEvent
进入onTouchEvent方法:
public boolean onTouchEvent(MotionEvent event) { final int viewFlags = mViewFlags; ................ if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) { switch (event.getAction()) { case MotionEvent.ACTION_UP: if ((mPrivateFlags & PRESSED) != 0) { // 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 if (mPendingCheckForLongPress != null) { removeCallbacks(mPendingCheckForLongPress); } // Only perform take click actions if we were in the pressed state if (!focusTaken) { performClick(); } } if (mUnsetPressedState == null) { mUnsetPressedState = new UnsetPressedState(); } if (!post(mUnsetPressedState)) { // If the post failed, unpress right now mUnsetPressedState.run(); } } break; case MotionEvent.ACTION_DOWN: mPrivateFlags |= PRESSED; refreshDrawableState(); if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) { postCheckForLongClick(); } break; case MotionEvent.ACTION_CANCEL: mPrivateFlags &= ~PRESSED; refreshDrawableState(); 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 = ViewConfiguration.get(mContext).getScaledTouchSlop(); if ((x < 0 - slop) || (x >= getWidth() + slop) || (y < 0 - slop) || (y >= getHeight() + slop)) { // Outside button if ((mPrivateFlags & PRESSED) != 0) { // Remove any future long press checks if (mPendingCheckForLongPress != null) { removeCallbacks(mPendingCheckForLongPress); } // Need to switch from pressed to not pressed mPrivateFlags &= ~PRESSED; refreshDrawableState(); } } else { // Inside button if ((mPrivateFlags & PRESSED) == 0) { // Need to switch from not pressed to pressed mPrivateFlags |= PRESSED; refreshDrawableState(); } } break; } return true; } return false; }
分析:代码有点长,首先进入该方法判断if条件是否成立?如果该View是可点击的或者是可以长按点击,则if条件成立,进入if判断,执行ACTION_UP分支。
1.代码第26行,调用了performClick方法来执行View的点击事件
public boolean performClick() { sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); if (mOnClickListener != null) { playSoundEffect(SoundEffectConstants.CLICK); mOnClickListener.onClick(this); return true; } return false; }
该方法判断如果mOnClickListener!=null条件成立,则执行mOnClickListener.onClick(this);接口方法。什么时候条件成立呢?当给当前View设置了点击监听事件之后,条件成立,因此调用接口onClick方法。在View类中有如下方法:
public void setOnClickListener(OnClickListener l) { if (!isClickable()) { setClickable(true); } mOnClickListener = l; }
先判断当前View是否可点击的状态?如果不可点击的话,先设置成可点击,之后对mOnClickListener赋值操作。总结:只要给任何一个View设置了setOnClickListener点击监听事件,不管这个View是否是可点击的状态,最后都设置为了可点击的状态了。
2.只有当前View是可点击或者长按的状态,才进入if条件判断,然后执行相应的手势操作,最后返回true。也就是说,只要View是可点击的,onTouchEvent方法返回的就是true,从而dispatchTouchEvent方法返回的也是true。
3.只要是当前View是不可点击或者长按的状态,if条件不成立,不执行任何操作,直接返回false。也就是说,View不可点击的时候,onTouchEvent方法返回的就是false,从而dispatchTouchEvent方法返回的也是false。
4.onClick方法是在ACTION_UP手势里面执行的,也就是当手势抬起时才去执行onClick方法。
到此,Android View触摸事件传递已经分析结束。如果条件都满足,则整个触摸事件传递过程就是:dispatchTouchEvent–>>onTouch–>>onTouchEvent–>>onClick。现在我们来验证一下如下几种情况:
onTouch方法返回值为true
改成如下
button.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: Log.e(TAG, "onTouch------->>ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.e(TAG, "onTouch------->>ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.e(TAG, "onTouch------->>ACTION_UP"); break; } return true; } });
此时点击View的打印如下:
07-29 14:42:22.969 2964-2964/com.xjp.toucheventdemo E/MyButton﹕ dispatchTouchEvent----->>ACTION_DOWN 07-29 14:42:22.970 2964-2964/com.xjp.toucheventdemo E/MainActivity﹕ onTouch------->>ACTION_DOWN 07-29 14:42:22.987 2964-2964/com.xjp.toucheventdemo E/MyButton﹕ dispatchTouchEvent----->>ACTION_MOVE 07-29 14:42:22.987 2964-2964/com.xjp.toucheventdemo E/MainActivity﹕ onTouch------->>ACTION_MOVE 07-29 14:42:22.987 2964-2964/com.xjp.toucheventdemo E/MyButton﹕ dispatchTouchEvent----->>ACTION_UP 07-29 14:42:22.988 2964-2964/com.xjp.toucheventdemo E/MainActivity﹕ onTouch------->>ACTION_UP
当onTouch方法返回true时,就不执行onTouchEvnet方法,因此也就不执行onClick点击事件。可以理解成此时onTouch把触摸事件已经消费掉了,也就不会继续往下传递触摸事件。所以如果你不想自己的View执行onTouchEvent方法,你可以设置onTouch事件,且返回值为true即可。
View不可点击情况
在文章开头的 MainActivity里面,我是添加了一行代码 button.setClickable(true); 目的是让当前View可点击,但是默认情况下除了Button,TextView少数控件外,其他大部分View控件默认都是不可点击的状态,除非你设置了View#setClickable(true)或者View#setOnClickListener。现在我将MainActivity中的button.setClickable(true);这一行代码去掉且不设置setOnClickListener事件
public class MainActivity extends Activity { private MyCustomView button; private String TAG = "MainActivity"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); button = (MyCustomView) findViewById(R.id.button); Log.e(TAG, "the view is clickable " + button.isClickable()); // button.setClickable(true); button.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: Log.e(TAG, "onTouch------->>ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.e(TAG, "onTouch------->>ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.e(TAG, "onTouch------->>ACTION_UP"); break; } return false; } }); // button.setOnClickListener(new View.OnClickListener() { // @Override // public void onClick(View v) { // Log.e(TAG, "onClick------->>onClick"); // } // }); } }
Log打印日志如下:
07-29 14:57:03.656 4896-4896/com.xjp.toucheventdemo E/MyButton﹕ dispatchTouchEvent----->>ACTION_DOWN 07-29 14:57:03.658 4896-4896/com.xjp.toucheventdemo E/MainActivity﹕ onTouch------->>ACTION_DOWN 07-29 14:57:03.658 4896-4896/com.xjp.toucheventdemo E/MyButton﹕ onTouchEvent----->>ACTION_DOWN
不知道你发现木有?此处打印看出只执行了ACTION_DOWN手指操作,其他的手势操作呢?没有执行,为什么呢?
情况是这样的:当onTouch方法返回false,则dispatchTouchEvent方法就会执行onTouchEvent方法,但是由于View不可点击,所以onTouchEvent是不执行if条件体的,也就是onTouchEvent方法返回false,从而导致dispatchTouchEvent方法返回false,由于dispatchTouchEvent方法返回false,导致后面的手势操作ACTION_MOVE,ACTION_UP得不到执行。
总结:如果我们将手势操作分为三个过程的话:ACTION_DOWN,ACTION_MOVE,ACTION_UP。只有当dispatchTouchEvent方法返回true时,系统才会执行对应过程后面的手势操作。
总结
至此Android View 触摸事件传递机制已经分析结束,现在用一个流程图来体现:
- 触摸事件传递顺序:dispatchTouchEvent–>>onTouch–>>onTouchEvent–>>onClick。
- onTouch和onTouchEvent区别:两个方法先后在dispatchTouchEvent中调用,只有给View设置了触摸事件View#setOnTouchListener才会执行onTouch方法;onTouch方法的返回值决定是否执行onTouchEvent方法。
- 手势操作执行的顺序为ACTION_DOWN,ACTION_MOVE,ACTION_UP,只有dispatchTouchEvent方法返回true值时后面的手势才会被执行。
- onClick方法的调用是在onTouchEvent的ACTION_UP手势里面执行的,也就是当手势抬起时,手势操作结束才会触发onClick方法的调用。
相关推荐
以后会按标准一个个的写完测试DEMO,一是自我学习,二是方便同样在学习的其他同学这是,本系列代码均来自《Android高级进阶》,经过测试可以运行,这是 第一章 Android 触摸事件传递机制 View的事件传递机制
在Android系统中,触摸事件是用户与应用交互的重要方式,理解其事件传递机制对于优化UI交互和处理复杂触摸逻辑至关重要。本文将深入解析Android的Touch事件传递机制,并结合源码进行详细阐述。 首先,Android的触摸...
在Android系统中,触摸事件的处理是通过一个复杂的分发机制来实现的,这个机制确保了用户在屏幕上点击或滑动时,相应的视图能够正确地接收到并响应这些动作。下面将详细介绍Android中触摸事件的分发机制,以及如何在...
在Android系统中,触摸事件(Touch Events)是用户与设备交互的主要方式,它涉及到了复杂的事件传递机制。本文将深入探讨Android的触摸事件处理流程,包括事件的产生、分发以及处理过程,以实例的形式帮助开发者理解...
当用户触摸屏幕时,系统会产生一系列的MotionEvent对象,并通过事件传递机制将其分发给相应的View。 事件传递机制遵循“父子组件”模型,可以分为三个主要阶段:捕获阶段、目标阶段和消耗阶段。 1. 捕获阶段:事件...
在Android开发中,View事件传递机制是理解和优化用户交互界面不可或缺的部分。本Demo源码着重展示了这一机制,旨在帮助开发者深入理解并应用到实际项目中。以下是对这一主题的详细阐述。 首先,我们要知道Android中...
在Android开发中,触摸事件是用户与应用交互的基础,它涉及到`MotionEvent`类和触摸事件的处理机制。本教程将深入探讨`android_触摸事件Demo`,帮助开发者理解如何在Android应用程序中处理用户的触摸输入。 一、...
在Android中,事件(如点击、触摸)会沿着View层次结构进行传递,这个过程分为两个阶段:Down事件分发和Up事件分发。 1. 下降阶段(Down事件分发): - `dispatchTouchEvent()`:事件首先由Activity接收,然后按照...
在Android系统中,事件传递机制是用户界面交互的基础,它涉及到Activity、View、 ViewGroup等组件之间的协同工作。本文将深入探讨这一机制,帮助你理解如何在Android应用中处理触摸事件。 1. 触摸事件的起源 触摸...
在Android开发中,事件传递机制是用户界面交互的核心部分,它决定了用户在屏幕上触摸或点击时,事件如何在各个View之间进行分发。本Demo,"事件传递机制Demo",着重展示了这一机制的工作原理和常见应用场景。我们...
Android 触摸事件传递机制 Android 触摸事件传递机制是 Android 开发中一个非常重要的机制,它决定了如何处理触摸事件,并且影响着应用程序的用户体验。下面将详细介绍 Android 触摸事件传递机制的三个阶段:分发、...
Android 触摸事件传递机制是 Android 系统中的一项核心机制,它决定了事件如何从 Activity 传递到 View,然后再传递到 ViewGroup 中。理解这个机制是非常重要的,因为它关系到应用程序的交互性和用户体验。 首先,...
在Android开发中,触摸事件处理是用户界面交互的关键部分,`onTouch`事件是其中的核心机制。本篇文章将深入探讨Android的`onTouch`事件传递机制,帮助开发者更好地理解和运用这一功能。 首先,Android的触摸事件...
事件分发是Android UI交互的核心机制,它决定了触摸事件如何在View层次结构中传递。本文将深入探讨自定义View的事件分发机制。 事件分发在Android中分为三个主要步骤:`dispatchTouchEvent()`, `...
Android事件分发机制主要包括三个步骤: DispatchTouchEvent(事件传递)、onInterceptTouchEvent(拦截事件)和onTouchEvent(处理事件)。这个机制确保了触摸事件能够正确地在View树中进行传播和处理。 1. **...
本篇文章将深入探讨Android中的View事件传递机制、事件消费以及触摸事件和点击事件的区别。 首先,让我们来了解Android的事件传递机制。在Android中,事件(如触摸事件)是从根View开始,沿着View树向下传递的。这...
本篇将深入解析Android的TouchEvent事件传递机制,帮助开发者更好地理解和利用这一机制。 首先,当用户触摸屏幕时,硬件层会生成一个MotionEvent,该事件包含了触摸事件的类型(ACTION_DOWN、ACTION_UP、ACTION_...
在Android开发过程中,理解事件传递机制对于实现高效的用户交互界面至关重要。本篇文章基于一个关于“事件传递机制和原理”的流程图进行深入解析,旨在帮助开发者更好地掌握事件传递的核心概念与流程。 #### 一、...