本篇文章主要介绍了谈谈对 Android View 事件分发机制的理解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
Android 是一种基于 Linux 的自由及开放源代码的操作系统,主要使用于移动设备,如智能手机和平板电脑,由 Google 公司和开放手机联盟领导及开发。尚未有统一中文名称,中国大陆地区较多人使用 "安卓" 或 "安致"。
最近因为项目中用到类似一个 LinearLayout 中水平布局中,有一个 TextView 和 Button,然后对该 LinearLayout 布局设置点击事件,点击 TextView 能够触发该点击事件,然而奇怪的是点击 Button 却不能触发。然后 google 到了解决办法(重写 Button, 然后重写其中的 ontouchEvent 方法,且返回值为 false),但是不知道原因,这两天看了几位大神的博客,然后自己总结下。
- public class MyButton extends Button {
- private final static String TAG = "MyButton::zjt";
- public MyButton(Context context, AttributeSet attrs) {
- super(context, attrs);
- // TODO Auto-generated constructor stub
- }
- public MyButton(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- // TODO Auto-generated constructor stub
- }
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- // TODO Auto-generated method stub
- 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;
- default:
- break;
- }
- //return super.onTouchEvent(event);
- return false;
- }
- @Override
- public boolean dispatchTouchEvent(MotionEvent event) {
- // TODO Auto-generated method stub
- 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;
- default:
- break;
- }
- return super.dispatchTouchEvent(event);
- }
- }
MyTextView.Java
- public class MyTextView extends TextView {
- private final static String TAG = "MyTextView : ";
- public MyTextView(Context context, AttributeSet attrs) {
- super(context, attrs);
- // TODO Auto-generated constructor stub
- }
- @Override
- public boolean dispatchTouchEvent(MotionEvent event) {
- // TODO Auto-generated method stub
- 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;
- default:
- break;
- }
- return super.dispatchTouchEvent(event);
- }
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- // TODO Auto-generated method stub
- switch (event.getAction()) {
- case MotionEvent.ACTION_DOWN:
- Log.e(TAG, "onTouchEvent ACTION_DOWN");
- ////return true 后面的ACTION_MOVE、和ACTION_UP能够得以执行,如果不做任何操作,即 break,由于textview默认是不可点击和长点击的,所以return false,
- //那么 dispatcTouchEvent 会 return false,导致后面的ACTION_MOVE 和 ACTION_UP不能执行
- //return true;
- break;
- case MotionEvent.ACTION_MOVE:
- Log.e(TAG, "onTouchEvent ACTION_MOVE");
- break;
- case MotionEvent.ACTION_UP:
- Log.e(TAG, "onTouchEvent ACTION_UP");
- break;
- default:
- break;
- }
- return super.onTouchEvent(event);
- }
- }
MainActivity 如下:
- public class TestTouchActivity extends Activity {
- private final static String TAG = "TestTouchActivity";
- private Button mButton;
- private TextView mTextView;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- // TODO Auto-generated method stub
- super.onCreate(savedInstanceState);
- setContentView(R.layout.my_button_layout);
- mButton = (Button) findViewById(R.id.my_btn);
- mTextView = (TextView) findViewById(R.id.my_textview);
- // mTextView.setOnClickListener(new OnClickListener() {
- //
- // @Override
- // public void onClick(View v) {
- // // TODO Auto-generated method stub
- // Log.e(TAG, "mTextView onClick");
- // }
- // });
- mButton.setOnTouchListener(new OnTouchListener() {
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- // TODO Auto-generated method stub
- int action = event.getAction();
- switch (action) {
- case MotionEvent.ACTION_DOWN:
- Log.e(TAG, "onTouch ACTION_DOWN");
- return true;
- //break;
- case MotionEvent.ACTION_MOVE:
- Log.e(TAG, "onTouch ACTION_MOVE");
- break;
- case MotionEvent.ACTION_UP:
- Log.e(TAG, "onTouch ACTION_UP");
- break;
- default:
- break;
- }
- return false;
- }
- });
- mTextView.setOnTouchListener(new OnTouchListener() {
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- // TODO Auto-generated method stub
- int action = event.getAction();
- switch (action) {
- case MotionEvent.ACTION_DOWN:
- Log.e(TAG, "mTextView onTouch ACTION_DOWN");
- break;
- case MotionEvent.ACTION_MOVE:
- Log.e(TAG, "mTextView onTouch ACTION_MOVE");
- break;
- case MotionEvent.ACTION_UP:
- Log.e(TAG, "mTextView onTouch ACTION_UP");
- break;
- default:
- break;
- }
- return false;
- }
- });
- }
- }
点击 Button 和 TextView 的节目如下:
点击 TextView:
为什么结果是这样的,参考博文已经写得很精彩了,我就站在巨人的肩膀上,总结下,我们从上面的结果可以看出,当我们点击屏幕上的 View 的时候首先触发的是 View 的 dispatchTouchEvent 事件。源码如下:
- /**
- * 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 (mOnTouchListener != null && ( mViewFlags & ENABLED_MASK) == ENABLED &&
- mOnTouchListener.onTouch( this, event)) {
- return true;
- }
- return onTouchEvent(event);
- }
上面的 mOnTouchListener 就是我们在 Activity 中设置的 Touch 事件,我们设置的时候在 onTouch 中返回的是 false,所以会接着执行下面的 onTouchEvent 方法,可以看出 onTouchEvent 的返回值就是 dispatchTouchEvent 的返回值。onTouchEvent 这个方法源码比较长,我截断了。
- public boolean onTouchEvent(MotionEvent event) {
- 。。。。。。。。。。。
- 此处有省略
- if (((viewFlags & CLICKABLE) == CLICKABLE ||
- (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
- switch (event.getAction()) {
- 。。。。。。。。。。。
- 此处有省略
- }
- return true;
- }
- return false;
- }
第 4 行就是判断该 View 是否是可点击或者可长按的,如果是返回 true。在 onTouchEvent 中先执行 ACTION_DOWN(手指按下),如果返回 true, 那么 dispatchTouchEvent 的返回值也就是 true,就可以接着执行后面的 ACTION_MOVE 和 ACTION_UP 方法。如果返回 false,那么后面的 ACTION_MOVE 和 ACTION_UP 就不执行了,这个具体原因我还不知道,如果有知道的可以分享下。
说明 1:长按事件是在 onTouchEvent 中的 ACTION_DOWN 中触发的(如果你设置了长按事件),而点击 onclick 事件是在 ACTION_UP 中触发的。
现在分析下前面的例子:
由于 button 默认是可点击的,所以在 onTouchEvent 中会返回 true,所以 dispatchTouchEvent 也会返回 true,后面的 ACTION_MOVE 和 ACTION_UP 可以接着执行。
而 TextView 默认是不可点击的所以 onTouchEvent 中会返回 false,那么 dispatchTouchEvent 也会返回 false,后面的 ACTION_MOVE 和 ACTION_UP 就执行不到了,和上面打印的 log 相符。
如果我们在 Activity 中对 TextView 设置 onTouch 事件返回 true,结果会怎么样呢,我们先就着 dispatchTouchEvent 的源码分析下:
- mTextView.setOnTouchListener(new OnTouchListener() {
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- return true;
- }
- });
由于返回 true,我们从 dispatchTouchEvent 源码的第 10 行可以看出 mOnTouchListener.onTouch(this, event)) 即返回 true,那么 if 条件就成立了,dispatchTouchEvent 直接返回 true,接着执行后面的 ACTION_MOVE 和 ACTION_UP,(ACTION_MOVE 如果你点击的时候滑动了才会执行)。但是后面的 onTouchEvent 就执行不到了。
log 如下:
没有执行 ACTION_MOVE 是因为我快速点击且没有滑动,从 log 可以看出执行完 dispatchTouchEvent 的 ACTION_DOWN 之后又执行了 dispatchTouchEvent 的 ACTION_UP。但并没有执行 onTouchEvent。
下面开始讲主题了,也就是前言交代的问题。下面是我自定义的 ViewGroup:
- public class MyLinearLayout extends LinearLayout {
- private final static String TAG = "MyLinearLayout :";
- public MyLinearLayout(Context context, AttributeSet attrs) {
- super(context, attrs);
- // TODO Auto-generated constructor stub
- }
- @Override
- public boolean dispatchTouchEvent(MotionEvent ev) {
- // TODO Auto-generated method stub
- int action = ev.getAction();
- switch (action) {
- 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;
- default:
- break;
- }
- return super.dispatchTouchEvent(ev);
- }
- @Override
- public boolean onInterceptTouchEvent(MotionEvent ev) {
- // TODO Auto-generated method stub
- int action = ev.getAction();
- switch (action) {
- case MotionEvent.ACTION_DOWN:
- Log.e(TAG, "onInterceptTouchEvent , ACTION_DOWN");
- //return true;
- break;
- case MotionEvent.ACTION_MOVE:
- Log.e(TAG, "onInterceptTouchEvent , ACTION_MOVE");
- //return true;
- break;
- case MotionEvent.ACTION_UP:
- Log.e(TAG, "onInterceptTouchEvent , ACTION_UP");
- break;
- default:
- break;
- }
- return super.onInterceptTouchEvent(ev);
- //return true;
- }
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- // TODO Auto-generated method stub
- int action = event.getAction();
- switch (action) {
- case MotionEvent.ACTION_DOWN:
- Log.e(TAG, "onTouchEvent , ACTION_DOWN");
- //return true;
- break;
- case MotionEvent.ACTION_MOVE:
- Log.e(TAG, "onTouchEvent , ACTION_MOVE");
- break;
- case MotionEvent.ACTION_UP:
- Log.e(TAG, "onTouchEvent , ACTION_UP");
- break;
- default:
- break;
- }
- return super.onTouchEvent(event);
- }
- @Override
- public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
- // TODO Auto-generated method stub
- Log.e(TAG, "enter requestDisallowInterceptTouchEvent");
- super.requestDisallowInterceptTouchEvent(disallowIntercept);
- }
xml 如下:
- <?xml version="1.0" encoding="utf-8"?>
- <com.example.test.view.touch.MyLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical"
- android:id="@+id/id_my_linearlayout"
- >
- <com.example.test.view.touch.MyButton
- android:id="@+id/btn_click"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="click me"
- />
- <com.example.test.view.touch.MyTextView
- android:id="@+id/my_textview_click"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="textview click"
- android:textSize="30sp"
- />
- </com.example.test.view.touch.MyLinearLayout>
MainActivity 如下:
- package com.example.test.view.touch;
- import com.example.drawview.R;
- import android.app.Activity;
- import android.os.Bundle;
- import android.provider.Telephony.Mms;
- 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.Button;
- import android.widget.LinearLayout;
- import android.widget.TextView;
- public class TestViewGroupeTouchActivity extends Activity {
- private final static String TAG = "TestViewGroupeTouchActivity : ";
- private Button mButton ;
- private TextView mTextView;
- private LinearLayout mLinearLayout ;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- // TODO Auto-generated method stub
- super.onCreate(savedInstanceState);
- setContentView(R.layout.my_linear_layout);
- mButton = (Button) findViewById(R.id.btn_click);
- mTextView = (TextView) findViewById(R.id.my_textview_click);
- mLinearLayout = (LinearLayout) findViewById(R.id.id_my_linearlayout);
- mLinearLayout.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- // TODO Auto-generated method stub
- Log.e(TAG, "mLinearLayout , onClick");
- }
- });
- mLinearLayout.setOnTouchListener(new OnTouchListener() {
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- // TODO Auto-generated method stub
- int action = event.getAction();
- switch (action) {
- case MotionEvent.ACTION_DOWN:
- Log.e(TAG, "mLinearLayout , onTouch ACTION_DOWN");
- break;
- case MotionEvent.ACTION_MOVE:
- Log.e(TAG, "mLinearLayout , onTouch ACTION_MOVE");
- break;
- case MotionEvent.ACTION_UP:
- Log.e(TAG, "mLinearLayout ,onTouch ACTION_UP");
- break;
- default:
- break;
- }
- return false;
- }
- });
- mButton.setOnTouchListener(new OnTouchListener() {
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- // TODO Auto-generated method stub
- int action = event.getAction();
- switch (action) {
- case MotionEvent.ACTION_DOWN:
- Log.e(TAG, "mButton onTouch ACTION_DOWN");
- break;
- case MotionEvent.ACTION_MOVE:
- Log.e(TAG, "mButton onTouch ACTION_MOVE");
- break;
- case MotionEvent.ACTION_UP:
- Log.e(TAG, "mButton onTouch ACTION_UP");
- break;
- default:
- break;
- }
- return false;
- }
- });
- mTextView.setOnTouchListener(new OnTouchListener() {
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- // TODO Auto-generated method stub
- switch (event.getAction()) {
- case MotionEvent.ACTION_DOWN:
- Log.e(TAG, "mTextView , onTouch ACTION_DOWN");
- break;
- case MotionEvent.ACTION_MOVE:
- Log.e(TAG, "mTextView , onTouch ACTION_MOVE");
- break;
- case MotionEvent.ACTION_UP:
- Log.e(TAG, "mTextView , onTouch ACTION_UP");
- break;
- default:
- break;
- }
- return false;
- }
- });
- }
- }
说明 2: 由于我是为了说明前言里面的问题,所以 ViewGroup 的 touch 事件分发,我不作过多的说明。ViewGroup 事件分发的流程是:dispatchTouchEvent–>onInterceptTouchEvent—> 然后到手指点击 View 的事件分发(参考上面所说的 View 的事件分发)。
onInterceptTouchEvent 默认返回 false,表示是否拦截事件。ViewGroup 的 dispatchTouchEvent 的源码如下:
- /**
- * {@inheritDoc}
- */
- @Override
- 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;
- //这个值默认是false, 然后我们可以通过requestDisallowInterceptTouchEvent(boolean disallowIntercept)方法
- //来改变disallowIntercept的值
- boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
- //这里是ACTION_DOWN的处理逻辑
- if (action == MotionEvent.ACTION_DOWN) {
- //清除mMotionTarget, 每次ACTION_DOWN都很设置mMotionTarget为null
- if (mMotionTarget != null) {
- mMotionTarget = null;
- }
- //disallowIntercept默认是false, 就看ViewGroup的onInterceptTouchEvent()方法
- 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;
- //遍历其子View
- for (int i = count - 1; i >= 0; i--) {
- final View child = children[i];
- //如果该子View是VISIBLE或者该子View正在执行动画, 表示该View才
- //可以接受到Touch事件
- if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
- || child.getAnimation() != null) {
- //获取子View的位置范围
- child.getHitRect(frame);
- //如Touch到屏幕上的点在该子View上面
- if (frame.contains(scrolledXInt, scrolledYInt)) {
- // offset the event to the view's coordinate system
- final float xc = scrolledXFloat - child.mLeft;
- final float yc = scrolledYFloat - child.mTop;
- ev.setLocation(xc, yc);
- child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
- //调用该子View的dispatchTouchEvent()方法
- if (child.dispatchTouchEvent(ev)) {
- // 如果child.dispatchTouchEvent(ev)返回true表示
- //该事件被消费了,设置mMotionTarget为该子View
- mMotionTarget = child;
- //直接返回true
- return true;
- }
- // The event didn't get handled, try the next view.
- // Don't reset the event's location, it's not
- // necessary here.
- }
- }
- }
- }
- }
- //判断是否为ACTION_UP或者ACTION_CANCEL
- boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||
- (action == MotionEvent.ACTION_CANCEL);
- if (isUpOrCancel) {
- //如果是ACTION_UP或者ACTION_CANCEL, 将disallowIntercept设置为默认的false
- //假如我们调用了requestDisallowInterceptTouchEvent()方法来设置disallowIntercept为true
- //当我们抬起手指或者取消Touch事件的时候要将disallowIntercept重置为false
- //所以说上面的disallowIntercept默认在我们每次ACTION_DOWN的时候都是false
- mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
- }
- // The event wasn't an ACTION_DOWN, dispatch it to our target if
- // we have one.
- final View target = mMotionTarget;
- //mMotionTarget为null意味着没有找到消费Touch事件的View, 所以我们需要调用ViewGroup父类的
- //dispatchTouchEvent()方法,也就是View的dispatchTouchEvent()方法
- if (target == null) {
- // We don't have a target, this means we're handling the
- // event as a regular view.
- 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里面的代码ACTION_DOWN不会执行,只有ACTION_MOVE
- //ACTION_UP才会走到这里, 假如在ACTION_MOVE或者ACTION_UP拦截的
- //Touch事件, 将ACTION_CANCEL派发给target,然后直接返回true
- //表示消费了此Touch事件
- 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)) {
- }
- // clear the target
- mMotionTarget = null;
- // Don't dispatch this event to our own view, because we already
- // saw it when intercepting; we just want to give the following
- // event to the normal onTouchEvent().
- return true;
- }
- if (isUpOrCancel) {
- mMotionTarget = null;
- }
- // finally offset the event to the target's coordinate system and
- // dispatch the event.
- 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;
- }
- //如果没有拦截ACTION_MOVE, ACTION_DOWN的话,直接将Touch事件派发给target
- return target.dispatchTouchEvent(ev);
- }
当我们点击 button 的时候,由于我们 Activity 中重写的 onInterceptTouchEvent 返回值为 super.onInterceptTouchEvent(ev); 即默认的 false,那么 25 行的 if 条件! onInterceptTouchEvent(ev)) 为 true。进入 if 语句里面,遍历所有的子 View,然后执行 51 行的 if (child.dispatchTouchEvent(ev)),上面讲到了 Button 是可点击的,那么 MyButton 的 onTouchEvent 返回值为 true,即 dispatchTouchEvent 返回值为 true。消费了该事件,所有不会触发 mLinearLayout 的点击事件。log 如下:
那么问题来了,为什么将 MyButton 的 onTouchEvent 返回值设为 false,然后点击 Button 就会触发 mLinearLayout 的点击事件呢?
我们来分析下:将 MyButton 的 onTouchEvent 返回值设为 false,那么 51 行的 if (child.dispatchTouchEvent(ev)) 的返回值为 false,为什么呢?
上面分析 View 的 dispatchTouchEvent 源码时分析过了。 返回了 false,那么看 ViewGroup 的源码,81 行, final View target = mMotionTarget; 由于 51 行的 if (child.dispatchTouchEvent(ev)) 返回 false, 所以没有对 mMotionTarget 进行赋值, mMotionTarget == null。
所以走到 85 行:if (target == null) //target = mMotionTarget ,所以该 if 条件成立。
走到 93 行:return super.dispatchTouchEvent(ev);
执行第 9 行的 super.dispatchTouchEvent(ev),viewgroup 的 Super 是 View,即执行 View 的 dispatchTouchEvent 方法。由于我们在 Activity 中 47 行设置了 ontouch 事件,所以先执行 Activity 中 mLinearLayout.setOnTouchListener 中的 onTouch, onTouch 返回 false , 接着执行 MyLinearLayout 中的 onTouchEvent。
说明:
本来由于 MyLinearLayout 是继承自 LinearLayout,默认和 textview 一样是没有点击(clickable)或长按 (longclickable) 的能力的。但是,我们在 Activity 的 38 行对他设置了点击事件,mLinearLayout.setOnClickListener,所以 MyLinearLayout 获得 点击的能力。
所以 MyLinearLayout 的 onTouchEvent 返回 true,然后执行 MyLinearLayout 的 onTouchEvent 的 ACTION_UP,而点击事件就是在 ACTION_UP 中执行的(说明 1)。所有触发了 mLinearLayout.setOnClickListener 点击事件。log 如下:
总结:
1.Touch 事件是从顶层的 View 一直往下分发到手指按下的最里面的 View,如果这个 View 的 onTouchEvent() 返回 false, 即不消费 Touch 事件,这个 Touch 事件就会向上找父布局调用其父布局的 onTouchEvent() 处理,如果这个 View 返回 true, 表示消费了 Touch 事件,就不调用父布局的 onTouchEvent()。
2. 一个 clickable 或者 longClickable 的 View 会永远消费 Touch 事件,不管他是 enabled 还是 disabled 的。
3.View 的长按事件是在 ACTION_DOWN 中执行,要想执行长按事件该 View 必须是 longClickable 的,如果设置的长按事件中返回 true,那么 clickable 事件不会触发。并且不能产生 ACTION_MOVE。
4.View 的点击事件是在 ACTION_UP 中执行,想要执行点击事件的前提是消费了 ACTION_DOWN 和 ACTION_MOVE, 并且是在没有设置 OnLongClickListener 的情况下,如设置了 OnLongClickListener 的情况,则必须使 onLongClick() 返回 false。
5. 如果 View 设置了 onTouchListener 了,并且 onTouch() 方法返回 true,则不执行 View 的 onTouchEvent() 方法,也表示 View 消费了 Touch 事件,返回 false 则继续执行 onTouchEvent()。
6.Touch 事件是从最顶层的 View 一直分发到手指 touch 的最里层的 View, 如果最里层 View 消费了 ACTION_DOWN 事件(设置 onTouchListener,并且 onTouch() 返回 true 或者 onTouchEvent() 方法返回 true)才会触发 ACTION_MOVE,ACTION_UP 的发生, 如果某个 ViewGroup 拦截了 Touch 事件,则 Touch 事件交给 ViewGroup 处理。
来源: http://www.phperz.com/article/17/0312/322266.html