Android事件分发详解(二)——View的事件分发

MainActivity如下:

package cc.cv;

import android.os.Bundle;
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.ImageView;
import android.app.Activity;
/**
 * Demo描述:
 * View(如ButtonSubClass extends Button)的事件分发
 *
 *
 * 重点说明:
 * 1 在该示例中讨论的是View(如ButtonSubClass extends Button)的事件分发,而不是ViewGroup的
 * 2 ViewGroup中事件处理的流程是:
 *   dispatchTouchEvent->onInterceptTouchEvent->onTouchEvent
 *   View(如ButtonSubClass extends Button)中事件处理的流程是:
 *   dispatchTouchEvent->onTouchEvent
 *   即在View(如ButtonSubClass extends Button)中是没有onInterceptTouchEvent事件拦截的.
 *   因为该类View已经处于事件传递的末端,对于拦截没有实际意义,也谈不上什么拦截
 *
 *
 * 概要梳理:
 * 在View(如ButtonSubClass extends Button)的事件分发过程中主要涉及到:
 * dispatchTouchEvent()和onTouchEvent()以及该View的TouchListener中的onTouch()
 * 1 事件的分发从dispatchTouchEvent()开始.
 *   返回值为true时表示继续事件分发;返回值为false时表示终止事件分发.
 * 2 onTouchEvent()中在ACTION_UP即手指抬起时处理点击Click事件
 * 3 TouchListener中的onTouch()返回 true或者false表示是事件是否被消耗
 *
 *
 * dispatchTouchEvent()源码如下:
 * public boolean dispatchTouchEvent(MotionEvent event) {
 *   if (mOnTouchListener!= null&&(mViewFlags & ENABLED_MASK)==ENABLED&&mOnTouchListener.onTouch(this,event)){
 *        return true;
 *     }
 *   return onTouchEvent(event);
 * }
 *
 * 该方法的返回值有两种情况:
 *
 * 1 满足if条件时,返回true.注意该if条件的三个判断.
 *     1.1 mOnTouchListener不等于null
 *     1.2 当前控件是enable的
 *     1.3 调用mOnTouchListener.onTouch(this,event)返回的结果
 *     前两个条件没啥可多说的,主要看看第三个条件:
 *     在 onTouch(View v, MotionEvent event)中会处理一系列的ACTION_DOWN,ACTION_MOVE,ACTION_UP.
 *     该onTouch()方法返回true表示事件已经消耗,返回false表示事件未消耗.
 *     比如在处理ACTION_DOWN时返回true才会继续分发ACTION_MOVE事件
 *     比如在处理ACTION_MOVE时返回true才会继续分发ACTION_UP事件
 *     比如在处理ACTION_DOWN时返回false,那么后续的ACTION_MOVE,ACTION_UP就不会再继续分发.
 *     我们在代码中也就无法捕捉到ACTION_MOVE,ACTION_UP这两个Action了.
 *
 * 2 假如该if条件不满足,那么就继续执行,返回 onTouchEvent(event)的执行结果.
 *   即在dispatchTouchEvent()中调用了onTouchEvent()!!!!!!!!
 *
 *
 * 从该dispatchTouchEvent()的源码也可以看出
 * onTouch(this,event)和 onTouchEvent(event)的区别和关系:
 * 1 onTouch()与onTouchEvent()都是用户处理触摸事件的API.
 * 2 onTouch()是View专门提供给用户的接口,是为了方便用户处理触摸事件,
 *   而onTouchEvent()是Android系统自己实现的接口。
 * 3 先调用TouchListener中的onTouch()后调用onTouchEvent()即onTouch()的优先级比onTouchEvent()的优先级更高。
 * 4 在TouchListener中的onTouch()方法中处理了Touch事件,即处理一系列的ACTION_DOWN,ACTION_MOVE,ACTION_UP事件
 *   返回false时表示事件(每个单独的ACTION_DOWN,ACTION_MOVE,ACTION_UP都叫一个事件,并不是说这三者联系在一起才是一个事件)
 *   未被消耗才会调用onTouchEvent(event).
 * 5 在onTouchEvent(event)中的ACTION_UP事件里(即手指抬起)会调用performClick()处理OnClick点击事件!!!!
 * 6 所以可知:
 *   4.1 Touch事件先于Click事件发生和处理,且注意TouchListener中的onTouch()方法默认返回为false.
 *   4.2 只有在TouchListener中的onTouch()返回false时(即事件未被消耗)才会调用onTouchEvent()
 *   4.3 在onTouchEvent()中的ACTION_UP事件里(即手指抬起)会调用performClick()处理OnClick点击事件.
 * 7 参见下面的onTouchEvent()源码,请注意第三个if判断,这个if判断很重要!!!!!!!
 *   5.1 在该if条件中判断该控件是否是可点击的(CLICKABLE)或者是否是可以长按的(LONG_CLICKABLE).
 *   5.2 如果满足CLICKABLE和LONG_CLICKABLE中任一条件则始终会返回true给onTouchEvent()方法
 *   5.3 如果CLICKABLE和LONG_CLICKABLE这两个条件都不满足则返回false给onTouchEvent()方法
 *
 *   请注意:
 *   Button默认情况下就是CLICKABLE和LONG_CLICKABLE的,但是ImageView在
 *   默认情况下CLICKABLE和LONG_CLICKABL均为不可用的.
 *
 *   所以在用Button和ImageView分别实验OnTouchListener和OnClickListener是有区别的.
 *   再次提醒注意:TouchListener中的onTouch()方法默认返回为false.
 *   1 Button做实验分析dispatchTouchEvent().
 *     mOnTouchListener.onTouch()返回false(默认值),
 *     所以dispatchTouchEvent()源码中的if不满足,于是会执行onTouchEvent(event).
 *     进入onTouchEvent()源码后,请留意第三个if的判断!!!
 *     由于Button默认情况下就是CLICKABLE和LONG_CLICKABLE的,所以满足该if,于是进入后
 *     进行ACTION_DOWN,ACTION_MOVE,ACTION_UP的处理.
 *     请注意:在该if最后总是会返回true(事件被消费)!!!!!!!!!!!!!!!!
 *     即onTouchEvent()的返回值为true,该值此时继续作为返回值返回给dispatchTouchEvent().
 *     因为dispatchTouchEvent()收到的返回值是true,所以继续ACTION_MOVE,ACTION_UP.
 *     小结:
 *     我们在Button控件的TouchListener的onTouch()返回了false,表示事件未消费.
 *     按理说只会响应ACTION_DOWN不会有后续的ACTION_MOVE,ACTION_UP.
 *     但是事与愿违,事实情况并非如此.这是为什么呢?
 *     因为mOnTouchListener.onTouch()返回false,所以dispatchTouchEvent()中会执行onTouchEvent(event)方法
 *     在onTouchEvent(event)对应Button(默认情况下是CLICKABLE和LONG_CLICKABLE的)而言,
 *     该方法总会返回true(事件被消费)给dispatchTouchEvent(),所以会继续事件的派发.
 *
 *     所以可以捕获到一系列的:ACTION_DOWN,ACTION_MOVE,ACTION_UP.
 *     这就解释了为什么在Button中虽然TouchListener的onTouch()返回false(默认值)但事件分发还在继续!!!!
 *
 *   2 用ImageView做实验分析dispatchTouchEvent().
 *     mOnTouchListener.onTouch()返回false(默认值),所以dispatchTouchEvent()
 *     如上dispatchTouchEvent()源码中的if不满足,在调用onTouchEvent(event)时
 *     由于ImageView不满足CLICKABLE和LONG_CLICKABLE中任何一个条件。
 *     所以最后返回给dispatchTouchEvent()的是false,即终止事件的分发.所以对于ImageView只有
 *     ACTION_DOWN没有ACTION_MOVE和ACTION_UP
 *     这里就解释了为什么在ImageView中在TouchListener的onTouch()返回false(默认值)就终止了事件分发!!!!!!!!!!!!!
 *
 *   如何才可以使ImageView像Button那样"正规的"事件分发,有如下两个方法:
 *   1 为ImageView设置setOnTouchListener,且在其onTouch()方法中返回true而不是默认的false.
 *   2 为ImageView设置android:clickable="true"或者ImageView设置OnClickListener.
 *      就是说让ImageView变得可点击.
 *   3 详情可见以下代码中的例子,关于这一点在下面的例子中有体现.
 *
 *
 *
 * 参考资料:
 * http://blog.csdn.net/guolin_blog/article/details/9097463
 * Thank you very much
 */

public class MainActivity extends Activity {
    private Button mButton;
    private ImageView mImageView;
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		initButton();
		initImageView();
	}

	private void initButton(){
		mButton=(Button) findViewById(R.id.button);
		mButton.setOnTouchListener(new OnTouchListener() {
			@Override
			public boolean onTouch(View v, MotionEvent event) {
				switch (event.getAction()) {
				case MotionEvent.ACTION_DOWN:
					System.out.println("Button ACTION_DOWN");
					break;
				case MotionEvent.ACTION_MOVE:
					System.out.println("Button ACTION_MOVE");
					break;
				case MotionEvent.ACTION_UP:
					System.out.println("Button ACTION_UP");
					break;
				default:
					break;
				}
				return false;
			}
		});

		mButton.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
				System.out.println("Button Clicked");
			}
		});

	}

	private void initImageView(){
		mImageView=(ImageView) findViewById(R.id.imageView);
		mImageView.setOnTouchListener(new OnTouchListener() {
			@Override
			public boolean onTouch(View v, MotionEvent event) {
				switch (event.getAction()) {
				case MotionEvent.ACTION_DOWN:
					System.out.println("ImageView ACTION_DOWN");
					break;
				case MotionEvent.ACTION_MOVE:
					System.out.println("ImageView ACTION_MOVE");
					break;
				case MotionEvent.ACTION_UP:
					System.out.println("ImageView ACTION_UP");
					break;
				default:
					break;
				}
				return false;
			}
		});

	    //因为ImageView默认是不可点击的,所以如果屏蔽掉以下的代码,则只有
		//ImageView的ACTION_DOWN没有ACTION_MOVE和ACTION_UP
		mImageView.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
				System.out.println("ImageView Clicked");
			}
		});

	}

}

/*
 * 此处为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判断
    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;
}
 */

ButtonSubClass如下:

package cc.cv;

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.Button;

public class ButtonSubClass extends Button {

	public ButtonSubClass(Context context) {
		super(context);
	}

	public ButtonSubClass(Context context, AttributeSet attrs) {
		super(context, attrs);
	}

	public ButtonSubClass(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
	}

	@Override
	public boolean onTouchEvent(MotionEvent event) {
		return super.onTouchEvent(event);
	}

	@Override
	public boolean dispatchTouchEvent(MotionEvent event) {
		return super.dispatchTouchEvent(event);
	}

}

main.xml如下:

<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" >

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="100dip"
        android:text="Button" />

    <ImageView
        android:id="@+id/imageView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="200dip"
        android:src="@drawable/ic_launcher" />

</RelativeLayout>
时间: 2024-10-25 12:42:51

Android事件分发详解(二)——View的事件分发的相关文章

Android事件分发详解(二)——Touch事件传入到Activity的流程

PS: 该系列博客已更新,详情请参见: http://blog.csdn.net/lfdfhl/article/details/50707742 http://blog.csdn.net/lfdfhl/article/details/50707731 http://blog.csdn.net/lfdfhl/article/details/50707724 http://blog.csdn.net/lfdfhl/article/details/50707721 http://blog.csdn.n

Android Studio 插件开发详解四:填坑

转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/78265540 本文出自[赵彦军的博客] 插件打包 坑一 id不能重复 坑二插件描述信息要完整 坑三插件支持的产品要说明 总结 在前面我介绍了插件开发的基本流程 [Android Studio 插件开发详解一:入门练手] [Android Studio 插件开发详解二:工具类] [Android Studio 插件开发详解三:翻译插件实战] 在经历的前面的3篇文章,我相信大家都可以

Android Studio 插件开发详解一:入门练手

转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/78112003 本文出自[赵彦军的博客] 一:概述 相信大家在使用Android Studio的时候,或多或少的会使用一些插件,适当的配合插件可以帮助我们提升一定的开发效率,更加快乐.例如: https://github.com/zzz40500/GsonFormat 可以帮助我们从Gson转化为实体类 https://github.com/avast/android-butter

Android Studio 插件开发详解三:翻译插件实战

转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/78113868 本文出自[赵彦军的博客] 一:概述 如果不了解插件开发基础的同学可以先看, Android Studio 插件开发详解一:入门练手 Android Studio 插件开发详解二:工具类 在上面的两篇文章,讲解了插件开发的基础,今天就来一个优点难度的项目,插件的名字叫 AndroidPluginTranslate , 顾名思义就是可以翻译文案的插件,废话不多说,先看最

Android 事件分发详解及示例代码_Android

事件分发是Android中非常重要的机制,是用户与界面交互的基础.这篇文章将通过示例打印出的Log,绘制出事件分发的流程图,让大家更容易的去理解Android的事件分发机制. 一.必要的基础知识 1.相关方法 Android中与事件分发相关的方法主要包括dispatchTouchEvent.onInterceptTouchEvent.onTouchEvent三个方法,而事件分发一般会经过三种容器,分别为Activity.ViewGroup.View.下表对这三种容器分别拥有的事件分发相关方法进行

Android编程输入事件流程详解_Android

本文实例讲述了Android编程输入事件流程.分享给大家供大家参考,具体如下: EventHub对输入设备进行了封装.输入设备驱动程序对用户空间应用程序提供一些设备文件,这些设备文件放在/dev/input里面. EventHub扫描/dev/input下所有设备文件,并打开它们. bool EventHub::openPlatformInput(void) { ... mFDCount = 1; mFDs = (pollfd *)calloc(1, sizeof(mFDs[0])); mDev

Android 布局学习之——Layout(布局)详解二(常见布局和布局参数)

  [Android布局学习系列]   1.Android 布局学习之--Layout(布局)详解一   2.Android 布局学习之--Layout(布局)详解二(常见布局和布局参数)   3.Android 布局学习之--LinearLayout的layout_weight属性   4.Android 布局学习之--LinearLayout属性baselineAligned的作用及baseline      Layout Parameters(布局参数):            在XML文

Android Animation动画详解(二): 组合动画特效

前言     上一篇博客Android Animation动画详解(一): 补间动画 我已经为大家介绍了Android补间动画的四种形式,相信读过该博客的兄弟们一起都了解了.如果你还不了解,那点链接过去研读一番,然后再过来跟着我一起学习如何把简单的动画效果组合在一起,做出比较酷炫的动画特效吧. 一. 动画的续播     如题,大家想想,如果一个页面上包含了许多动画,这些动画要求按顺序播放,即一个动画播放完成后,继续播放另一个动画,使得这些动画具有连贯性.那该如何实现呢? 有开发经验或者是逻辑思维

android 之断点续传详解三部曲之[二] → 断点续传下载

在上一篇中,我们简单介绍了如何创建多任务下载,但那种还不能拿来实用,这一集我们重点通过代码为大家展示如何创建多线程断点续传下载,这在实际项目中很常用. 和上一篇中一样,先来布局文件: main.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" and