Android开发多手指触控事件处理

目录
  • 正文
  • 触摸事件
    • 事件类型
    • 手指索引
    • 手指ID
  • 多手指事件处理
  • 支持多手指的滑动控件
  • 总结

正文

多点触控,一直以来都是事件处理中比较晦涩的一个话题。其一是因为它的机制与我们常规思维有点不同,基二是因为我们用的比较少。那么作为一个有点追求的Android开发者,我们必须要掌握这些,这样可以提高代码的格调。

写这篇文章还是有点难度的,我反反复复修改了好多次,真的是删了又改,改了又删,只为把多点触控讲得明明白白。最后我决定把本文分为三部分进行讲解

  • 讲解多手指触摸的一些关键性概念。虽然这部分概念非常抽象,并且也无法用源码去解释(源码在底层),但是这部分概念是最关键的。如果你想掌握多点触控,必须理解并记住这些概念。
  • 讲解多手指触摸事件在ViewGroup是如何分发处理的。因为只有理解了这个,我们才能写出正确的多手指触摸事件的代码。
  • 通过一个例子讲解如何在滑动控件中支持多手指滑动。

好了,废话不多说了,让我们开始这次愉快的旅程吧。

触摸事件

首先我们从MotionEvent.getAction()讲起吧。很多地方把这个方法的返回值叫做触摸事件的类型,其实这个叫法是错误的,它的返回值不仅包含事件的类型,还包含手指的索引值。

假如MotionEvent.getAction()返回一个值,用十六进制表示为0X0100,这个值的高八位的值是01,用二进制表示就是0000 0001,它表示手指的索引,而低八位的值是00,用二进制表示就是0000 0000,它才表示事件的类型。

事件类型

那么我们怎么获取这个事件的类型呢,我想大家应该都想到了事件类型的掩码,MotionEvent.getActionMask()就是通过事件类型掩码获取事件类型的。

那么,为什么大家一直说MotionEvent.getAction()返回的就是事件类型呢?因为这是一个巧合,对于单手指操作,MotionEvent.getAction()的返回值中,高八位的索引值是0,因此它正好与事件类型的值一样。

对于支持多手指操作,MotionEvent.getAction()返回值的事件索引就不再一直是0了,它会随着手指的增加而改变,因此MotionEvent.getActionMask()才是返回事件类型的正确操作。

那么我们来看下,多手指触摸情况下所支持的事件类型

事件类型 事件说明
ACTION_DOWN 第一个手指按下
ACTION_POINTER_DOWN 其它手指按下
ACTION_MOVE 手指移动
ACTION_POINTER_UP 不是最后一个手指抬起
ACTION_UP 最后一个手指抬起

我们通过一个例子来解释下这几个事件的触发时机。

  • 当第一个手指按下的时候,此时触发的事件类型是ACTION_DOWN
  • 当有第二个,甚至更多的手指按下的时候就会触发ACTION_POINTER_DOWN事件。
  • 当任意一个手指滑动的时候,就会触发ACTION_MOVE事件。
  • 当不是最后一个手指抬起时,会触发ACTION_POINTER_UP事件。
  • 当最后一个手指择时,会触发ACTION_UP事件。

手指索引

MotionEvent.getAction()返回值中还有个神秘的手指索引,它可以通过MotionEvent.getActionIndex()获取。那么它有啥用呢?对于单手指,没有任何叼用,但是对于多手指,那它的作用就大了,这可以获取手指的触摸事件的信息,例如MotionEvent.getX(int pointerIndex)获取X坐标值。

手指ID

刚才在事件类型部分,不知大家有没有注意到,ACTION_MOVE是不区分手指的,那么我们怎么知道是哪个手指触发了ACTION_MOVE的呢?你是不是第一时间想到了手指索引?请你放弃这个想法!

人可以通过眼睛观察到手指的按下顺序,但是硬件和软件是无法做到的,而手指的索引在事件中可能会改变的。那么一个严峻的问题来了,如何跟踪一个手指呢?用PointerId!至于原理是什么,我也不太清楚。

那么怎么获取一个手指的PointerId呢?当遇到ACTION_DOWNACTION_POINTER_DOWN的时候,通过如下代码获取

// 获取手指的索引
int pointerIndex = motionEvent.getActionIndex();
// 通过手指索引获取手指ID
int pointerId = motionEvent.getPointerId(pointerIndex);

在前面的手指索引部分,我们知道通过索引可能获取事件的信息,例如坐标值,如下代码

        // 获取手指索引
        int pointerIndex = event.getActionIndex();
        // 获取坐标值
        float x = event.getX(pointerIndex);
        float y = event.getY(pointerIndex);

然而在ACTION_MOVE事件中,我们要获取某个手指的坐标值,怎么办呢?首先我们要保存在ACTION_DOWNACTION_POINTER_DOWN中保存手指PointerId值,然后通过这个PointerId调用MotionEvent.findPointerIndex(int pointerId)获取手指索引值,最后通过索引值获取坐标值,代码如下

case MotionEvent.ACTION_MOVE:
    // 根据PointerId获取某个手指的索引
    int pointerIndex = event.findPointerIndex(mPrimaryPointerId);
    // 获取坐标值
    float x = event.getX(pointerIndex);
    float y = event.getY(pointerIndex);
    break;

多手指事件处理

对于多手指触摸事件呢,其实比单手指只是多出了ACTION_POINTER_DOWNACTION_POINTER_UP两个事件,那么这两个事件在ViewGroup中是如何分发处理的呢?如果要用源码来分析呢,这篇文章的篇幅就太长了,但是呢,恰巧这两个事件与ACTION_MOVE的分发处理流程是一样的。如果你还不懂ACTION_MOVE是如何分发处理的,可以参考我之前写的ViewGroup事件分发和处理源码分析。

支持多手指的滑动控件

掌握了前面的基础知识后,我们现在就又到了喜闻乐见的实战环节,在这一部分,我们要使一个滑动控件支持多手指滑动。

在实现这个功能之前,我们要明确实现思路

  • 只有主手指能控制控件的滑动。
  • 如果有手指按下,就认为这个手指是主手指。
  • 当有手指抬起时,如果是主手指,那就必须重新找一个手指作为新的主手指。

首先我们需要一个可滑动的控件,这个控件取自手把手教你如何写事件处理的代码这篇文章的滑动控件,并且我需要大家对这篇文章的讲的事件处理能理解清楚,因为下面写的代码,我不会去解释这些基本知识。

我们前面说过,ACTION_POINTER_DOWNACTION_POINTER_UP的处理流程是和ACTION_MOVE一样的,那么要不要截断呢?那就要看当遇到这两个事件的时候我们要做什么。

根据实现思路中的第二条,如果有手指按下,就认为是主手指,因此在处理ACTION_POINTER_DOWN时候只是简单获取手指的PointerId,然后保存为主手指即可,所以不需要去截断。

根据实现思路的第三条,如果抬起的是主手指,那么就要重新找一个替代的手指作为主手指,所以也不需要去截断。

那么,在onInterceptTouchEvent()onTouchEvent()的处理方式是一样的,首先我们看下保存主手指的代码如下

    public boolean onInterceptTouchEvent(MotionEvent ev) {
        switch (ev.getActionMasked()) {
            case MotionEvent.ACTION_DOWN:
                onPrimaryPointerDown(ev);
                break;
            case MotionEvent.ACTION_POINTER_DOWN:
                onPrimaryPointerDown(ev);
                break;
        }
        return super.onInterceptTouchEvent(ev);
    }
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getActionMasked()) {
            case MotionEvent.ACTION_POINTER_DOWN:
                onPrimaryPointerDown(event);
                break;
        }
        return true;
    }
    /**
     * 当有新手指按下的时候,就认作是主手指,于是重新记录按下点的坐标,以及更新最新的X坐标。
     *
     * @param event 触摸事件。
     */
    private void onPrimaryPointerDown(MotionEvent event) {
        // 获取手指索引
        int pointerIndex = event.getActionIndex();
        // 通过手指索引获取手指ID
        mPrimaryPointerId = event.getPointerId(pointerIndex);
        // 通过手指索引保存坐标值
        mLastX = mStartX = event.getX(pointerIndex);
        mStartY = event.getY(pointerIndex);
    }

然后,我们来看下当有主手指抬起时,如何寻找替代的主手指

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        switch (ev.getActionMasked()) {
            case MotionEvent.ACTION_POINTER_UP:
                onPrimaryPointerUp(ev);
                break;
        }
        return super.onInterceptTouchEvent(ev);
    }
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getActionMasked()) {
            case MotionEvent.ACTION_POINTER_UP:
                onPrimaryPointerUp(event);
                break;
        }
        return true;
    }
    /**
     * 当主手指抬起时,寻找一个新的主手指,并且更新最新的X坐标值为新主手指的X坐标值。
     *
     * @param event
     */
    private void onPrimaryPointerUp(MotionEvent event) {
        // 获取抬起手指的索引值
        int pointerIndex = event.getActionIndex();
        // 通过索引值,获取抬起手指的ID
        int pointerId = event.getPointerId(pointerIndex);
        // 如果抬起手指的ID等于主手指的ID
        if (pointerId == mPrimaryPointerId) {
            // 寻找一个已经存在的手指索引
            final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
            // 通过新的手指索引获取手指ID
            mPrimaryPointerId = event.getPointerId(newPointerIndex);
            // 通过新的手指索引获取坐标值
            mLastX = event.getX(newPointerIndex);
        }
    }

把这些问题解决后,那么在处理滑动的代码的时候,就要通过这个主手指ID来获取坐标值,然后根据这些坐标值来决定滑动,我这里用部分代码来演示下

    public boolean onInterceptTouchEvent(MotionEvent ev) {
        switch (ev.getActionMasked()) {
            case MotionEvent.ACTION_MOVE:
                // 获取主手指的坐标值
                PointF primaryPointerPoint = getPrimaryPointerPoint(ev);
                // 根据坐标值判断是否需要滑动
                if (canScroll(primaryPointerPoint.x, primaryPointerPoint.y)) {
                    mBeingDragged = true;
                    getParent().requestDisallowInterceptTouchEvent(true);
                    // 执行一次滑动
                    performDrag(primaryPointerPoint.x);
                    mLastX = primaryPointerPoint.x;
                    // 可以滑动就截断事件
                    return true;
                }
                break;
        }
        return super.onInterceptTouchEvent(ev);
    }
    /**
     * 获取主手指在某个事件触发时的坐标。
     *
     * @param event 触摸事件。
     * @return 如果成功,返回坐标点,否则返回null。
     */
    private PointF getPrimaryPointerPoint(MotionEvent event) {
        PointF pointF = null;
        if (mPrimaryPointerId != INVALID_POINTER_ID) {
            int pointerIndex = event.findPointerIndex(mPrimaryPointerId);
            if (pointerIndex != -1) {
                pointF = new PointF(event.getX(pointerIndex), event.getY(pointerIndex));
            }
        }
        return pointF;
    }

总结

要掌握多手指滑动,必须先得掌握其关键的概念,有了这些概念我们就可以知道事件何时触发,怎么跟踪一个手指。然后我们需要掌握多手指事件的处理流程,巧合的是,只要知道ACTION_MOVE的处理流程就明白了多手指事件的流程。最后我们要掌握为一个滑动控件添加多手指支持的实现思路。

有了这三步,基本上就可以实现一个支持多手指滑动的控件。不过请注意我的措辞,是基本上,是基本上,是基本上!

最后,我默默地留下一个github地址,供大家参考。

以上就是Android开发多手指触控事件处理的详细内容,更多关于Android多手指触控的资料请关注我们其它相关文章!

(0)

相关推荐

  • Android中父View和子view的点击事件处理问题探讨

    android中的事件类型分为按键事件和屏幕触摸事件,Touch事件是屏幕触摸事件的基础事件,有必要对它进行深入的了解. 一个最简单的屏幕触摸动作触发了一系列Touch事件:ACTION_DOWN->ACTION_MOVE->ACTION_MOVE->ACTION_MOVE...->ACTION_MOVE->ACTION_UP 当屏幕中包含一个ViewGroup,而这个ViewGroup又包含一个子view,这个时候android系统如何处理Touch事件呢?到底是ViewG

  • Android 中 EventBus 的使用之多线程事件处理

    在这一系列教程的最后一篇中,我想谈谈GR的EventBus,在处理多线程异步任务时是多么简单而有效. AsyncTask, Loader和Executor-- 拜托! Android中有很多种执行异步操作的方法(指平行于UI线程的).AsyncTask对于用户来说是最简单的一种机制,并且只需要少量的设置代码即可.然而,它的使用是有局限的,正如Android官方文档中所描述的: AsyncTask被设计成为一个工具类,在它内部包含了Thread和Handler,但它本身并不是通用线程框架的一部分.

  • Android的Touch事件处理机制介绍

    Android的Touch事件处理机制比较复杂,特别是在考虑了多点触摸以及事件拦截之后. Android的Touch事件处理分3个层面:Activity层,ViewGroup层,View层. 首先说一下Touch事件处理的几条基本规则. 如果在某个层级没有处理ACTION_DOWN事件,那么该层就再也收不到后续的Touch事件了直到下一次ACTION_DOWN事件. 说明: a.某个层级没有处理某个事件指的是它以及它的子View都没有处理该事件. b.这条规则不适用于Activity层(它是顶层

  • Android XRecyclerView最简单的item点击事件处理

    以前一直都是用PullToRefresh,后来觉得还是太out了.现在很多人都是用RecyclerView,很简单的用法,布局多样化,主要是有瀑布流.这才知道RecyclerView.LayoutManager真正的强大. 但是说要addHeaderView这个的话,RecyclerView没有实现,所以我用了XRecyclerView,其实它也是在RecyclerView的基础上再次封装的,用起来还是蛮好的. 这里说一下,正确的使用XRecyclerView点击item做事件处理的问题.其实就

  • 详解Android的两种事件处理机制

    UI编程通常都会伴随事件处理,Android也不例外,它提供了两种方式的事件处理:基于回调的事件处理和基于监听器的事件处理. 对于基于监听器的事件处理而言,主要就是为Android界面组件绑定特定的事件监听器:对于基于回调的事件处理而言,主要做法是重写Android组件特定的回调函数,Android大部分界面组件都提供了事件响应的回调函数,我们主要重写它们就行. 一 基于监听器的事件处理 相比于基于回调的事件处理,这是更具"面向对象"性质的事件处理方式.在监听器模型中,主要涉及三类对象

  • Android开发事件处理的代码如何写手摸手教程

    目录 正文 剖析事件分发的过程 ACTION_DOWN ACTION_MOVE ACTION_UP ACTION_CANCEL 完成案例代码 ACTION_DOWN ACTION_MOVE ACTION_UP ACTION_CANCEL 截断ACTION_DOWN 结束 正文 经过事件分发之View事件处理和ViewGroup事件分发和处理源码分析这两篇的的理论知识分析,我们已经大致的了解了事件的分发处理机制,但是这并不代表你就一定能写好事件处理的代码. 既然我们有了基本功,那么本文就通过一个案

  • Android事件处理的两种方式详解

    安卓提供了两种方式的事件处理:基于回调的事件处理和基于监听的事件处理. 基于监听的事件处理 基于监听的事件处理一般包含三个要素,分别是: Event Source(事件源):事件发生的场所,通常是各个组件 Event(事件):事件封装了界面组件上发生的特定事件(通常就是用户的一次操作) Event Listener(事件监听器):负责监听事件源发生的事件,并对各种事件作出相应的响应 下面使用一个简单的案例介绍按钮事件监听器 布局文件就是简单的线性布局器,上面是一个EditText,下面是一个Bu

  • Android事件分发之View事件处理关键及示例分析

    目录 目的 View处理事件的关键 View事件处理分析 View.onTouchEvent()分析 处理长按事件 处理点击事件 处理tap事件 总结 目的 网上已经有很多关于事件分发的优秀文章,为何我还要自己写?因为别人总结的毕竟都是别人的,自己亲自阅读源码不仅会让自己更懂得原理,也会让自己记得更清楚,而且也会发现另一番天地. View处理事件的关键 由于所以的控件都直接或者间接继承自View,因此View的事件分发机制就是最基础的一环,需要首先掌握其原理. 那么View的事件从哪里来的呢?当

  • Android开发多手指触控事件处理

    目录 正文 触摸事件 事件类型 手指索引 手指ID 多手指事件处理 支持多手指的滑动控件 总结 正文 多点触控,一直以来都是事件处理中比较晦涩的一个话题.其一是因为它的机制与我们常规思维有点不同,基二是因为我们用的比较少.那么作为一个有点追求的Android开发者,我们必须要掌握这些,这样可以提高代码的格调. 写这篇文章还是有点难度的,我反反复复修改了好多次,真的是删了又改,改了又删,只为把多点触控讲得明明白白.最后我决定把本文分为三部分进行讲解 讲解多手指触摸的一些关键性概念.虽然这部分概念非

  • Android编程实现两点触控功能示例

    本文实例讲述了Android编程实现两点触控功能.分享给大家供大家参考,具体如下: 下面是一个两点触控的案例代码: package com.zzj; import android.app.Activity; import android.os.Bundle; import android.view.MotionEvent; public class AndroidTestActivity extends Activity { private float x0, y0; private float

  • Android开发之瀑布流控件的实现与使用方法示例

    本文实例讲述了Android开发之瀑布流控件的实现与使用方法.分享给大家供大家参考,具体如下: public class FlowLayout extends ViewGroup { /**行里子view之间的行距离*/ public int mHorizontolSpace = Util.getDimen(R.dimen.top_padding); /**行里子view之间的垂直距离*/ public int mVerticalSpace = Util.getDimen(R.dimen.top

  • Android开发中自定义ProgressBar控件的方法示例

    本文实例讲述了Android开发中自定义ProgressBar控件的方法.分享给大家供大家参考,具体如下: 很简单,首先加载Drawable,在onMeasure设置好其区域大小, 然后使用canvas.clipRect绘图 public class ProgressView extends ImageView { private Drawable maskDraw; /** * 加载的进度 0-100 */ private int mProcess = 20; public ProgressV

  • Android开发中给EditText控件添加TextWatcher监听实现对输入字数的限制(推荐)

    做这个功能是因为开发项目的时候,由于后台接口的一些参数的值的长度有要求,不能超过多少个字符,所以在编辑框中输入的字符是要有限制的. 下面就来看一下demo的实现过程: 首先,在xml控件中放置一个EditText控件,然后初始化该控件并对该控件添加文本监听.xml自己简单的设计一下,代码较为简单,直接上代码: package com.example.edittext; import android.app.Activity; import android.os.Bundle; import an

  • Android开发中使用WebView控件浏览网页的方法详解

    本文实例讲述了Android开发中使用WebView控件浏览网页的方法.分享给大家供大家参考,具体如下: 项目中遇到数学展示问题,常规的Textview显示处理不了数学公式,利用图片生成对服务器又产生较大压力,经过查询,可以通过webview加载JS实现.IOS同样的方法也可实现,但JS渲染效率远高于安卓.对Webview做下总结. 1.WebView 在使用WebView控件时,首先需要在xml布局文件中定义一个WebView控件,定义的方法如下: <WebView android:id=&quo

  • Android实现手指触控图片缩放功能

    这次记录的是实现Android图片两手触控缩放的功能. 编译环境:eclipse Android版本4.0 创建工程过程略 实现图片在页面两手触控缩放 原理图---图片缩放 两手拉开图片变大,两手合拢图片缩小,根据两手的移动距离来判断图片放大和缩小的倍数,两手的移动距离计算方法如下: 两手不管是正着还是斜着拉伸,用勾股定理都能计算出两点的距离. 除此之外,还要确定两个手位置的中心点,图片以这个中心点为参照进行放大和缩小: 计算两点之间的中心点的方法是,点1距原点位置X1(或者Y1)加上点2距原点

  • Android开发技巧之ViewStub控件惰性装载

    在4.5.6节介绍过一个<include>标签,该标签可以在布局文件中引用另外一个布局文件,并可以覆盖被引用布局文件根节点所有与布局相关的属性,也就是以android:layout开头的属性.通过<include>标签可以将一个非常庞大的布局文件分解成若干个较小的布局文件,而且这些小的布局文件也可以被多次引用,从而达到一个重用的目的. <include>标签固然很好用,但有一个问题,就是布局文件中的控件并不一定在程序启动时全都用到,有一些控件只在特定的情况下才会被使用到

  • Android开发自定义实时图表控件实现示例

    目录 概述 演示 环境 实现 第一步:新建项目RealTimeChartDemo 第二步:新建RealTimeChart类 第三步:添加自定义变量 第四步:初始化基础参数 第五步:初始化宽高等参数 第六步:定义添加坐标点方法 第七步:定义坐标偏移方法 第八步:定义绘制网格线方法 第九步:定义绘制X轴标签方法 第十步:定义绘制坐标点方法 第十一步:绘制 第十二步:在activity_main.xml中使用控件 第十三步:在MainActivity中模拟添加数据点 概述 有时我们需要实时滚动显示一段

  • 简单介绍Android开发中的Activity控件的基本概念

    Activity是最基本的模块,一般称之为"活动",在应用程序中,一个Activity通常就是一个单独的屏幕.简单理解,Activity代表一个用户所能看到的屏幕,主要用于处理应用程序的整体性工作,例如监听系统事件,为用户显示指定的View,启动其他Activity等.所有应用的Activity都继承于android.app.Activity类,该类是Android提供的基层类,其他的Activity继承该父类后,通过父类的方法来实现各种功能. Activity 生命周期图如下: 在a

随机推荐