很多时候,利用触摸屏的Fling、Scroll等Gesture(手势)操作来操作会使得应用程序的用户体验大大提升,比如用Scroll手势在 浏览器中滚屏,用Fling在阅读器中翻页等。在Android系统中,手势的识别是通过 GestureDetector.OnGestureListener接口来实现的,不过William翻遍了Android的官方文档也没有找到一个相 关的例子,API Demo中的TouchPaint也仅仅是提到了onTouch事件的处理,没有涉及到手势。Android Developer讨论组里也有不少人有和我类似的问题,结合他们提到的方法和我所做的实验,我将给大家简单讲述一下Android中手势识别的实现。

我们先来明确一些概念,首先,Android的事件处理机制是基于Listener(监听器)来实现的,比我们今天所说的触摸屏相关的事件,就是通 过onTouchListener。其次,所有View的子类都可以通过setOnTouchListener()、 setOnKeyListener()等方法来添加对某一类事件的监听器。第三,Listener一般会以Interface(接口)的方式来提供,其中 包含一个或多个abstract(抽象)方法,我们需要实现这些方法来完成onTouch()、onKey()等等的操作。这样,当我们给某个view设 置了事件Listener,并实现了其中的抽象方法以后,程序便可以在特定的事件被dispatch到该view的时候,通过callbakc函数给予适 当的响应。
看一个简单的例子,就用最简单的TextView来说明(事实上和ADT中生成的skeleton没有什么区别)。

  1. public class GestureTest extends Activity implements OnTouchListener{
  2. @Override
  3. protected void onCreate(Bundle savedInstanceState) {
  4. super.onCreate(savedInstanceState);
  5. setContentView(R.layout.main);
  6. // init TextView
  7. TextView tv = (TextView) findViewById(R.id.page);
  8. // set OnTouchListener on TextView
  9. tv.setOnTouchListener(this);
  10. // show some text
  11. tv.setText(R.string.text);
  12. }
  13. @Override
  14. public boolean onTouch(View v, MotionEvent event) {
  15. Toast.makeText(this, "onTouch", Toast.LENGTH_SHORT).show();
  16. return false;
  17. }

我们给TextView的实例tv设定了一个onTouchListener,因为GestureTest类实现了OnTouchListener 接口,所以简单的给一个this作为参数即可。onTouch方法则是实现了OnTouchListener中的抽象方法,我们只要在这里添加逻辑代码即 可在用户触摸屏幕时做出响应,就像我们这里所做的——打出一个提示信息。

这里,我们可以通过MotionEvent的getAction()方法来获取Touch事件的类型,包括 ACTION_DOWN, ACTION_MOVE, ACTION_UP, 和ACTION_CANCEL。ACTION_DOWN是指按下触摸屏,ACTION_MOVE是指按下触摸屏后移动受力点,ACTION_UP则是指松 开触摸屏,ACTION_CANCEL不会由用户直接触发(所以不在今天的讨论范围,请参考ViewGroup.onInterceptTouchEvent(MotionEvent))。借助对于用户不同操作的判断,结合getRawX()、getRawY()、getX()和getY()等方法来获取坐标后,我们可以实现诸如拖动某一个按钮,拖动滚动条等功能。待机可以看看MotionEvent类的文档,另外也可以看考TouchPaint例子。
回到今天所要说的重点,当我们捕捉到Touch操作的时候,如何识别出用户的Gesture?这里我们需要GestureDetector.OnGestureListener接口的帮助,于是我们的GestureTest类就变成了这个样子。

  1. public class GestureTest extends Activity implements OnTouchListener,
  2. OnGestureListener {
  3. ....
  4. }

随后,在onTouch()方法中,我们调用GestureDetector的onTouchEvent()方法,将捕捉到的MotionEvent交给 GestureDetector 来分析是否有合适的callback函数来处理用户的手势。

  1. @Override
  2. public boolean onTouch(View v, MotionEvent event) {
  3. // OnGestureListener will analyzes the given motion event
  4. return mGestureDetector.onTouchEvent(event);
  5. }

接下来,我们实现了以下6个抽象方法,其中最有用的当然是onFling()、onScroll()和onLongPress()了。我已经把每一个方法代表的手势的意思写在了注释里,大家看一下就明白了。
// 用户轻触触摸屏,由1个MotionEvent ACTION_DOWN触发

  1. @Override
  2. public boolean onDown(MotionEvent e) {
  3. // TODO Auto-generated method stub
  4. Toast.makeText(this, "onDown", Toast.LENGTH_SHORT).show();
  5. return false;
  6. }
  7. // 用户轻触触摸屏,尚未松开或拖动,由一个1个MotionEvent ACTION_DOWN触发
  8. // 注意和onDown()的区别,强调的是没有松开或者拖动的状态
  9. @Override
  10. public void onShowPress(MotionEvent e) {
  11. // TODO Auto-generated method stub
  12. }

// 用户(轻触触摸屏后)松开,由一个1个MotionEvent ACTION_UP触发

  1. @Override
  2. public boolean onSingleTapUp(MotionEvent e) {
  3. // TODO Auto-generated method stub
  4. return false;
  5. }

// 用户按下触摸屏、快速移动后松开,由1个MotionEvent ACTION_DOWN, 多个ACTION_MOVE, 1个ACTION_UP触发

  1. @Override
  2. public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
  3. float velocityY) {
  4. // TODO Auto-generated method stub
  5. return false;
  6. }
  7. // 用户长按触摸屏,由多个MotionEvent ACTION_DOWN触发
  8. @Override
  9. public void onLongPress(MotionEvent e) {
  10. // TODO Auto-generated method stub
  11. }
  12. // 用户按下触摸屏,并拖动,由1个MotionEvent ACTION_DOWN, 多个ACTION_MOVE触发
  13. @Override
  14. public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
  15. float distanceY) {
  16. // TODO Auto-generated method stub
  17. return false;
  18. }

我们来试着做一个onFling()事件的处理吧,onFling()方法中每一个参数的意义我写在注释中了,需要注意的是Fling事件的处理代 码中,除了第一个触发Fling的ACTION_DOWN和最后一个ACTION_MOVE中包含的坐标等信息外,我们还可以根据用户在X轴或者Y轴上的 移动速度作为条件。比如下面的代码中我们就在用户移动超过100个像素,且X轴上每秒的移动速度大于200像素时才进行处理。

  1. @Override
  2. public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
  3. float velocityY) {
  4. // 参数解释:
  5. // e1:第1个ACTION_DOWN MotionEvent
  6. // e2:最后一个ACTION_MOVE MotionEvent
  7. // velocityX:X轴上的移动速度,像素/秒
  8. // velocityY:Y轴上的移动速度,像素/秒
  9. // 触发条件 :
  10. // X轴的坐标位移大于FLING_MIN_DISTANCE,且移动速度大于FLING_MIN_VELOCITY个像素/秒
  11. if (e1.getX() - e2.getX() > FLING_MIN_DISTANCE
  12. && Math.abs(velocityX) > FLING_MIN_VELOCITY) {
  13. // Fling left
  14. Toast.makeText(this, "Fling Left", Toast.LENGTH_SHORT).show();
  15. } else if (e2.getX() - e1.getX() > FLING_MIN_DISTANCE
  16. && Math.abs(velocityX) > FLING_MIN_VELOCITY) {
  17. // Fling right
  18. Toast.makeText(this, "Fling Right", Toast.LENGTH_SHORT).show();
  19. }
  20. return false;
  21. }

问题是,这个时候如果我们尝试去运行程序,你会发现我们根本得不到想要的结果,跟踪代码的执行的会发现onFling()事件一直就没有被捕捉到。这正是一开始困扰我的问题,这到底是为什么呢?
我在讨论组的Gesture detection这个帖子里找到了答案,即我们需要在onCreate中tv.setOnTouchListener(this);之后添加如下一句代码。
tv.setLongClickable(true);只有这样,view才能够处理不同于Tap(轻触)的hold(即ACTION_MOVE,或者多个ACTION_DOWN),我们同样可以通过layout定义中的android:longClickable来做到这一点。
这次遇到的这个问题和上次MapView中setOnKeyListener遇到的问题挺类似,其实都是对SDK的了解不够全面,遇到了一次记住了就好。不过话说回来,Google在文档方面确实需要加强了,起码可以在OnGestureListener中说明需要满足那些条件才可以保证手势被正确识别。
Android触摸屏手势识别就简单的介绍到这里了,希望对大家有用。运行的效果大家可以点击下载Demo的sourcecode来体验一下。

简述Android触摸屏手势识别的更多相关文章

  1. Android 多点手势识别详解

    google 提供的API中,有个类,大家都很熟悉,GestureDetector.使用它,我们可以识别用户通常会用的手势.但是,这个类不支持多点触摸(可能 google认为没有人会在几个手指都在屏幕 ...

  2. 实现在Android 多点手势识别

    google 提供的API中,有个类,大家都很熟悉,GestureDetector.使用它,我们可以识别用户通常会用的手势.但是,这个类不支持多点触摸(可能 google认为没有人会在几个手指都在屏幕 ...

  3. Android触摸屏配置调试

    前几天搞乐蛙时,进入后是鼠标模式,好坑爹的模式有木有~~ 但是大蛋给出了解决方法,我不怕不怕啦~让我们向大牛致敬!!! 首先输入Command查看你的input配置~ adb shell dumpsy ...

  4. 简述Android系统内存不足时候,内存回收机制

    当Android系统的内存不足时,会根据以下的内存回收规则来回收内存: 1.先回收与其他Activity或Service/Intent Receiver无关的进程(即优先回收独立的Activity) ...

  5. 理解Android的手势识别

    对于触摸屏,其原生的消息无非按下.抬起.移动这几种,我们只需要简单重载onTouch或者设置触摸侦听器setOnTouchListener即可进行处理.不过,为了提高我们的APP的用户体验,有时候我们 ...

  6. Android手势识别的发展

    在播放器.与手势识别.所以,看看今天的我们Android手势识别. 首先,我们需要站在巨人的肩膀上.有些人举了个例子和说明. 第一章: http://www.2cto.com/kf/201110/10 ...

  7. 【转】 71道经典Android面试题和答案,重要知识点都包含了

    ,,面试题1.        下列哪些语句关于内存回收的说明是正确的? (b ) A. 程序员必须创建一个线程来释放内存  B.内存回收程序负责释放无用内存   C.内存回收程序允许程序员直接释放内存 ...

  8. Android 学习资料收集

    收集整理这份资料灵感来自于 trip_to_iOS, 征得同意引用了该资料的开头描述 收集整理这份资料主要帮助初学者学习 Android 开发, 希望能快速帮助到他们快速入门, 找到适合自己学习资料, ...

  9. Android面试总结 (转)

    1. 下列哪些语句关于内存回收的说明是正确的? (b) A. 程序员必须创建一个线程来释放内存 B. 内存回收程序负责释放无用内存 C. 内存回收程序允许程序员直接释放内存 D. 内存回收程序可以在指 ...

随机推荐

  1. tomcat怎么运行servlet程序

    新建一个web project 取名 myproject 在myproject 新建一个继承了HttpServlet 的类 MyServlet 重写HttpServlet 的 dopost doget ...

  2. @JVM垃圾回收调优方法

    JVM调优工具 Jconsole,jProfile,VisualVM Jconsole:jdk自带,功能简单,但是可以在系统有一定负荷的情况下使用.对垃圾回收算法有很详细的跟踪. JProfiler: ...

  3. Objective-C:除数为0的情况下异常的处理(检测、抛出、捕捉、处理)

    // DivTest.h // 异常的处理 // // Created by ma c on 15/8/11. // Copyright (c) 2015年. All rights reserved. ...

  4. 同步FIFO的设计

    module scfifo #( , ) ( input clk, input rst_n, input wren, input rden, :] din, :] dout, output full, ...

  5. mysql搭建及数据迁移教程

    1.如果jumbo不存在,先安装jumbo 参考  http://hetu.baidu.com/api/tool/show?toolId=174: bash -c "$( curl  htt ...

  6. Bridge 桥接模式 MD

    桥接模式 简介 将抽象部分与实现部分分离,使它们都可以独立的变化. 业务抽象角色引用业务实现角色,或者说业务抽象角色的部分实现是由业务实现角色完成的 Bridge模式基于类的最小设计原则,通过使用封装 ...

  7. Android 跳转到系统应用管理

    Intent i = new Intent("android.settings.APPLICATION_DETAILS_SETTINGS");String pkg = " ...

  8. Leaf - 一个由 Go 语言编写的开发效率和执行效率并重的开源游戏服务器框架

    转自:https://toutiao.io/posts/0l7l7n/preview Leaf 游戏服务器框架简介 Leaf 是一个由 Go 语言(golang)编写的开发效率和执行效率并重的开源游戏 ...

  9. 【Tip】如何在chrome浏览器中查看网页的Header

    步骤:打开“开发者工具”,点Network标签,然后刷新网页,选择Name中的第一项,再点右边的Headers,就出来了. 似乎有点复杂,配合下面的图看就一目了然了.

  10. AsyncTask 和 Thread 区别

    一.AsyncTask是封装好的线程池,比起Thread+Handler的方式,AsyncTask在操作UI线程上更方便,因为onPreExecute().onPostExecute()及更新UI方法 ...