事件分发机制之 源码解析

事件的下发:dispatchTouchEvent

ViewGroup相关事件有三个:onInterceptTouchEvent、dispatchTouchEvent、onTouchEvent

View相关事件有两个:dispatchTouchEvent、onTouchEvent

简单来说就是:当一个Touch事件到达根节点时,它会依次【下发】,下发的过程是调用子View的dispatchTouchEvent方法实现的。

详细来说就是:ViewGroup遍历它包含着的【子View】,如果Touch事件在屏幕上的位置处于该子View的范围内,则调用此子View的dispatchTouchEvent方法;如果此子View为ViewGroup时,又会通过调用此子ViwGroup的dispatchTouchEvent方法继续调用其子View的dispatchTouchEvent方法;一旦发现某个View的dispatchTouchEvent方法返回值为true时,则下发过程结束。

注意:上面说的"结束"指的是"下发"过程结束,也即一旦某个View的dispatchTouchEvent方法返回值为true,则其子View就不可能会获取到任何Touch事件;但是此View的所有父View都可以获取到此View能获取到的任何事件,包括ACTION_DOWN、ACTION_UP 和ACTION_MOVE 。

View mTarget=null;//记录捕获Touch事件处理的那个View

public boolean dispatchTouchEvent(MotionEvent ev) {

if(ev.getAction()==KeyEvent.ACTION_DOWN){//当是DOWN事件时

if(!onInterceptTouchEvent()){//如果没有拦截

mTarget=null;//每次Down事件,都置为Null

View[] views=getChildView();

for(int i=0;i<views.length;i++){//逐个遍历所有子View

if(...){ //判断Touch事件在屏幕上的位置是否在该子View的范围内……

if(views[i].dispatchTouchEvent(ev))  mTarget=views[i];//判断该子View的dispatchTouchEvent方法是否返回true

return true;//如果返回true,则分发过程结束,且此ViewGroup的dispatchTouchEvent也返回true

}

}

//当子View没有捕获down事件时,ViewGroup自身处理。这里处理的Touch事件包含Down、Up和Move

if(mTarget==null) return super.dispatchTouchEvent(ev);

...

if(onInterceptTouchEvent()){ }

//这一步在Action_Down中是不会执行到的,只有Move和UP才会执行到。

return mTarget.dispatchTouchEvent(ev);

}

//View

public boolean dispatchTouchEvent(MotionEvent event) {

if (!onFilterTouchEventForSecurity(event)) return false;

//如果mOnTouchListener不为null,并且view是enable的状态,并且mOnTouchListener的onTouch的返回值为true

if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED

&& mOnTouchListener.onTouch(this, event)) {

return true;//返回true。也就是说,此时View自己的onTouchEvent方法就不会被执行了

}

return onTouchEvent(event);//否则,返回值完全由自己的onTouchEvent方法决定

}

在此可以看出,【ViewGroup】的dispatchTouchEvent是真正在执行事件【分发】工作,而【View】的dispatchTouchEvent方法只是调用了自己的【onTouchEvent】方法,此方法决定了View是否要处理此touch事件。一般情况下,我们不该在普通View内重写dispatchTouchEvent方法,因为它并不执行分发逻辑,它仅仅是返回了onTouchEvent方法的返回值而已,我们只需重写onTouchEvent方法即可。

事件的上传: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.

// 如果当前View是Disabled状态且是可点击则会消费掉事件

return (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));

}

// 如果设置了mTouchDelegate,则会将事件交给代理者处理,直接return true

if (mTouchDelegate != null && mTouchDelegate.onTouchEvent(event))  return true;

// 如果我们的View可以点击或者可以长按,则……注意if范围内,最终一定是 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  判断当前触摸点有没有移出我们的View

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;

}

关于对ACTION_DOWN的处理

当用户按下触发ACTION_DOWN后,首先会设置标识为PREPRESSED(预压),如果115ms后没有抬起,会去掉PREPRESSED标识并将View的标识设置为PRESSED(压),然后发出一个检测长按的延迟任务,延时为:ViewConfiguration.getLongPressTimeout() - delayOffset(500ms -115ms),这个115ms刚好是检测PREPRESSED时间。

也就是用户从DOWN触发开始算起,如果500ms内没有抬起则认为触发了长按事件:

  • 如果此时设置了长按的回调,则执行长按时的回调,且如果长按的回调返回true才把mHasPerformedLongPress置为ture
  • 否则,如果没有设置长按回调或者长按回调返回的是false,则mHasPerformedLongPress依然是false

关于对ACTION_MOVE的处理

首先拿到当前触摸的x,y坐标,然后判断当前触摸点有没有移出我们的View,如果移出了:

  • 1、执行removeTapCallback(),移除DOWN触发时设置的PREPRESSED的检测,即当前触发时机在DOWN触发不到115ms时,你就已经移出控件外了;
  • 2、然后判断是否包含PRESSED标识(如果115ms后才移出控件外,则包含),如果包含,调用removeLongPressCallback()移除长按的检查
  • 3、然后把mPrivateFlags中PRESSED标识去除
  • 4、最后刷新背景

简单说就是:只要用户移出了我们的控件,则将mPrivateFlags取出PRESSED标识,且移除所有在DOWN中设置的检测,长按等。

关于对ACTION_UP的处理

如果mPrivateFlags包含PRESSED或者PREPRESSED则进入执行体,也就是无论是115ms内或者之后抬起都会进入执行体。

如果mHasPerformedLongPress没有被执行,则调用removeLongPressCallback()移除长按的检测;

如果mPerformClick为null则初始化一个实例,然后立即通过handler添加到消息队列尾部

  • 如果添加失败则直接执行 performClick()
  • 如果添加成功,则在mPerformClick的run方法中执行performClick()

下面看一下performClick()方法:

public boolean performClick() {

sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);

if (mOnClickListener != null) {

playSoundEffect(SoundEffectConstants.CLICK);

mOnClickListener.onClick(this);

return true;

}

return false;

}

久违了~我们的mOnClickListener ;

如果prepressed为true,为mPrivateFlags设置表示为PRESSED,刷新背景,125毫秒后执行mUnsetPressedState.run();

否则mUnsetPressedState.run()立即执行;也就是不管咋样,最后mUnsetPressedState.run()都会执行;

看看这个UnsetPressedState主要干什么:

private final class UnsetPressedState implements Runnable {

public void run() {

setPressed(false);

}

}

public void setPressed(boolean pressed) {

if (pressed)  mPrivateFlags |= PRESSED;

else  mPrivateFlags &= ~PRESSED;

refreshDrawableState();

dispatchSetPressed(pressed);

}

把我们的mPrivateFlags中的PRESSED取消,然后刷新背景,把setPress转发下去。

onTouch和onTouchEvent的区别

对于View,我们可以通过重写onTouchEvent方法来处理Touch事件,也可以通过实现OnTouchListener的接口,然后在onTouch方法中达到同样的目的,这两种监听有什么区别呢?

这两个方法都是在View的dispatchTouchEvent中调用的,onTouch优先于onTouchEvent执行。如果在onTouch方法中通过返回true将事件消费掉,onTouchEvent将不会再执行。

另外需要注意的是,onTouch能够得到执行需要两个前提条件,第一mOnTouchListener的值不能为空,第二当前点击的控件必须是enable的。因此如果你有一个控件是非enable的,那么给它注册onTouch事件将永远得不到执行。对于这一类控件,如果我们想要监听它的touch事件,就必须通过在该控件中重写onTouchEvent方法来实现。

总结

  • 1、onTouchListener的onTouch方法优先级比onTouchEvent方法高,会先触发。
  • 2、假如onTouch方法返回false会接着触发onTouchEvent方法,反之onTouchEvent方法不会被调用。
  • 3、内置诸如click事件的实现等等都基于onTouchEvent,假如onTouch返回true,这些事件将不会被触发。
时间: 2024-12-19 08:48:14

事件分发机制之 源码解析的相关文章

grunt源码解析:整体运行机制&amp;grunt-cli源码解析

前端的童鞋对grunt应该不陌生,前面也陆陆续续的写了几篇grunt入门的文章.本篇文章会更进一步,对grunt的源码进行分析.文章大体内容内容如下: grunt整体设计概览 grunt-cli源码分析 grunt-cli模块概览 grunt-cli源码分析 写在后面 grunt整体设计概览 grunt主要由三部分组成.其中,grunt-cli是本文的讲解重点 grunt-cli:命令行工具,调用本地安装的grunt来运行任务,全局安装. grunt:本地grunt,一般安装在项目根目录下.主要

JDK1.8 动态代理机制及源码解析

动态代理 a) jdk 动态代理 Proxy, 核心思想:通过实现被代理类的所有接口,生成一个字节码文件后构造一个代理对象,通过持有反射构造被代理类的一个实例,再通过invoke反射调用被代理类实例的方法,来实现代理. 缺点:被代理类必须实现一个或多个接口 参考链接:http://rejoy.iteye.com/blog/1627405 源码解析:见第四部分 cglib 动态代理 核心思想:通过生成子类字节码实现,代理类为每个委托方法都生成两个方法,以add方法为例,一个是重写的add方法,一个

Android异步消息处理机制(2)源码解析

上一章讲解了Android异步消息处理机制的基本使用,下面将简单地探寻一下异步机制背后的奥妙,源码版本为:API22. 首先,声明一下本文是在我参考了一下各位大神的文章之后才慢慢熟悉的, 若有不足之处,还望各位批评指正!.菜鸟上路,,,, 郭霖博客 鸿洋博客 刘超 深入解析android5.0系统 任玉刚博客 先后顺序按照拼音排序,无关技术本身. 先简单地总结一下Looper,MessageQueue,Message和Handler四者之间的关系: Looper和MessageQueue Loo

jdk1.8ArrayList主要方法和扩容机制(源码解析)

参见博客:https://blog.csdn.net/u010890358/article/details/80515284?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task   基于jdk1.8涵盖所有方法,有些地方解释牵强不好理解. 补充博客:https://blog.csdn.net/u010250240/article/details/897629

Android 开发艺术探究V第三章之view的事件分发机制

在介绍点击事件的传递机制,首先我们要分析的对象就是MOtionEvent,即点击事件,(当点击屏幕时由硬件传递过来,关于MotionEvent在View的基础知识中做了介绍),所谓的点击事件的分发就是MotionEvent的分发过程.即当一个MoTionEvent产生以后,系统需要把这个事件具体传递给一个具体的View,而这个传递过程就是分发过程,点击事件传递过程有三个很重要的方法,下面先来介绍这几个方法.  public boolean dispatchTouchEvent(MOtionEve

【朝花夕拾】Android自定义View篇之(六)Android事件分发机制(中)从源码分析事件分发逻辑及经常遇到的一些“诡异”现象

前言 转载请注明,转自[https://www.cnblogs.com/andy-songwei/p/11039252.html]谢谢! 在上一篇文章[[朝花夕拾]Android自定义View篇之(五)Android事件分发机制(上)Touch三个重要方法的处理逻辑][下文简称(五),请先阅读完(五)再阅读本文],我们通过示例和log来分析了Android的事件分发机制.这些,我们只是看到了现象,如果要进一步了解事件分发机制,这是不够的,我们还需要透过现象看本质,去研究研究源码.本文将从源码(基

android 从源码分析view事件分发机制

一直对View的事件分发机制不太明白,在项目开发中也遇到过,在网上也找到一些解决问题方法,但是其原理并不太了解,现在辞职了有时间,今天写写View的事件分发,结合android源码一起来学习下,如果讲的不对,往指出一起学习提高,言归正传. 新建一个android项目,里面只有一个activity,有一个button,我们给Button设置setOnClickListener(),setOnTouchListener(),通过log看看结果: btnClick.setOnClickListener

Android异步消息处理机制(4)AsyncTask源码解析

上一章我们学习了抽象类AsyncTask的基本使用(地址:http://blog.csdn.net/wangyongge85/article/details/47988569),下面我将以问答的方法分析AsyncTask源码内容,源码版本为:API22. 1. 为什么必须在UI线程实例化我们的AsyncTask,并且必须在主线程中调用execute(Params... params)? 在分析为什么在UI线程调用之前,我们先看一下实例化AsyncTask并调用execute(Params...

Android源码解析——Toast

简介 Toast是一种向用户快速提供少量信息的视图.当它显示时,它会浮在整个应用层的上面,并且不会获取到焦点.它的设计思想是能够向用户展示些信息,但又能尽量不显得唐突.本篇我们来研读一下Toast的源码,并探明它的显示及隐藏机制. 源码解析 Toast 我们从Toast的最简单调用开始,它的调用代码是: Toast.makeText(context,"Show toast",Toast.LENGTH_LONG).show(); 在上面的代码中,我们是先调用Toast的静态方法来创建一个