【Android】事件

本文介绍App开发常见的一些事件处理技术,主要包括如何检测并接管按键事件,如何对触摸事件进行分发、拦截与处理,如何实现手势检测与飞掠视图的联合运用,如何正确避免手势冲突的意外状况。

按键事件

本节介绍App开发对按键事件的检测与处理,首先说明如何检测控件对象的按键事件;然后说明如何检测活动页面的物理按键,并以返回键为例阐述"再按一次返回键退出"的功能实现;最后以音量调节对话框为例,介绍如何接管音量按键的处理。

检测软键盘

一般不对手机上的输入按键进行处理,直接由系统按照默认情况操作。当然有时为了改善用户体验,需要让App拦截按键事件,并进行额外处理。在第3章介绍编辑框EditText的用法时提到监控输入字符中的回车键,一旦发现用户敲了回车键,就将焦点自动移到下一个控件,而不是在编辑框输入回车换行。当时的字符输入拦截采用注册文本观测器TextWatcher实现,但该监听器只适用于编辑框控件,无法用于其他控件。因此,若想让其他控件能够监听按键操作,则要另外调用控件对象的setOnKeyListener方法设置按键监听器,并实现监听器接口OnKeyListener的onKey方法。

要监控按键事件,首先得知道每个按键的编码,这样才能根据不同的编码值进行相应的处理。按键编码的取值说明见表11-1。这里注意,监听器OnKeyListener只会检测控制键,不会检测文本键(字母、数字、标点等)。

表11-1 按键编码的取值说明

实际监控结果显示,每次按控制键时,onKey方法都会收到两次重复编码的按键事件,这是因为该方法把每次按键都分成按下与松开两个动作,所以一次按键变成了两个按键动作。解决这个问题的办法很简单,就是只监控按下动作(KeyEvent.ACTION_DOWN)的按键事件,不监控松开动作(KeyEvent.ACTION_UP)的按键事件。

检测物理按键

除了给控件注册按键监听器外,还可以直接在活动页面上检测物理按键,即重写Activity的onKeyDown方法。onKeyDown方法的使用与前面的onKey方法类似,拥有按键编码与按键事件KeyEvent两个参数,当然这两个方法也存在不同之处,具体说明如下:

(1)onKeyDown只能在Activity代码中使用,而onKey只要有可注册的控件就能使用。

(2)onKeyDown只能检测物理按键,无法检测输入法按键(如回车键、删除键等),而onKey可同时检测两类按键。

(3)onKeyDown不区分按下与松开两个动作,而onKey区分这两个动作。

触摸事件

本节介绍对屏幕触摸事件的相关处理,首先说明手势事件的分发流程,包括3个手势方法、3类手势执行者、派发与拦截处理;然后说明手势事件的具体用法,包括单点触摸和多点触控;最后阐述一个手势触摸的具体应用------手写签名功能的实现。

手势事件的分发流程

智能手机的一大革命性技术是把屏幕变为可触摸设备,既可用于信息输出(显示界面)又可用于信息输入(检测用户的触摸行为)。为方便开发者使用,Android已经自动识别特定的几种触摸手势,包括按钮的点击事件(OnClickListener)、长按事件(OnLongClickListener)、滚动视图ScrollView的上下滚动事件、翻页视图ViewPager的左右翻页事件等。不过对于App的高级开发来说,系统自带的几个固定手势显然无法满足丰富多变的业务需求。这时就要求开发者深入了解触摸行为的流程与方法,并在合适的场合接管触摸行为,进行符合需求的事件处理。

与手势事件有关的方法主要有3个(按执行顺序排列):dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent。

  • dispatchTouchEvent:进行事件分发处理,返回结果表示该事件是否需要分发。默认返回true表示分发给下级视图,由下级视图处理该手势,不过最终是否分发成功还得根据onInterceptTouchEvent方法的拦截判断结果;返回false表示不分发,此时必须实现自身的onTouchEvent方法,否则该手势将不会得到处理。
  • onInterceptTouchEvent:进行事件拦截处理,返回结果表示当前容器是否需要拦截该事件。返回true表示予以拦截,该手势不会分发给下级视图,此时必须实现自身的onTouchEvent方法,否则该手势将不会得到处理;默认返回false表示不拦截,该手势会分发给下级视图进行后续处理。
  • onTouchEvent:进行事件触摸处理,返回结果表示该事件是否处理完毕。返回true表示处理完毕,无须处理上级视图的onTouchEvent方法,一路返回结束流程;返回false表示该手势事件尚未完成,返回继续处理上级视图的onTouchEvent方法,然后根据上级onTouchEvent方法的返回值判断直接结束或由上上级处理。

上述手势方法的执行者有3个(按执行顺序排列):页面类、容器类和控件类。

  • 页面类:包括Activity及其派生类。页面类可操作dispatchTouchEvent和onTouchEvent两种方法。
  • 容器类:包括从ViewGroup类派生出的各类容器,如各种布局Layout,以及ListView、GridView、Spinner、ViewPager、RecyclerView、Toolbar等。容器类可操作dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent三种方法。
  • 控件类:包括从View类派生的各类控件,如TextView、ImageView、Button等。控件类可操作dispatchTouchEvent和onTouchEvent两种方法。

可以看出,只有容器类才能操作onInterceptTouchEvent方法,这是因为该方法用于拦截发往下层视图的事件,而控件类已经位于底层,只能被拦截,不能拦截别人。页面类不拥有下层视图,所以不能操作onInterceptTouchEvent方法。

以上涉及3个手势方法和3种手势执行者,其中手势流程的排列组合千变万化,并不容易解释清楚。对于实际开发来说,真正需要处理的组合并不多,所以只要把常见的几种组合搞清楚,就能应付大部分开发工作。

(1)首先是页面类的手势处理,其dispatchTouchEvent必须返回true,因为如果不分发,页面上的视图就无法处理手势。至于页面类的onTouchEvent,基本没什么作用,因为手势动作由具体视图处理,页面直接处理手势没什么意义。所以页面类的手势处理可以不用关心,直接略过。

(2)其次是控件类的手势处理,其dispatchTouchEvent没有任何作用,因为控件下面没有下级视图,无所谓分不分发。至于控件类的onTouchEvent,如果要进行手势处理,就需要自定义一个控件,重写自定义类中的onTouchEvent方法;如果不想自定义控件,就直接调用控件对象的setOnTouchListener方法,注册一个触摸监听器OnTouchListener,并实现该监听器的onTouch方法。所以控件类的手势处理只需关心onTouchEvent方法。

(3)最后是容器类的手势处理,这才是真正要深入了解的地方。容器类的dispatchTouchEvent与onInterceptTouchEvent两个方法都能决定是否将手势交给下级视图处理。为了避免手势响应冲突,一般要重写dispatchTouchEvent方法或onInterceptTouchEvent方法。这两个方法之间的区别可以这么理解:前者是大领导,只管派发任务,不会自己做事情;后者是小领导,尽管有拦截的权利,不过也得自己做点事情,比如处理纠纷。容器类的onTouchEvent近乎摆设,因为需要拦截的在前面已经拦截了,需要处理的在下级视图已经处理了,很少会兜一大圈在这儿处理。

经过上面的详细分析,常见的手势处理方法有下面3种。

  • 容器类的dispatchTouchEvent方法:控制事件的分发,决定把手势交给谁处理。
  • 容器类的onInterceptTouchEvent方法:控制事件的拦截,决定是否要把手势交给下级视图处理。
  • 控件类的onTouchEvent方法:进行手势事件的具体处理。
手势事件处理MotionEvent

dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent的输入参数都是手势事件MotionEvent,其中包含触摸动作的所有信息,各种手势操作都得到MotionEvent中获取信息并进行判断处理。

下面是MotionEvent的常用方法说明。

  • getAction:获取当前的动作类型。动作类型的取值说明见表11-2。

    表11-2 动作类型的取值说明

  • getEventTime:获取事件时间(从开机到现在的毫秒数)。

  • getX:获取在控件内部的相对横坐标。

  • getY:获取在控件内部的相对纵坐标。

  • getRawX:获取在屏幕上的绝对横坐标。

  • getRawY:获取在屏幕上的绝对纵坐标。

  • getPressure:获取触摸的压力大小。

  • getPointerCount:获取触控点的数量,如果为2就表示有两个手指同时按压屏幕。如果触控点数目大于1,坐标相关方法就可输入整型编号,表示获取第几个触控点的坐标信息。

手势检测器GestureDetector

由于触摸事件的检测与识别比较烦琐,因此Android提供了手势检测器GestureDetector帮助开发者识别手势。利用GestureDetector可以自动辨别常用的几个手势事件,如点击、长按、滑动等,从而使开发者专注于业务逻辑,不必在手势的行为判断上绞尽脑汁。

下面是GestureDetector的常用方法。

  • 构造函数:注册手势监听器OnGestureListener,该监听器提供了若干种手势方法,需要重写以接管对应的事件处理。手势方法说明如下:
  1. onDown:在用户按下时触发。
  2. onShowPress:已按下但还未滑动或松开时触发,通常用于按下状态时的高亮显示。
  3. onSingleTapUp:在用户轻点一下弹起时触发,通常用于点击事件。按下时间在0.5秒内为点击。
  4. onScroll:在用户滑动过程中触发。
  5. onLongPress:在用户长按时触发,通常用于长按事件。按下时间超过0.5秒为长按。
  6. onFling:在用户飞快地滑出一段距离时触发,通常用于翻页事件。该方法的前两个参数为滑动开始和结束时的事件信息,后面两个参数分别为滑动操作在横坐标上的滑动速率和在纵坐标上的滑动速率。
    上述手势方法有部分需要返回布尔值,返回true表示该手势已经被处理了,其他人不需要再做无用功;返回false表示该手势没被处理,留给其他人处理。
  • onTouchEvent:由手势检测器接管对应视图的触摸事件。
飞掠视图ViewFlipper

手机屏幕尺寸不大,为了在有限空间中展示尽可能多的信息,Android设计了多种方式显示超出屏幕尺寸的界面,包括上下滚动、左右滑动等。飞掠视图ViewFlipper的层次翻动就是其中一项技术。与ViewPager相比,两者都是一系列类似视图的组合,ViewFlipper更像是视图的立体排列(如现实生活中的书籍),从上往下翻页;ViewPager更像是一幅长长的平面画卷,从左往右翻页。

下面是ViewFlipper的常用方法。

  • setFlipInterval:设置每次翻页的时间间隔。单位毫秒。
  • setAutoStart:设置是否自动开始翻页。为true表示自动开始。
  • startFlipping:开始翻页。
  • stopFlipping:停止翻页。
  • isFlipping:判断当前是否正在翻页。
  • showNext:显示下一个视图。
  • showPrevious:显示上一个视图。
  • setDisplayedChild:设置当前展示第几个视图。
  • getDisplayedChild:获取当前展示的是第几个视图。
  • setInAnimation:设置视图的移入动画。
  • getInAnimation:获取移入动画的动画对象。
  • setOutAnimation:设置视图的移出动画。
  • getOutAnimation:获取移出动画的动画对象。
手势冲突处理

本节介绍手势冲突的三种常见处理办法,对于上下滚动与左右滑动的冲突,既可由上级视图主动判断是否拦截,又可由下级视图根据情况向上级反馈是否允许拦截;对于内部滑动与翻页滑动的冲突,可以通过限定某块区域接管特定的手势实现对不同手势的区分处理;对于正常下拉与下拉刷新的冲突,需要监控当前是否已经下拉到页面顶部,若未拉到页面顶部则为正常下拉,若已拉到页面顶部则为下拉刷新。

上下滚动与左右滑动的冲突处理

Android控件繁多,允许滚动或滑动操作的视图也不少,比如滚动视图ScrollView、翻页视图ViewPager等,如果开发者要自己接管手势处理,像上一节手势控制横幅轮播那样处理,这个页面的滑动就存在重叠的情况,即很可能造成滑动冲突,系统响应了A视图的滑动事件,就顾不上B视图的滑动事件。

举个例子,某电商App的主页很长,内部采用滚动视图ScrollView,允许上下滚动。该页面中央有一个手势控制的横幅轮播。用户在Banner上左右滑动,试图查看Banner的前后广告,结果翻页不成功,整个页面反而往上滚动了。

因为Banner外层被ScrollView包着,系统检测到用户手势的一撇,上级领导ScrollView自作主张地认为用户要把页面往上拉,于是页面往上滚动,完全没考虑这一撇其实是用户想翻动Banner。但是ScrollView不会考虑这些,因为没有告诉它超过多大斜率才可以上下滚动;既然没有通知,ScrollView只要发现手势事件前后的纵坐标发生变化,就会一律进行上下滚动处理。

要解决这个滑动冲突,关键在于提供某种方式通知ScrollView,告诉它什么时候可以上下滚动,什么时候不能上下滚动。这个通知方式主要有两种,一种是上级主动下乡体察民情,即由滚动视图判断滚动规则并决定是否拦截手势;另一种是下级向上反映民意,即由下级视图告诉滚动视图是否拦截手势。下面分别介绍这两种处理方式。
1. 由滚动视图判断滚动规则

前两节提到,容器类视图可以重写onInterceptTouchEvent方法,根据条件判断结果决定是否拦截发给下级的手势。我们可以自定义一个滚动视图,在onInterceptTouchEvent方法中判断本次手势的横坐标与纵坐标,如果纵坐标的偏移大于横坐标的偏移,此时就是垂直滚动,应拦截手势并交给自身进行上下滚动;否则表示此时为水平滚动,不应拦截手势,而是让下级视图处理左右滑动事件。
2. 下级视图告诉滚动视图能否拦截手势

具体到代码的实现,是调用requestDisallowInterceptTouchEvent方法,该方法的参数为true时,表示禁止上级拦截触摸事件。至于何时调用该方法,当然是在检测到滑动前后的横坐标偏移大于纵坐标偏移了。对于Banner采用手势监听器的情况,可重写监听器的onScroll方法,在该方法中加入坐标偏移的判断,代码如下:

修改后的手势滑动效果:左右滑动能够正常翻页,整个页面也不容易上下滚动了。

相关推荐
数据猎手小k2 小时前
AndroidLab:一个系统化的Android代理框架,包含操作环境和可复现的基准测试,支持大型语言模型和多模态模型。
android·人工智能·机器学习·语言模型
你的小103 小时前
JavaWeb项目-----博客系统
android
风和先行3 小时前
adb 命令查看设备存储占用情况
android·adb
AaVictory.4 小时前
Android 开发 Java中 list实现 按照时间格式 yyyy-MM-dd HH:mm 顺序
android·java·list
似霰5 小时前
安卓智能指针sp、wp、RefBase浅析
android·c++·binder
大风起兮云飞扬丶5 小时前
Android——网络请求
android
干一行,爱一行5 小时前
android camera data -> surface 显示
android
断墨先生5 小时前
uniapp—android原生插件开发(3Android真机调试)
android·uni-app
无极程序员7 小时前
PHP常量
android·ide·android studio
萌面小侠Plus8 小时前
Android笔记(三十三):封装设备性能级别判断工具——低端机还是高端机
android·性能优化·kotlin·工具类·低端机