上次跟大家分享了一下自定义View的一下要点,这次跟大家聊一下View的事件分发及处理,为什么主题都是View,因为作为一名初级应用层Android工程师,跟我打交道最多的莫过于各种各样的View,只有详细了解他们各自的习性,才能更好地跟他们沟通交流,做出自己想要的效果。

基础储备 View、MotionEvent

我们都能详细地说出Android的四大组件:Activity,Service,ContentProvider和BoardcastReceiver,但是四大组件之外,我们用到也很多的是什么,其中肯定包括View,View是用户跟程序沟通的入口,也是程序展现给用户信息的窗口。关于View,一些基础属性还是要了解的,left,top,right,bottom,分别代表了view的左上角和右下角分别相对x轴,y轴的坐标,而且view的getWidth和getHeight的值都是通过这四个值算得,而且在Android3.0中还增加了x,y,translationX和translationY这几个属性,便于我们对view的平移操作,x、y代表了当前view左上角的xy坐标,而translationX和translationY代表了view相对它的父容器的偏移量,默认值是0。

MotionEvent表示用户的触摸事件,用户的一次点击、触摸或者滑动都会产生一系列的MotionEvent:

  • MotionEvent.ACTION_DOWN 表示用户的手指刚接触到屏幕
  • MotionEvent.ACTION_MOVE 表示用户的手指正在移动
  • MotionEvent.ACTION_UP 表示用户的手指从屏幕上抬起

所以一次用户触摸屏幕可能会产生这些事件:

  • 点击屏幕然后松开,Down->Up
  • 点击屏幕,然后滑动一段距离,松开屏幕 ,Down->Move->…->Move->Up
    了解了这些基本知识以后,我们就来学习一下具体怎么分发这些事件

ViewGroup 分发-> 拦截 -> 处理

首先说一点,虽然ViewGroup也是继承View而来,但是因为在事件拦截上,ViewGroup分析起来更加方便理解,所以先说ViewGroup,下面也会简单介绍一下View的事件处理。

在事件分发的过程中,主要涉及到三个方法:

  • dispatchTouchEvent(MotionEvent event);
  • onInterceptTouchEvent(MotionEvent event);
  • onTouchEvent();
    初看这三个方法就有蒙圈,如果这时候在蒙头钻进源码里,就更是糊涂,我在这里借用任玉刚大大的一段伪代码解释一下这三者之间的关系:
  • [代码]java代码:

    public boolean dispatchTouchEvent(MotionEvent event) {
    boolean consume = false;
    if (onInterceptTouchEvent(event)) {
    consume = onTouchEvent(event);
    } else {
    consume = child.dispatchTouchEvent(event);
    } return consume;
    }

  • 从这段伪代码中,我们可以看出来,在dispatchTouchEvent中,先调用ViewGroup自身的onInterceptTouchEvent方法,判断自己是否要拦截,如果这时候自己拦截,那就调用自己的onTouchEvent方法,如果onTouchEvent方法返回了True,那么这次的事件就算消耗了,事件传递到此为止,如果返回了False,证明这次没有消耗这次MotionEvent,那么这次的事件就会往上返回,由上一级继续处理;如果当前ViewGroup的onInterceptTouchEvent返回了False,那就会调用它的子view的dispatchTouchEvent方法,这样这个事件就传递下去了,如果它的子View处理不了,那么还会回来调用ViewGroup的onTouchEvent方法,当然这一点是没有在这一段伪代码里体现的,用一段通俗的例子解释:

    领导收到一份任务(有可能是上级给的),自己看了一眼,然后决定好好休息,今天不工作,就把这个任务交给了手下的小王,小王的默认属性是只要来任务就接,而且就干,能干不能干一样接,如果这是一个简单的任务,那么小王就解决了,这个任务也就完成了,不幸的是,这次任务小王没有解决掉,然后向领导反馈,领导没办法,手下没人能解决,只好自己干了,就开始解决问题,然后解决掉,任务也完成了。

这就是ViewGroup层的事件分发,当然不是这么简单,这只不过是通过简单的方式去理解,其实在真实的事件分发中,有很多问题需要注意:

  • 一个完成的事件序列以Down开始,中间可能包含若干个Move,然后以Up结束
  • 一个view一旦拦截一个某个事件,当前事件所在的完整事件序列将都会由这个view去处理,反应在真实的代码中,就是一旦view拦截了down事件,那么此后的move和up事件都将不调用onInterceptTouchEvent,而直接由它处理,这就也意味着在onInterceptTouchEvent处理事件是不合适的,因为有可能来了事件,却直接跳过onInterceptTouchEvent方法。这个也意味着,一旦一个ViewGroup没有拦截ACTION_DOWN,那么这个事件序列的其他Action,它都将收不到,所以在处理ACTION_DOWN的时候,尤其需要谨慎。
  • onTouchEvent中是要判断MotionEvent的Action,因为一次点击操作就会调用两次onTouchEvent方法,一次是ACTION_DOWN,一次是ACTION_UP,如果手滑一下,还会有若干个ACTION_MOVE
  • ViewGroup默认不拦截任何事件,源码中ViewGroup的onInterceptTouchEvent方法默认返回的是false

整个事件分发,看起来都是由外向内传递的,父View将事件传递给子View,理论上来看,子View是没有办法影响到父View的事件处理的,但是有一个标示位,requestDisallowInterceptTouchEvent方法,通过这个方法 ,子View能够影响父view的事件处理,这个可以用于解决父view和子view的滑动冲突,具体想了解的可以搜索它的相关用法,这里将不进行展开。

View 只有默默的承受

View不同于ViewGroup的是,View中没有onInterceptTouchEvent方法,因为View作为事件处理的最后一级,不需要判断是否要拦截,是一定要拦截,不管能不能处理,都要试一下,所以在View中调用流程是:

dispatchTouchEvent -> onTouchEvent

而且,最后onTouchEvent的返回值默认都是True,也就是说事件传递下去一般都会被消耗掉的,只是看中途是否有人拦截,这个时候读者可能会有疑问:TextView的onTouchEvent的返回值也是True吗?答案就是:是的,那为什么点在TextView上面还是能触发它的父视图的onTouchEvent,理论上不应该是,TextView消耗掉这次的事件,不回传。理论上确实是这样,但是因为TextView的clickable和longClickable属性都是false,当这两个属性都为false的时候,是不会消耗事件的,所以TextView不会消耗事件,这也就可以解释为什么把一个TextView放在一个Button上面,然后点击TextView还是能触发Button的点击事件

在这里可能需要提醒一下大家,算是一个我之前踩到的一个坑,我把一个view的enable状态设成了false,然后又给它增加了onClickListener,这时候我本以为,它的点击事件不会被触发,结果它还是可以被点击,后来才了解到,view的enable状态和onTouchEvent是没有关系的,只有clickable状态是对onTouchEvent有影响的,还有一点 ,设置 view的enable为false确实也会把view的clickable设成false,但是设置view的onclickListener就又把view的clickable变成了true,所以最后的解决方案就是把那两行代码换下先后顺序,问题就迎刃而解了。

详解处理GesutureDetector

费劲千辛万苦,终于把事件拦截下来了,然后我们需要总得做点什么吧,不然都对不起自己浪费这么多口舌,说到对事件的处理,我们首先想到的就是setOnClickListener,殊不知onClickListener的优先级是最低的,下一节里面会对优先级进行说明,而这里,我们将主要想着如果处理事件,当我们兴奋地拿到一连串的事件,但又不知如何下手,甚至于连最简单的点击事件都要自己进行一番处理,更别提做成平移、旋转、缩放这样的操作,但是官方提供的GestureDetector给我们提供了可能。

官方提供的GestureDetector是一个手势辅助检测类,默认能够检测多种手势:

[代码]java代码:

class SimpleGestureListener implements GestureDetector.OnGestureListener {
@Override
public boolean onDown(MotionEvent e) {
return false;
} @Override
public void onShowPress(MotionEvent e) { } @Override
public boolean onSingleTapUp(MotionEvent e) {
return false;
} @Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
return false;
} @Override
public void onLongPress(MotionEvent e) { } @Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
return false;
}
}

通过这个类,我们可以很方便的处理除了单击和长按之外,还有滑动,双击等各种手势,并对其分别进行处理,如果这些还是不能满足你的好奇心,那还有一个官方提供的ScaleGestureDetector,从名字就可以判断出来这是一个检测缩放手势的辅助类,而且还有大牛仿照ScaleGestureDetector思路做出了平移以及旋转的辅助类,然后我们就可以根据这些辅助类,几乎为所欲为了,下面我写了一个支持平移,缩放,旋转的小Demo。

[代码]java代码:

private void init() {

        scaleGesture = new ScaleGestureDetector(getContext(), new ScaleListener());
moveGesture = new MoveGestureDetector(getContext(), new MovingListener());
rotateGesture = new RotateGestureDetector(getContext(), new RotateListener()); } @Override
public boolean onTouchEvent(MotionEvent event) { scaleGesture.onTouchEvent(event);
moveGesture.onTouchEvent(event);
rotateGesture.onTouchEvent(event); return true;
} private class ScaleListener implements ScaleGestureDetector.OnScaleGestureListener { @Override
public boolean onScale(ScaleGestureDetector detector) { setScaleX(detector.getScaleFactor() * getScaleX());
setScaleY(detector.getScaleFactor() * getScaleY()); return true;
} @Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
return true;
} @Override
public void onScaleEnd(ScaleGestureDetector detector) { }
}


只贴了部分的代码,而且貌似旋转好像还有点问题,以后时间再修正,有用到的读者可以在详细了解下,完整代码,我会在文章的最后给出链接,同时感谢Android multitouch gesture detectors的作者,提供了这么方便的手势操作类

onTouchListener OnTouchEvent OnClickListener

我们在知道onTouchEvent之前肯定都知道onClickListener和onTouchListener,而他们都是事件的消费者,onTouchListener是在onTouch方法中生效,而且onTouch要先于onTouchEvent,就是说一旦设置了onTouchListener并且最后onTouch方法返回了True,那onTouchEvent将不会再被执行,而onClickListener和onTouchEvent有些关系,onTouchEvent的默认实现里会调用onClickListener的onClick方法,如果重写了onTouchEvent,因为onClickListener接受不到ACTION_DOWN和ACTION_UP,那么再设置onClickListener也就不会再生效了,这个时候的单击或者长按处理只能在onTouchEvent中自己处理。

Android中的事件分发和处理的更多相关文章

  1. Android中的事件分发机制

    Android中的事件分发机制 作者:丁明祥 邮箱:2780087178@qq.com 这篇文章这周之内尽量写完 参考资料: Android事件分发机制完全解析,带你从源码的角度彻底理解(上) And ...

  2. 【转】Android中的事件分发和处理

    原文链接:http://www.apkbus.com/home.php?mod=space&uid=705730&do=blog&id=61207 上次跟大家分享了一下自定义V ...

  3. Android中的事件分发机制总结

    Android 的事件分发机制 一.View的事件分发总结: View的onTouchEvent和OnTouch区别  还是以自定义的TestButton为例. 我们可以通过重写onTouchEven ...

  4. 浅谈Android中的事件分发机制

    View事件分发机制的本质就是就是MotionEvent事件的分发过程,即MotionEvent产生后是怎样在View之间传递及处理的. 首先介绍一下什么是MotionEvent.所谓MotionEv ...

  5. 一个demo让你彻底理解Android中触摸事件的分发

    注:本文涉及的demo的地址:https://github.com/absfree/TouchDispatch 1. 触摸动作及事件序列 (1)触摸事件的动作 触摸动作一共有三种:ACTION_DOW ...

  6. cocos2dx 中触摸事件分发一些解读

    触摸事件分发中几个代码解读: 怎么说呢,感觉cocos2dx中的消息分发机制,相对于android中触摸事件分发机制要简单的多.因为android中要做区域判断,过滤器,以及父子组件分发给谁等等的逻辑 ...

  7. android中的事件传递和处理机制

    一直以来,都被android中的事件传递和处理机制深深的困扰!今天特意来好好的探讨一下.现在的感觉是,只要你理解到位,其实事件的 传递和处理机制并没有想象中的那么难.总之,不要自己打击自己,要相信自己 ...

  8. 安卓中的事件分发机制之View控件

    前言:Android 中与 Touch 事件相关的方法包括:dispatchTouchEvent(MotionEvent ev).onInterceptTouchEvent(MotionEvent e ...

  9. Android View 的事件分发原理解析

    作为一名 Android 开发者,每天接触最多的就是 View 了.Android View 虽然不是四大组件,但其并不比四大组件的地位低.而 View 的核心知识点事件分发机制则是不少刚入门同学的拦 ...

随机推荐

  1. 传说中的WCF(4):发送和接收SOAP头

    如果你实在不明白Header是个啥玩意儿,你就想一想你发送电子邮件时,是不是有个叫“附件”的东东?对啊,那么SOAP头是不是可以理解为一种附加信息?就是附加到消息正文的内容. 消息正文又是啥?WCF除 ...

  2. Android OpenGL 学习笔记 --开始篇

    转自: http://www.cnblogs.com/TerryBlog/archive/2010/07/09/1774475.html 1.什么是 OpenGL? OpenGL 是个专业的3D程序接 ...

  3. Linux网络编程8——对TCP与UDP的简易封装

    引言 每次使用socket通信,都会有很对相似的操作.本文,会对TCP与UDP通信做一简单封装,并生成动态库. 代码 my_socket.h #ifndef __MY_SOCKET_H__ #defi ...

  4. poj 2762(强连通+判断链)

    题目链接:http://poj.org/problem?id=2762 思路:首先当然是要缩点建新图,由于题目要求是从u->v或从v->u连通,显然是要求单连通了,也就是要求一条长链了,最 ...

  5. python--httplib模块使用

    httplib是一个相对底层的http请求模块,其上有专门的包装模块,如urllib内建模块,goto等第三方模块,但是封装的越高就越不灵 活,比如urllib模块里请求错误时就不会返回结果页的内容, ...

  6. linux登陆欢迎提示信息的设置

    Linux可以设置登录前后的欢迎信息,虽然没啥技术含量,但却是非常实用的一个小技巧. 实现登录消息的功能,可以修改3个文件. 1./etc/issue 本地登陆显示的信息,本地登录前 2./etc/i ...

  7. 百度首页html代码

          把百度设为主页 关于百度 About Baidu ©2015 Baidu 使用百度前必读 意见反馈 京ICP证030173号

  8. iOS开发日期处理

    Foundation框架之 日期与时间 #import"ViewController.h"@interfaceViewController() { NSTimer*_timer;/ ...

  9. JLINK固件丢失或升级固件后提示Clone的解决办法

    J-LINK V8固件烧录指导 J-LINK 是使用过程中,如果内部固件意外损坏或丢失,请参考下面操作步骤说明,重新烧录JLINK固件. 安装固件烧录软件 请ATMEL官方网址下载AT91-ISP下载 ...

  10. C语言:几种字符输入函数的区别

    几种字符输入函数的区别: 1.getche()函数:用于从键盘读入一个字符并显示,然后直接执行下一条语   句. 2.getch()函数:用于从键盘中读入一个字符,但不显示在屏幕上,       然后 ...