android事件(手机按键 、触摸、手势)(十三)
介绍App开发常见的一些事件处理技术,主要包括如何检测并接管按键事件,如何对触摸事件进行分发、拦截与处理,如何实现手势检测与飞掠视图的联合运用,如何正确避免手势冲突的意外状况。
一、按键事件
介绍App开发对按键事件的检测与处理,首先说明如何检测控件对象的按键事件;然后说明如何检测活动页面的物理按键,并以返回键为例阐述“再按一次返回键退出”的功能实现;最后以音量调节对话框为例,介绍如何接管音量按键的处理。
1.检测软键盘
一般不对手机上的输入按键进行处理,直接由系统按照默认情况操作。当然有时为了改善用户体验,需要让App拦 截按键事件,并进行额外处理。
例如,编辑框EditText允许通过文本观察器TextWatcher监听回车键,但该监听器只适用于编辑框控件,无法用于 其他控件。 若想让其他控件能够监听按键操作,就要另外调用控件对象的setOnKeyListener方法设置按键监听器。
按键编码的取值说明
使用按键监听器的注意事项
(1)监听器OnKeyListener只会检测控制键,不会检测文本键(字母、数字、标点等)。
(2)每次按控制键时,onKey方法都会收到两次重复编码的按键事件(按下与松开两个动作)。解决这个问题的 办法就是只监控按下动作,不监控松开动作。
(3)虽然按键编码表存在首页键、任务键、电源键的定义,但这3个键并不开放给普通App。
2.检测物理按键
除了给控件注册按键监听器外,还可以直接在活动页面上检测物理按键,即重写Activity的onKeyDown方法。
onKeyDown方法的使用与前面的onKey方法类似,拥有按键编码与按键事件KeyEvent两个参数。
onKeyDown方法只可检测4个物理按键事件,即菜单键、返回键、加大音量键和减小音量键,而主页键和任务键 需要通过广播接收器来监测。
onKeyDown与onKey的区别
onKeyDown与onKey两个方法的区别具体说明如下:
(1)onKeyDown只能在Activity代码中使用,而onKey只要有可注册的控件就能使用。
(2)onKeyDown只能检测物理按键,无法检测输入法按键(如回车键、删除键等),而onKey可同时检测两类 按键。
(3)onKeyDown不区分按下与松开两个动作,而onKey区分这两个动作。
“再按一次返回键退出”的实现
在App首页按返回键,系统默认的做法是直接退出该App。然而用户有可能不小心按了返回键,并非想退出该 App,因此这里加个提示,等待用户再次按返回键才执行退出操作。
“再按一次返回键退出”的实现代码有下面两种:
(1)在onKeyDown方法中拦截返回键。
(2)重写Activity代码的onBackPressed方法可实现同样的效果,该方法专门响应按返回键事件。
3.音量调节对话框
除了检测回车键与返回键,音量键也常常需要拦截。
Android有6类铃音,可是音量键只有加大与减少两个键。当用户按音量增加键时,App怎么知道用户希望加大 哪类铃音的音量呢?
因此,在按下音量增减键的时候,最好弹出一个对话框,让用户选择希望调节的铃音类型,并显示拖动条,方 便用户把音量一次调整到位,不必连续按增加键或减小键。
自定义音量对话框的实现
自定义音量对话框还有一个好处,即允许定制对话框的界面风格与显示位置,这在播放音乐和播放电影时尤其 适用。
因为自定义对话框的代码不在Activity中,所以无法通过onKeyDown方法检测按键,只能给拖动条注册按键监听 器OnKeyListener。
另外,在页面代码要重写onKeyDown方法,通过检测音量增加键和减小键弹出音量对话框。
音量调节对话框的展示效果
二、 触摸事件
介绍对屏幕触摸事件的相关处理,首先说明手势事件的分发流程,包括3个手势方法、3类手势执行者、派发与拦截处理;然后说明手势事件的具体用法,包括单点触摸和多点触控;最后阐述一个手势触摸的具体应用——手写签名功能的实现。
1.手势事件的分发流程
Android自动识别的触摸手势包括按钮的点击事件、长按事件,滚动视图的上下滚动事件,翻页视图的左右翻页事 件等。
不过几个固定手势无法满足丰富多变的业务需求。这时就要求开发者在合适的场合接管触摸行为,进行符合需求 的事件处理。
与手势事件有关的方法主要有3个。
dispatchTouchEvent:进行事件分发处理。
onInterceptTouchEvent:进行事件拦截处理。
onTouchEvent:进行事件触摸处理。
手势方法的执行者
上述手势方法的执行者有3个:
(1)页面类:包括Activity及其派生类。
页面类可操作dispatchTouchEvent和onTouchEvent两种方法。
(2)容器类:包括从ViewGroup类派生出的各类容器与布局。
容器类可操作dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent。
(3)控件类:包括从View类派生的各类控件。
控件类可操作dispatchTouchEvent和onTouchEvent两种方法。
下面是一个触摸事件的方法调用流程图。
常见的手势处理方法
常见的手势处理方法有下面3种。
(1)容器类的dispatchTouchEvent方法 控制事件的分发,决定把手势交给谁处理。
(2)容器类的onInterceptTouchEvent方法 控制事件的拦截,决定是否要把手势交给下级视图处理。
(3)控件类的onTouchEvent方法 进行手势事件的具体处理。
2. 手势事件处理MotionEvent
dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent的输入参数都是手势事件MotionEvent,其中包 含触摸动作的所有信息。
下面是MotionEvent的常用方法说明。
getAction:获取当前的动作类型。
getEventTime:获取事件时间(从开机到现在的毫秒数)。
getX:获取在控件内部的相对横坐标。
getY:获取在控件内部的相对纵坐标。
getRawX:获取在屏幕上的绝对横坐标。
getRawY:获取在屏幕上的绝对纵坐标。
getPointerCount:获取触控点的数量。为2表示有两个手指同时按压屏幕。
手势动作的类型说明
单点触摸的实现效果
多点触控的实现效果
3. 手写签名
把手机屏幕当作画板,把用户手指当作画笔,手指在屏幕上划来划去,屏幕就显示手指的移动轨迹,就像画笔在 画板上写字一样。 实现手写签名需要结合绘图的路径工具Path,处理步骤如下:
(1)有按下动作时调用Path对象的moveTo方法,将路径起始点移到触摸点;
(2)有移动操作时调用Path对象的quadTo方法,将记录本次触摸点与上次触摸点之间的路径;
(3)有移动操作与提起动作时调用Canvas对象的drawPath方法,将本次触摸轨迹绘制在画布上。
手写签名的实现效果
三、手势检测
介绍常见手势的检测与使用,首先说明手势检测器的原理与具体用法;然后阐述飞掠视图的基本用法,利用飞掠视图实现简单的横幅轮播;最后结合手势检测器与飞掠视图说明如何通过手势检测器控制横幅轮播的翻页动作。
1.手势检测器GestureDetector
Android提供了手势检测器GestureDetector帮助开发者识别手势。利用它可以自动辨别常用的手势事件,如点 击、长按、滑动等。
下面是GestureDetector的常用方法。
构造函数:注册手势监听器OnGestureListener,该监听器提供了下列手势方法:
onDown:在用户按下时触发。
onShowPress:已按下但还未滑动或松开时触发。
onSingleTapUp:在用户轻点一下弹起时触发。
onScroll:在用户滑动过程中触发。
onLongPress:在用户长按时触发。
onFling:在用户飞快地滑出一段距离时触发。
onTouchEvent:由手势检测器接管对应视图的触摸事件。
手势检测器的实现效果
手势监听器的手势方法有部分需要返回布尔值,说明如下:
(1)返回true表示该手势已经被处理了,其他人不需要再做无用功;
(2)返回false表示该手势没被处理,留给其他人处理。
下面是手势检测器的使用效果图。
2.飞掠视图ViewFlipper
Android设计了多种方式显示超出屏幕尺寸的界面,包括上下滚动、左右滑动等。
飞掠视图ViewFlipper的层次翻动就是其中一 项技术。
飞掠视图ViewFlipper与翻页视图ViewPager相比,两者都是一系列类似视图的组合,区别在于:
(1)ViewFlipper更像是视图的立体排列(如现实生活中的书籍),从上往下翻页;
(2)ViewPager更像是一幅长长的平面画卷,从左往右翻页。
如何使用飞掠视图
下面是ViewFlipper的常用方法。
setFlipInterval:设置每次翻页的时间间隔,单位毫秒。
setAutoStart:设置是否自动开始翻页。
startFlipping:开始翻页。
stopFlipping:停止翻页。
isFlipping:判断当前是否正在翻页。
showNext:显示下一个视图。
showPrevious:显示上一个视图。
setDisplayedChild:设置当前展示第几个视图。
getDisplayedChild:获取当前展示的是第几个视图。
飞掠视图的实现效果
3. 手势控制横幅轮播
下面结合手势检测器与飞掠视图实现手势控制的轮播效果。具体处理步骤如下:
(1)定义一个手势检测器的对象,并在自定义视图的dispatchTouchEvent方法中声明本视图的触摸事件由该检 测器接管。
(2)实现手势监听器的onFling方法,在该方法内部判断播放上一页还是播放下一页。
(3)做一个简单定时器,通过获取当前正在播放的视图编号设置下方指示器对应次序的高亮圆点。
手势控制横幅轮播的效果
四、手势冲突处理
介绍手势冲突的三种常见处理办法,包括这些手势冲突的产生原因,及其相应的处理对策。
1.上下滚动与左右滑动的冲突处理
App主页内部采用滚动视图ScrollView,允许上下滚动。页面中央又有一个手势控制的横幅轮播,此时用户在 Banner上左右滑动,结果非但翻页不成功,整个页面反而往上滚动了。
翻页失败的原因
因为Banner外层被ScrollView包着,系统检测到用户手势的一撇,外层的ScrollView认为用户要把页面往上拉,于 是页面往上滚动,完全没考虑这一撇到底要干什么。
造成问题的缘由是,没人告诉ScrollView超过多大斜率才可以上下滚动;既然无人通知, ScrollView只要发现手势 事件前后的纵坐标发生变化,就会一律进行上下滚动处理。
要解决这个滑动冲突,关键在于提供某种方式通知ScrollView,告诉它什么时候可以上下滚动,什么时候不能上下 滚动。
翻页失败的处理办法
通知ScrollView怎么滚动有两种处理方式:
1. 由滚动视图判断滚动规则
自定义一个滚动视图,在onInterceptTouchEvent方法中判断本次手势的横坐标与纵坐标,如果纵坐 标的偏移大于横坐标的偏移,此时就是垂直滚动,应由自身上下滚动;否则为水平滚动,应让下级视图左右 滑动。
2. 下级视图告诉滚动视图能否拦截手势
下级视图检测到滑动前后的横坐标偏移大于纵坐标偏移时,就调用 requestDisallowInterceptTouchEvent方法(参数值为true),表示禁止上级拦截触摸事件。
2.内部滑动与翻页滑动的冲突处理
比如页面采用ViewPager布局,每个Fragment之间是左右滑动的关系,每个Fragment都可以拥有自己 的ScrollView。如此一来,在左右滑动时,ScrollView反而变成ViewPager的下级。
对于这个新问题,可以考虑两种解决办法:
(1)自定义一个基于ViewPager的翻页视图。
(2)借鉴Android的抽屉布局DrawerLayout,该布局视图允许在左右两侧边缘滑动,在滑动时会拉 出侧面的抽屉面板,常用于实现侧滑菜单。
如何使用抽屉布局
下面是DrawerLayout的常用方法说明。
setDrawerShadow:设置主页面的渐变阴影图形。
addDrawerListener:添加抽屉面板的拉出监听器。
removeDrawerListener:移除抽屉面板的拉出监听器。
closeDrawers:关闭所有抽屉面板。
openDrawer:打开指定抽屉面板。
closeDrawer:关闭指定抽屉面板。
isDrawerOpen:判断指定抽屉面板是否打开。
抽屉布局的实现效果
3.正常下拉与下拉刷新的冲突处理
沉浸式状态栏跟京东首页的头部轮播图相比,依然有三处缺憾:
(1)京东的头部Banner上方,除了有悬浮着的状态栏,状态栏下面还有一行悬浮工具栏,内嵌扫一扫图标、搜索框,以及消息图标;
(2)把整个页面往上拉,状态栏的背景色从透明变为深灰,同时工具栏的背景也从透明变为白色;
(3)页面下拉到顶后,继续下拉会拉出带有“下拉刷新”字样的布局,此时松手则会触发页面的刷新动作;
自定义下拉刷新布局的起因
虽然Android提供了专门的下拉刷新布局SwipeRefreshLayout,但它并没有页面随手势下滚的效果。
若想呈现完全仿照京东的下拉刷新特效,只能由开发者编写一个自定义的下拉刷新布局了,这个新布局首先要能够区分是页面的正常下滚,还是拉伸头部要求刷新。
(1)倘若还没拉到顶,继续下拉动作属于正常的页面滚动;
(2)倘若已经拉到顶了,继续下拉动作才会拉出头部提示刷新。
如何平衡下拉布局和滚动视图
下拉布局和滚动视图需要一个上级布局统筹协调二者的拉动手势。
新的上层视图需要完成以下三项任务:
(1)在下层视图的最前面自动添加一个下拉刷新头部,保证该下拉头部位于整个页面的最上方;
(2)给前面自定义的滚动视图注册滚动监听器和触摸监听器,其中滚动监听器用于处理到达顶部/底部的事件,触摸监听器用于处理下拉过程中的持续位移。
(3)重写触摸监听器接口需要实现的onTouch函数,既要准确响应正常的下拉手势,也要避免误操作不属于下拉的手势。
自定义下拉刷新的实现效果