手势事件的流程

基本手势事件

基本的手势事件主要有如下三个方法:
dispatchTouchEvent : 判断该事件是否需要下发。返回true表示需要下发给下级视图,返回false表示不需要下发(交给自身的onTouchEvent处理)。但是否最终下发,还需根据onInterceptTouchEvent的拦截结果。
onInterceptTouchEvent : 判断当前容器是否需要拦截该事件。返回true表示予以拦截(交给自身的onTouchEvent处理)、不放给下级视图,返回false表示不拦截该事件。
onTouchEvent : 判断该事件是否处理完毕。返回true表示处理完毕,则无需处理上级视图的onTouchEvent,一路返回结束流程。返回false表示该事件未完成,则返回继续处理上级视图的onTouchEvent,然后再根据上级onTouchEvent的返回值判断是直接结束还是由再上级处理。

手势方法的执行者

页面类:包括Activity及Activity的派生类。页面类可操作dispatchTouchEvent和onTouchEvent。注意Fragment不能操作基本手势方法,只能通过实现OnTouchListener接口来响应手势事件。

控件类:包括从View类派生出的各类控件,包括TextView、ImageView、Button等及它们的派生类。控件类可操作dispatchTouchEvent和onTouchEvent。

容器类:包括从ViewGroup类派生出的各类容器,如三个布局LinearLayout、RelativeLayout、FrameLayout,以及AdapterView派生出来的GridView、ListView、Spinner,还有ViewPager、ViewFlipper等等。容器类可操作dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent。

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

手势事件的生命周期

控件响应
Activity.dispatchTouchEvent(返回true)->ViewGroup.dispatchTouchEvent(返回true)->ViewGroup.onInterceptTouchEvent(返回false)->View.dispatchTouchEvent(返回true)->View.onTouchEvent(返回true)->结束

容器响应
方式一:Activity.dispatchTouchEvent(返回true)->ViewGroup.dispatchTouchEvent(返回false)->ViewGroup.onTouchEvent(返回true)->结束
方式二:Activity.dispatchTouchEvent(返回true)->ViewGroup.dispatchTouchEvent(返回true)->ViewGroup.onInterceptTouchEvent(返回true)->ViewGroup.onTouchEvent(返回true)->结束
方式三:Activity.dispatchTouchEvent(返回true)->ViewGroup.dispatchTouchEvent(返回true)->ViewGroup.onInterceptTouchEvent(返回false)->View.dispatchTouchEvent(返回true或false)->View.onTouchEvent(返回false)->ViewGroup.onTouchEvent(返回true)->结束

Activity响应
Activity.dispatchTouchEvent(返回false)->Activity.onTouchEvent(返回true)->结束

更详细的生命周期见下图所示:

TouchEvent

下面是触摸事件的常用方法:
getAction : 获取当前的动作
getX : 获取当前在控件内部的相对坐标X
getY : 获取当前在控件内部的相对坐标Y
getRawX : 获取当前在屏幕上的相对坐标X
getRawY : 获取当前在屏幕上的相对坐标Y
getEventTime : 获取当前的事件时间

手势检测GestureDetector

由于在onTouchEvent中判断用户手势的真实想法很不容易,因此Android提供了GestureDetector检测器来帮助我们识别手势。借助于GestureDetector,可以在大多数场合下辨别出常用的几个手势事件,如点击、长按、翻页等等。下面是GestureDetector的相关方法:

构造函数 : GestureDetector(Context context, OnGestureListener listener)
监听器类名 : OnGestureListener
设置监听器的方法,先给指定控件注册触摸监听器,然后在触摸方法onTouch中由GestureDetector接管触摸事件 :

 private ScrollTextView tv_rough;private GestureDetector mGesture;tv_rough = (ScrollTextView) findViewById(R.id.tv_rough);tv_rough.setOnTouchListener(this);mGesture = new GestureDetector(this, this);@Overridepublic boolean onTouch(View v, MotionEvent event) {return mGesture.onTouchEvent(event);}

另外,也可在当前视图或当前Activity中重写onTouchEvent方法,在该方法中由GestureDetector接管触摸事件。

监听器需要重写的方法 : 
onDown : 在用户按下时调用
onShowPress : 已按下但还未滑动或松开时调用,通常用于pressed状态时的高亮显示
onSingleTapUp : 在用户轻点一下再弹起时调用,通常用于点击事件
onScroll : 在用户滑动过程中调用
onLongPress : 在用户长按时调用,通常用于长按事件
onFling : 在用户飞快掠出一段距离时调用,通常用于翻页事件

滑动冲突的处理

app功能多起来之后,页面上有多个控件是可以滑动的,比如说ScrollView、下拉刷新、ViewFlipper、ViewPager等等,有的需要处理上下滑动手势,有的需要处理左右滑动手势。这样多个控件争相响应同一个手势事件,就会产生滑动冲突,如果没处理好冲突,页面上的某些控件便无法正常使用。避免滑动冲突的处理办法,主要有以下三个:

1、对不同的手势事件,要返回正确的布尔值。
手势监听器OnGestureListener需要重写的方法中,onDown、onScroll、onSingleTapUp、onFling这四个方法得返回布尔值,返回true表示其他事件仍需响应,返回false表示其他事件无需响应。一般情况下,onDown和onScroll要返回true,因为这两个方法尚无法构成具体的事件意图;而onSingleTapUp和onFling要返回false,因为onSingleTapUp表明了此次是点击事件,onFling表明了此次是翻页事件。

2、在底层控件中,如果当前手势还未处理完成,那么必须阻止上级视图的手势拦截。requestDisallowInterceptTouchEvent就是底层控件用来通知上级视图是否拦截的方法,参数输入true告知上级不要拦截,输入false告知上级可以拦截。下面示例代码演示了这么一个意图:当用户按下或者滑动时,当前控件需要响应手势事件,请上级视图不要拦截手势;当用户松开或取消时,当前控件已经处理完毕,允许上级视图拦截手势。

@Override
public boolean onTouch(View v, MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_DOWN:case MotionEvent.ACTION_MOVE:getParent().requestDisallowInterceptTouchEvent(true);...//响应手势break;case MotionEvent.ACTION_UP:case MotionEvent.ACTION_CANCEL:...//处理完毕getParent().requestDisallowInterceptTouchEvent(false);break;}
}

3、上级视图先在onInterceptTouchEvent方法中拦截手势,对手势事件进行筛选,如果需要上级处理,则返回true,表示我拦截了自己处理;如果无需上级处理,则返回false,表示我不要了给你用吧。下面示例代码演示了ScrollView拦截垂直滑动而放过水平滑动的功能:

import android.annotation.SuppressLint;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.ScrollView;@SuppressLint("ClickableViewAccessibility")
public class CustomScrollView extends ScrollView {private final static String TAG = "CustomScrollView";private float mOffsetX;private float mOffsetY;private float mLastPosX;private float mLastPosY;public CustomScrollView(Context context) {super(context);}public CustomScrollView(Context context, AttributeSet attr) {super(context, attr);setFadingEdgeLength(0);}@Overridepublic boolean dispatchTouchEvent(MotionEvent event) {return super.dispatchTouchEvent(event);}@Overridepublic boolean onTouchEvent(MotionEvent event) {return super.onTouchEvent(event);}@Overridepublic boolean onInterceptTouchEvent(MotionEvent event) {Log.d(TAG, "onInterceptTouchEvent");boolean result = false;switch (event.getAction()) {case MotionEvent.ACTION_DOWN:mOffsetX = 0.0F;mOffsetY = 0.0F;mLastPosX = event.getX();mLastPosY = event.getY();result = super.onInterceptTouchEvent(event); // false传给子控件break;default:float thisPosX = event.getX();float thisPosY = event.getY();mOffsetX += Math.abs(thisPosX - mLastPosX); // x轴偏差mOffsetY += Math.abs(thisPosY - mLastPosY); // y轴偏差mLastPosX = thisPosX;mLastPosY = thisPosY;if (mOffsetX < 3 && mOffsetY < 3) {result = false; // false传给子控件(点击事件)} else if (mOffsetX < mOffsetY) {result = true; // true不传给子控件(垂直滑动)} else {result = false; // false传给子控件}break;}return result;}
}

弹性滑动

滑动计算器Scroller

Scroller是Android用于计算滑动参数的辅助类,常用方法如下:
startScroll : 设置开始滑动的参数,包括起始的xy坐标、xy偏移量,另一个重载的方法还可以设置滑动的持续时间。
computeScrollOffset : 计算滑动偏移量。返回值可判断滑动是否结束,返回fasle表示滑动结束,返回true表示还在滑动当中。
getCurrX : 获得当前的X坐标
getCurrY : 获得当前的Y坐标
getDuration : 获得滑动的持续时间
forceFinished : 强行停止滑动
isFinished : 判断滑动是否结束。返回fasle表示还未结束,返回true表示滑动结束。该方法与computeScrollOffset的区别在于:1、computeScrollOffset内部还有计算偏移量,而isFinished只返回标志不做其他处理;2、computeScrollOffset返回fasle表示滑动结束,而isFinished返回true表示滑动结束。

View的滑动方法

虽然Scroller提供了滑动的相关计算函数,但是Scroller本身并不能直接滑动控件。因为Scroller只是个运算模拟器,根据时间的流逝计算xy坐标,所以我们必须调用控件自身的滑动方法,才能真正让控件动起来。View类中操纵滑动的方法有两个:
scrollTo : 将控件滑动到指定坐标位置
scrollBy : 将控件滑动指定偏移量。查看源码会发现scrollBy内部就是调用scrollTo,当然得先把当前坐标加上偏移量,从而得到滑动后的绝对坐标。

视图滑动例子

下面是一个简单滑动TextView的效果图:

<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">ScrollTextView控件的源码如下:</span>
import android.content.Context;
import android.util.AttributeSet;
import android.widget.Scroller;
import android.widget.TextView;public class ScrollTextView extends TextView {private static final String TAG = "ScrollTextView";private Scroller mScroller;public ScrollTextView(Context context, AttributeSet attrs) {super(context, attrs);mScroller = new Scroller(context);}public void smoothScrollTo(int fx, int fy) {int dx = fx - mScroller.getFinalX();int dy = fy - mScroller.getFinalY();smoothScrollBy(dx, dy);}public void smoothScrollBy(int dx, int dy) {//设置滚动偏移量,注意正数是往左滚往上滚,负数才是往右滚往下滚mScroller.startScroll(mScroller.getFinalX(), mScroller.getFinalY(), -dx, -dy);//调用invalidate()才能保证computeScroll()会被调用invalidate();}@Overridepublic void computeScroll() {//先判断mScroller滚动是否完成if (mScroller.computeScrollOffset()) {//这里调用View的scrollTo()完成实际的滚动scrollTo(mScroller.getCurrX(), mScroller.getCurrY());//刷新页面postInvalidate();}super.computeScroll();}
}

通过onFling调用滑动控件的代码如下:

import com.example.exmgesture.widget.ScrollTextView;import android.annotation.SuppressLint;
import android.app.Activity;
import android.os.Bundle;
import android.view.GestureDetector;
import android.view.GestureDetector.OnGestureListener;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;@SuppressLint("ClickableViewAccessibility")
public class RoughActivity extends Activity implements OnTouchListener,OnGestureListener {private final static String TAG = "RoughActivity";private ScrollTextView tv_rough;private GestureDetector mGesture;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_rough);tv_rough = (ScrollTextView) findViewById(R.id.tv_rough);tv_rough.setOnTouchListener(this);mGesture = new GestureDetector(this, this);}@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {// TODO Auto-generated method stubreturn super.dispatchTouchEvent(ev);}@Overridepublic boolean onTouch(View v, MotionEvent event) {return mGesture.onTouchEvent(event);}@Overridepublic boolean onDown(MotionEvent e) {return true;}@Overridepublic void onShowPress(MotionEvent e) {}@Overridepublic boolean onSingleTapUp(MotionEvent e) {tv_rough.setText("您轻轻点击了一下");return false;}@Overridepublic boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {return true;}@Overridepublic void onLongPress(MotionEvent e) {}@Overridepublic boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {tv_rough.setText("您拖动我啦");float offsetX = e2.getRawX() - e1.getRawX();float offsetY = e2.getRawY() - e1.getRawY();tv_rough.smoothScrollBy((int)offsetX, (int)offsetY);return false;}}

点此查看Android开发笔记的完整目录

Android开发笔记(四十五)手势事件相关推荐

  1. 【Visual C++】游戏开发笔记四十五 浅墨DirectX教程十三 深度测试和Z缓存专场

    本系列文章由zhmxy555(毛星云)编写,转载请注明出处. 文章链接: http://blog.csdn.net/zhmxy555/article/details/8607864 作者:毛星云(浅墨 ...

  2. Android开发笔记(十五)淡入淡出动画TransitionDrawable

    说到淡入淡出动画,可能大家会想到补间动画里面的AlphaAnimation,不过这个深浅动画只能对透明度做渐变效果,也就是只能对一个图形做深浅的颜色变换.如果我们想要从A图片逐渐变为B图片,也就是要实 ...

  3. Android开发笔记(九十五)自定义Drawable

    Drawable Bitmap是Android对图像的定义描述,而Drawable则是对图像的展现描述,在View视图中显示图像都是通过Drawable来实现的.其中有关Bitmap的介绍参见< ...

  4. 【Visual C++】游戏开发笔记四十六 浅墨DirectX教程十四 模板测试与镜面特效专场

    本系列文章由zhmxy555(毛星云)编写,转载请注明出处.   文章链接: http://blog.csdn.net/zhmxy555/article/details/8632184 作者:毛星云( ...

  5. Android开发笔记(一百五十七)使用OpenGL实现翻书动画

    上一篇文章介绍了如何通过纹理渲染绘制地球仪,当然OpenGL的三维图形处理能力是很强大的,只要善于利用OpenGL,就能很方便地虚拟各种现实生活中的动画效果.本文再来谈谈使用OpenGL实现浏览电子书 ...

  6. Android开发笔记(一百五十四)OpenGL的画笔工具GL10

    上一篇文章介绍了OpenGL绘制三维图形的流程,其实没有传说中的那么玄乎,只要放平常心把它当作一个普通控件就好了,接下来继续介绍OpenGL具体的绘图操作,这项工作得靠三维图形的画笔GL10来完成了. ...

  7. Android开发笔记(一百五十五)利用GL10描绘点、线、面

    上一篇文章介绍了GL10的常用方法,包括如何设置颜色.如何指定坐标系.如何调整镜头参数.如何挪动观测方位等等,不过这些方法只是绘图前的准备工作,真正描绘点.线.面的制图工作并未涉及,那么本文就来谈谈如 ...

  8. Android开发笔记(一百五十二)H5通过WebView上传图片

    上一篇文章介绍了WebView与JS之间的数据交互,其实就是把字符串传来传去,这对文本格式的信息传输来说倒还凑合,倘若要传输图片信息就不管用了.所以,要想让h5网页支持从手机上传图片,还得另外想办法, ...

  9. Android开发笔记(一百五十一)WebView与JavaScript交互的四种形式

    WebView如果作为简单的网页浏览器,对于一般的浏览行为来说,已经足够了.可做为企业开发者,你的App通常要嵌入自家公司的网页,如此一来,还得考虑App与Web之间的消息传递,这就涉及到App的原生 ...

  10. Android开发笔记(十四)圆弧进度动画CircleAnimation

    一个好看的APP,都有不少精致的动画效果.熟练运用各种动画技术,可让我们的APP灼灼生辉.Android在技术上把动画分为了三类,分别是帧动画FrameAnimation.补间动画TweenAnima ...

最新文章

  1. 再次携号转网_“携号转网”日期再次确定!这三个开头的号码,可以优先办理转网...
  2. 「 每日一练,快乐水题 」717. 1比特与2比特字符
  3. 牛客题霸 [拼接所有的字符串产生字典序最小的字符串] C++题解/答案
  4. OpenVINO Inference Engine之GetAvailableDevices
  5. ROR中简单的数据操作
  6. 凸函数、凸规划的定义及学习
  7. C# Json转list List转json
  8. 计算机打不开sai文件夹,无法运行 SAI2 的解决办法
  9. 计算机系统应用软件的核心是什么,计算机系统软件的核心是什么?
  10. Jenkins - Publish Over SSH
  11. Mysql——DQL(查询语句语法、格式、举例)以及全部数据库源码,复制就可实现全部功能
  12. 计算机设计辅助 CAD 试题汇编,计算机辅助设计试题汇编-第二单元
  13. php excel 进度,在php中生成Excel文件时显示进度条
  14. NTVDM CPU 遇到无效的指令的解决方法大全
  15. 敦煌日历2023 | 千年流光,风雅不绝
  16. cocos2d-x-3.3-023-仿微信飞机大战-总体分析和建模
  17. 3.Spark的安装(华为云学习笔记,Spark编程基础,大数据)
  18. 系统盘重装linux,制作linuxu盘启动盘电脑系统盘重装启动
  19. 基于git hooks的前端代码质量控制解决方案
  20. 电源php38电路,8脚电源芯片TB6806的电路图

热门文章

  1. leetcode每日一题:406.queue-reconstruction-by-height(根据升高重建队列)
  2. Leetcode每日一题:剑指offer22.lian-biao-zhong-dao-shu-di-kge-jie-dian-lcof(链表中倒数第k个节点)
  3. Algorithm:贪心策略之区间覆盖问题
  4. 函数的基本知识,定义,调用,参数,返回值,说明文档,函数的嵌套及应用
  5. SpringBoot指南(一)——SpringBoot入门
  6. Android技能树 — 网络小结(6)之 OkHttp超超超超超超超详细解析
  7. 自动化测试学习之路--json、dom编程
  8. python接口自动化(四十)- logger 日志 - 下(超详解)
  9. jenkins手把手教你从入门到放弃03-安装Jenkins时web界面出现该jenkins实例似乎已离线
  10. tasker运行java_Tasker 打开桌面快捷方式(以微信公众号为例)[No Root]