Android中的触摸事件——MotionEvent中的多点触控

Android中的触摸事件——MotionEvent中的多点触控

前言

触摸事件在Android手机中有多重要不言而喻,用户的每次操作都和它有关。不知道大家有没有见过一些自定义的控件有这样的问题:当单手操作的时候,没有问题,但是当第二根手指头放上去的时候,该View中的内容就会“跳动一下”,如果有这样问题的控件,就是没有处理多点触控。该篇内容主要讲解MotionEvent对象中的多点触控信息,以及RecyclerView对于这种多点触控是如何处理的。MotionEvent又是啥呢?用来记录用户在屏幕中的触摸信息的对象。假如你点了一下屏幕(假如你手没抖,而且速度很快),这个时候就会产生两个MotionEvent对象(UP和DOWM)。

正文

一次完整的触摸事件流(官方叫gesture)至少包括ACTION_DOWN和ACTION_UP两个Event(手指触摸的情况,不考虑使用鼠标的情况,本文后面所描述的所有情况都是手指触摸的情况)。前面提到了Event的ACTION,我们一般有两个方法可以取到ACTION,一个是getAction(),还有一个就是getActionMasked()。这两个方法有什么区别呢?action中包含了Pointer的Index,而actionMasked中不包含这个Index。那什么又是Pointer呢?这就涉及到该篇内容要重点讨论的多点触控。可以理解成每一个触控点,通俗点讲就是你放在屏幕上的手指头。我们一般都用ActionMasked这个方法来拿这些Action。下面就简单来说明一下这些常见的Event。

  1. ACTION_DOWN

第一根手指头触摸到屏幕(之前屏幕上没有手指头),一次事件触摸流的开始,很简单,但是很重要,这里也要简单的提一下,在ViewGroup中也是根据这次事件的坐标来决定该次事件流交给谁来处理,直到这次事件流完成(ACTION_UP)。

  1. ACTION_MOVE

就是你的手指头在屏幕上滑动,就会产生这个事件。

  1. ACTION_UP

最后一根手指头离开屏幕(屏幕上没有手指头了),标志着该次事件流已经完成。

  1. ACTION_CANCEL

这次事件流被取消了,虽然还没有完成,一般是ViewGroup经过某种条件判断会设置这样的ACTION。

  1. ACTION_POINTER_DOWN

当屏幕上已经有手指头的时候,再按一个手指头下去就会触发这个事件。

  1. ACTION_POINTER_UP

当手指头离开屏幕,同时屏幕上还有手指头的时候就会触发这个事件。

如果你的自定义控件处理好了上面的6种ACTION,那么你的控件对触摸的处理就很好了,因为RecyclerView就只是处理了这6种Event。

在一个MotionEvent对象中,包含了你在屏幕上所有的触摸点信息,他默认会有一个类似于active的触摸点,可以通过方法getActionIndex()拿到这个触摸点的Index,然后再通过方法getPointerId()能拿到这个触摸点的Id,Id通过findPointerIndex(),能再拿到这个Index。这里需要注意的是在一次事件流中,同一个触摸点的Index是可能发生改变的,但是Id是不会改变的。在方法getX()getY()中都可以传一个Index来拿你想要的触摸点的坐标,不止这两个方法可以传入index,其他的读者自己去研究了。下面我们来讨论下不同情况下active的默认触摸点都是哪些点呢?

  1. ACTION_DOWN, ACTION_MOVE, ACTION_UP

这些ACTIONs默认的active触摸点Index都是0,也就是说这些事件如果你的初次点击屏幕的手指头没有离开屏幕,那就一直是这个点,如果这个手指头已经离开屏幕,那这个点就变成了第二个点击屏幕的点,依次类推。

  1. ACTION_POINTER_DOWN

默认active触摸点是新点击到屏幕上的那个点,但是在后续的move中这个默认index又回变成0。这里也可以简单解释下文章开篇中提到的“跳一下”的Bug:因为在第二个手指头点击屏幕的瞬间,active的触摸点为第二个手指头,这个时候默认的坐标也是第二个指头,后续的move事件中默认的active触摸点又会变成第一个手指头,所以会出现跳一下的Bug。

  1. ACTION_POINTER_UP

这个Event和ACTION_POINTER_DOWN类似,只是默认的active的Index变成了离开屏幕的那个触摸点。

下面我们来看看RecyclerView中是如何来处理多点触控的。


    @Override

    public boolean onTouchEvent(MotionEvent e) {



    final MotionEvent vtev = MotionEvent.obtain(e);

        final int action = e.getActionMasked();

        // 默认active的Index

        final int actionIndex = e.getActionIndex();





        switch (action) {

            case MotionEvent.ACTION_DOWN: {

              // 直接取第一个Pointer为自己要处理的,将它的Id保存到成员变量中

                mScrollPointerId = e.getPointerId(0);



                // 记录初始化的坐标

                mInitialTouchX = mLastTouchX = (int) (e.getX() + 0.5f);

                mInitialTouchY = mLastTouchY = (int) (e.getY() + 0.5f);

            } break;

            case MotionEvent.ACTION_POINTER_DOWN: {

              // 当第二个手指头(也可能是第n个手指头)点击屏幕的时候,就会用这个手指头来接管这次事件流。

                mScrollPointerId = e.getPointerId(actionIndex);

                mInitialTouchX = mLastTouchX = (int) (e.getX(actionIndex) + 0.5f);

                mInitialTouchY = mLastTouchY = (int) (e.getY(actionIndex) + 0.5f);

            } break;

            case MotionEvent.ACTION_MOVE: {

                // 通过pointerID拿到需要处理的Index。

                final int index = e.findPointerIndex(mScrollPointerId);

                if (index < 0) {

                    Log.e(TAG, "Error processing scroll; pointer index for id "

                            + mScrollPointerId + " not found. Did any MotionEvents get skipped?");

                    return false;

                }

// 获取需要处理的点的坐标。

                final int x = (int) (e.getX(index) + 0.5f);

                final int y = (int) (e.getY(index) + 0.5f);



            } break;

            case MotionEvent.ACTION_POINTER_UP: {

                onPointerUp(e);

            } break;

            case MotionEvent.ACTION_UP: {

                resetTouch();

            } break;

            case MotionEvent.ACTION_CANCEL: {

                cancelTouch();

            } break;

        }





    }



    // 处理pointer up的情况。

    private void onPointerUp(MotionEvent e) {

        // 离开屏幕的触摸点index

        final int actionIndex = e.getActionIndex();



        // 如果离开的那个点的id正好是我们接管触摸的那个那个点,那么我们就需要重新再找一个pointer来接管,反之不用管。

        if (e.getPointerId(actionIndex) == mScrollPointerId) {

            // Pick a new pointer to pick up the slack.

            // 如果离开屏幕的点index是0,那就用index 为 1 的点,反之就直接用0。

            final int newIndex = actionIndex == 0 ? 1 : 0;



            // 重置id和初始化initX和initY。

            mScrollPointerId = e.getPointerId(newIndex);

            mInitialTouchX = mLastTouchX = (int) (e.getX(newIndex) + 0.5f);

            mInitialTouchY = mLastTouchY = (int) (e.getY(newIndex) + 0.5f);

        }

    }

这里简单说明下:一个手指到滑动就不说了,当屏幕上有新的手指加入时(之前屏幕上已经有手指头了),这个新加入的手指就会接管RecyclerView的滑动。当有手指头离开屏幕时(屏幕上还有其他的手指头),这个时候如果离开的手指头刚好时接管滑动的那个手指头,这个时候就会找index为0或着1的手指头重新接管滑动;如果离开的不是接管滑动的手指头,就不用管。




到这里多点触控相关的内容就没了,如果有错误的地方,欢迎大家指出来。后面可能还会有触摸事件的发送和滚动相关内容。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,710评论 4 376
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,839评论 2 308
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 112,295评论 0 255
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,776评论 0 223
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,198评论 3 297
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 41,074评论 1 226
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,200评论 2 322
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,986评论 0 214
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,733评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,877评论 2 254
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,348评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,675评论 3 265
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,393评论 3 246
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,209评论 0 9
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,996评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,212评论 2 287
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 36,003评论 2 280

推荐阅读更多精彩内容