相关API 介绍

MotionEvent.getY() 和 MotionEvent.getRawY() 的区别

getY 表示触摸事件在当前的View内的Y 坐标, getRawY表示触摸事件在整个屏幕上面的Y 坐标

MotionEvent.getActionIndex()

event.getActionIndex() 表示当前触摸手指的index, 用于多点触控。
getActionIndex 只在 ACTION_POINTER_DOWN 和 ACTION_POINTER_UP 的时候用到。
返回当前ACTION_POINTER_DOWN 或者 ACTION_POINTER_UP 对应的手指Index。如果不是ACTION_POINTER_DOWN 和ACTION_POINTER_UP 事件就会一直返回0。

我们拿到当前的触摸手指的Index 之后,就可以拿到当前触摸手指的Id:event.getPointerId(event.getActionIndex()). 在多点触控过程中,Index 可能会变,但是Id 不会变。 我们也可以根据Id 拿到 index,从而计算触摸手指Id 对应的Y 坐标:event.findPointerIndex(mActivePointerId)

MotionEvent.getActionMasked() 和 MotionEvent.getAction 有什么区别

MotionEvent.getAction() 识别不了 MotionEvent.ACTION_POINTER_DOWN 和 ACTION_POINTER_UP 事件,所以如果自定义View 用到了多点触控,要使用getActionMasked() 方法

问题

比如我们写一个支持手指滑动操作的控件时,当你一根手指操作你发现没有问题,但是当多根手指的时候,会有一些问题。具体表现为:
开始的时候,按下第一根手指,然后我们在距离第一根手指很远的地方,按下第二根手指,就会发现页面突然滚动了一段距离。

是因为代码没有去支持多点触控
因为event.getY() 返回的可能是任意的一个手指的位置, 你可以打印log 去观察。默认getY()返回第1个手指的位置 当你再放一个手指(第二个手指)到屏幕上, 然后松开第一根手指, 你会发现第getY() 返回的变成了第二个手指的位置 然后你再放一个手指,你会发现getY()又变成了新的手指的位置。

所以你在onTouchEvent 里面 ,如果你是按照getY() 和 LastY 做差值去移动页面,ACTION_MOVE 的时候会有两个手指的落差 ,造成双指切换的时候 页面会来回跳动

解决方法:

我们定义一个变量为有效手指Id,每当有一个有新的手指触摸屏幕的时候,把新的手指当做有效手指,记录有效手指的Id,更新上次手指的位置为新手指的位置。在Action_MOVE的时候,我们根据有效手指的Id 去拿到有效手指滚动的距离,从而决定当前View 滚动多远。

    @Overridepublic boolean onTouchEvent(MotionEvent event) {if (mVelocityTracker == null) {mVelocityTracker = VelocityTracker.obtain();}switch (event.getActionMasked()) {case MotionEvent.ACTION_DOWN:if (!mScroller.isFinished()) {mScroller.abortAnimation();}mLastY = (int) event.getY();mActivePointerId = event.getPointerId(event.getActionIndex());Log.d(TAG, "ACTION_DOWN mLastY:" + mLastY);break;case MotionEvent.ACTION_UP:Log.d(TAG, "ACTION_UP");                mVelocityTracker.computeCurrentVelocity(1000,mScaledMaximumFlingVelocity);int yVelocity = (int) mVelocityTracker.getYVelocity();Log.d("ScrollTextView", "yVelocity:" + yVelocity);if (Math.abs(yVelocity) > mScaledMinimumFlingVelocity) {if (!mScroller.isFinished()) {mScroller.abortAnimation();}mScroller.fling(0,getScrollY(),0, -yVelocity,0,0,0,getLayout().getHeight() - getHeight());//之前老是掉帧的感觉   原来是因为fling 之后 并没有去执行invalidate 有时候可以是因为可能刚好到了60帧刷新的节点ViewCompat.postInvalidateOnAnimation(this);}mVelocityTracker.recycle();mVelocityTracker = null;mLastY = (int) event.getY();mActivePointerId = INVALIDATE_ID;break;              case MotionEvent.ACTION_MOVE:if (mActivePointerId == INVALIDATE_ID) {break;}int pointerIndex = event.findPointerIndex(mActivePointerId);Log.d(TAG, "mActivePointerId:" + mActivePointerId);Log.d(TAG, "pointerIndex:" + pointerIndex);//通过index 获取 坐标float eventY = event.getY(pointerIndex);boolean b = overScrollBy(0, (int) (mLastY - eventY), getScrollX(), getScrollY(), 0, getLayout().getHeight() - getHeight(), 0, 0, true);if (b) {mVelocityTracker.clear();}mLastY = (int) event.getY(pointerIndex);Log.d(TAG, "mLastY:" + mLastY);break;//手指放下case MotionEvent.ACTION_POINTER_DOWN://拿到手指的indexint actionIndex = event.getActionIndex();//记录有效的手指idmActivePointerId = event.getPointerId(actionIndex);mLastY = (int) event.getY(actionIndex);break;//手指拿起case MotionEvent.ACTION_POINTER_UP:int actionIndex1 = event.getActionIndex();int pointerId = event.getPointerId(actionIndex1);if (pointerId == mActivePointerId) {int newPointIndex = actionIndex1 == 0 ? 1 : 0;mLastY = (int) event.getY(newPointIndex);mActivePointerId = event.getPointerId(newPointIndex);if (mVelocityTracker != null) {mVelocityTracker.clear();}}break;default:Log.d(TAG, "event:" + event);break;}if (mVelocityTracker != null) {mVelocityTracker.addMovement(event);}return true;}

反思总结

1.ScrollView 原生控件都是我们学习的很好的例子。如果你想学习多点触控,ScrollView 只一个很好地学习材料。

2.某一时刻 触摸事件的打印:

2020-06-17 21:56:31.141 22380-22380/com.pipiyang.cn03 D/ScrollTextView: event:MotionEvent { action=ACTION_POINTER_DOWN(2), actionButton=0, id[0]=0, x[0]=396.0, y[0]=1144.0, toolType[0]=TOOL_TYPE_FINGER, id[1]=1, x[1]=510.0, y[1]=803.0, toolType[1]=TOOL_TYPE_FINGER, id[2]=2, x[2]=690.0, y[2]=441.0, toolType[2]=TOOL_TYPE_FINGER, buttonState=0, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=3, historySize=0, eventTime=488846740, downTime=488846006, deviceId=4, source=0x1002 }

附:完整的一个支持超长文字滚动的TextView:

import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.ViewConfiguration;
import android.widget.OverScroller;import androidx.annotation.Nullable;
import androidx.core.view.ViewCompat;/*** =======================================================================================* 作    者:caoxinyu* 创建日期:2020/5/10.* 类的作用:可以上下滑动的TextView* 修订历史:*** 问题1.textView 高度有问题* 终于知道MeasureSpec.UNSPECIFIED 有什么用了* 因为有些是无限大的 比如 listView* 所以 他的尺寸应该是UNSPECIFIED 的* =======================================================================================*/
public class ScrollTextView extends androidx.appcompat.widget.AppCompatTextView {private OverScroller mScroller;private OverScroller mOverScroller;private int mLastY;private VelocityTracker mVelocityTracker;private ViewConfiguration mViewConfiguration;private int mScaledMaximumFlingVelocity;private int mScaledMinimumFlingVelocity;private static final String TAG = "ScrollTextView";private int mActivePointerId;private final int INVALIDATE_ID = -1;public ScrollTextView(Context context) {super(context);init(context);}public ScrollTextView(Context context, @Nullable AttributeSet attrs) {super(context, attrs);init(context);}public ScrollTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);init(context);}private void init(Context context) {mScroller = new OverScroller(context);mScroller = new OverScroller(context);mOverScroller = new OverScroller(context);mViewConfiguration = ViewConfiguration.get(context);mScaledMinimumFlingVelocity = mViewConfiguration.getScaledMinimumFlingVelocity();mScaledMaximumFlingVelocity = mViewConfiguration.getScaledMaximumFlingVelocity();Log.d("ScrollTextView", "mScaledMinimumFlingVelocity:" + mScaledMinimumFlingVelocity);Log.d("ScrollTextView", "mScaledMaximumFlingVelocity:" + mScaledMaximumFlingVelocity);}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);}@Overridepublic boolean onTouchEvent(MotionEvent event) {if (mVelocityTracker == null) {mVelocityTracker = VelocityTracker.obtain();}//getY 表示在当前的View 内的Y 坐标 getRawY表示 在整个屏幕上面的Y 坐标Log.d(TAG, "event.getY():" + event.getY());Log.d(TAG, "event.getRawY():" + event.getRawY());Log.d(TAG+"23", "event.getActionIndex():" + event.getActionIndex());Log.d(TAG+"23", "event.getPointerId(event.getActionIndex()):" + event.getPointerId(event.getActionIndex()));Log.d(TAG, "event:" + event);// TODO: 2020/6/18  event.getActionIndex() 为什么一直返回0?//getActionIndex 只在 ACTION_POINTER_DOWN 和 ACTION_POINTER_UP 的时候用到//放回当前ACTION_POINTER_DOWN 或者 ACTION_POINTER_UP 对应的手指Index//如果不是这种ACTION_POINTER_DOWN 和 ACTION_POINTER_UP 事件//就会一直返回0Log.d(TAG, "event.getActionIndex():" + event.getActionIndex());// TODO: 2020/6/18 event.getActionMasked() 和 event.getAction 有什么区别?// TODO: 2020/6/18  event.getAction 好像识别不了 MotionEvent.ACTION_POINTER_DOWNswitch (event.getActionMasked()) {case MotionEvent.ACTION_DOWN:if (!mScroller.isFinished()) {mScroller.abortAnimation();}mLastY = (int) event.getY();mActivePointerId = event.getPointerId(event.getActionIndex());Log.d(TAG, "ACTION_DOWN mLastY:" + mLastY);break;case MotionEvent.ACTION_UP:Log.d(TAG, "ACTION_UP");mVelocityTracker.computeCurrentVelocity(1000,mScaledMaximumFlingVelocity);int yVelocity = (int) mVelocityTracker.getYVelocity();Log.d("ScrollTextView", "yVelocity:" + yVelocity);if (Math.abs(yVelocity) > mScaledMinimumFlingVelocity) {if (!mScroller.isFinished()) {mScroller.abortAnimation();}mScroller.fling(0,getScrollY(),0, -yVelocity,0,0,0,getLayout().getHeight() - getHeight());//之前老是掉帧的感觉   原来是因为fling 之后 并没有去执行invalidate 有时候可以是因为可能刚好到了60帧刷新的节点ViewCompat.postInvalidateOnAnimation(this);}mVelocityTracker.recycle();mVelocityTracker = null;mLastY = (int) event.getY();mActivePointerId = INVALIDATE_ID;break;//todo 同时只能收到一个手指的移动?case MotionEvent.ACTION_MOVE:// TODO: 2020/6/17 为什么event.getY() 是任意的?//因为总是第0个 但是第0个 可能会是任意一个// TODO: 2020/6/18  event.getY(mPointerId)//根据有效id 获取 index// TODO: 2020/6/18 为什么在actionUP 之后还能收到actionMove? 多指触控的时候if (mActivePointerId == INVALIDATE_ID) {break;}int pointerIndex = event.findPointerIndex(mActivePointerId);Log.d(TAG, "mActivePointerId:" + mActivePointerId);Log.d(TAG, "pointerIndex:" + pointerIndex);//通过index 获取 坐标float eventY = event.getY(pointerIndex);boolean b = overScrollBy(0, (int) (mLastY - eventY), getScrollX(), getScrollY(), 0, getLayout().getHeight() - getHeight(), 0, 0, true);if (b) {mVelocityTracker.clear();}mLastY = (int) event.getY(pointerIndex);Log.d(TAG, "mLastY:" + mLastY);break;//手指放下case MotionEvent.ACTION_POINTER_DOWN://拿到手指的indexint actionIndex = event.getActionIndex();//记录有效的手指idmActivePointerId = event.getPointerId(actionIndex);mLastY = (int) event.getY(actionIndex);break;//手指拿起case MotionEvent.ACTION_POINTER_UP:int actionIndex1 = event.getActionIndex();int pointerId = event.getPointerId(actionIndex1);if (pointerId == mActivePointerId) {int newPointIndex = actionIndex1 == 0 ? 1 : 0;mLastY = (int) event.getY(newPointIndex);mActivePointerId = event.getPointerId(newPointIndex);if (mVelocityTracker != null) {mVelocityTracker.clear();}}break;default:Log.d(TAG, "event:" + event);break;}if (mVelocityTracker != null) {mVelocityTracker.addMovement(event);}return true;}@Overrideprotected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {super.onOverScrolled(scrollX, scrollY, clampedX, clampedY);if (!mScroller.isFinished()) {if (clampedY) {
//                mScroller.springBack(mScrollX, mScrollY, 0, 0, 0, getScrollRange());}}else {super.scrollTo(scrollX, scrollY);
//            Log.d("ScrollTextView", "onOverScrolled");}}@Overridepublic void computeScroll() {super.computeScroll();if (mScroller.computeScrollOffset()) {scrollBy(mScroller.getCurrX() - getScrollX() ,mScroller.getCurrY() - getScrollY());Log.d("ScrollTextView", "computescorll");}if (mOverScroller.computeScrollOffset()) {scrollTo(mOverScroller.getCurrX(),mOverScroller.getCurrY());}}/**** @param startx* @param starty* @param xDistance* @param yDistance* @param time*///如果time 很大,那么你的距离要相应的大 不然可能不会滑动public void startScroll(int startx,int starty,int xDistance,int yDistance,int time ){//startScroll 要调用invalidate 请求重新绘制mScroller.startScroll(startx,starty,xDistance,yDistance,time);invalidate();}public void startScroll(int startx,int starty,int xDistance,int yDistance){//startScroll 要调用invalidate 请求重新绘制
//        mScroller.startScroll(startx,starty,xDistance,yDistance);mOverScroller.startScroll(startx,starty,xDistance,yDistance);ViewCompat.postInvalidateOnAnimation(this);}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);}/*** 不支持多点触控* 是因为event.getY() 返回的可能是任意的一个手指的位置* 你可以打印log 默认getY()返回第1个手指的位置  当你再放一个手指(第二个手指)到屏幕上* 然后松开 你会发现第getY() 返回的变成了第二个手指的位置  然后你再放一个手指,你会发现* getY()又变成了新的手指的位置** 所以你在onTouchEvent 里面 ,ACTION_MOVE 的时候* 会有两个手指的落差  造成双指切换的时候 页面会来回跳动*/
}

Android 多点触控 MotionEvent详解相关推荐

  1. Android多点触控详解

    本文转载自GcsSloop的 安卓自定义View进阶-多点触控详解 的文章 Android 多点触控详解,在前面的几篇文章中我们大致了解了 Android 中的事件处理流程和一些简单的处理方案,本次带 ...

  2. Android自定义View进阶-MotionEvent详解

    欢迎Follow我的GitHub, 关注我的CSDN. 其余参考Android目录 我们微信公众号:杨守乐 推荐文章: 如果你喜欢上了一个程序员小伙,献给所有的程序员女友 学习资料(干货汇集)不断更新 ...

  3. Android多点触控揭秘

    本文原创,转载请注明:http://blog.csdn.net/cloudzfy1/article/details/6582707 Google 暑期大学生博客分享大赛 - 2011 Android ...

  4. Android多点触控技术

    1 简介 Android多点触控在本质上需要LCD驱动和程序本身设计上支持,目前市面上HTC.Motorola和Samsung等知名厂商只要使用电容屏触控原理的手机均可以支持多点触控Multitouc ...

  5. Android 多点触控消息捕获与处理

    1 简介 Android多点触控在本质上需要LCD驱动和程序本身设计上支持,目前市面上HTC.Motorola和Samsung等知名厂商只要使用电容屏触控原理的手机均可以支持多点触控Multitouc ...

  6. 模拟Android多点触控

    Android多点触控 Android多点触控 多点触控实现思路 第一种adb shell input方式 第二种adb shell sendevent方式 多点触控实现思路   经过资料的查询,要在 ...

  7. Android多点触控之——MotionEvent(触控事件)

    今天晚上刚学习了一个多点触控的小程序,后面想对其做一个定制.在写的时候遇到很多问题,于是乎就查了一下API文档,又到网上查了一下高手的文章,最后自己又实践了一下.终于把多点触控事件监听的大概原理给弄清 ...

  8. Android多点触控MultiTouch浅析

    申明: 参考:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2013/0226/914.html 下面实现如何通过应用层支持多点触控操作, ...

  9. android 多点触控缩放,Android多点触控(图片的缩放Demo)

    本文主要介绍Android的多点触控,使用了一个图片缩放的实例,来更好的说明其原理.需要实现OnTouchListener接口,重写其中的onTouch方法. 实现效果图: 源代码: 布局文件: ac ...

最新文章

  1. 注意力是智力的五个基本因素之一
  2. mysql怎么写Connection_MySQL里面的CONNECTION_ID
  3. java守护线程和用户线程
  4. matlab练习程序(二值图像连通区域标记法,一步法)
  5. 简化MVVM属性设置和修改 - .NET CORE(C#) WPF开发
  6. java内存不足错误_调试Java内存不足错误
  7. 卷积神经网络中feature map的含义
  8. 2.shiro+jdbc+idea+maven数据库
  9. 吉米多维奇数学分析习题集学习指引
  10. mysql至少选修了两门课程_数据库中用关系代数表达式,查询至少选修两门课程的学生的学号和姓名怎么写?...
  11. BZOJ3654 : 图样图森破
  12. 将北京时间转换为世界协调时
  13. 微机接口-8086CPU
  14. 嵌入式linux操作系统的移植 实验报告,嵌入式linux系统移植试题
  15. L2正则化和collection,tf.GraphKeys
  16. 用ctrl+鼠标滚动调节字体大小
  17. Android 通过 WebView 请求下载 APK
  18. Table '表名' doesn't exist
  19. STM32F0系列出现overrun interrupt 和PB6 PB7映射的解决办法
  20. yolov5 目标检测算法

热门文章

  1. Error in configuration process解决方法
  2. python制作网页的步骤_使用httplib模块来制作Python下HTTP客户端的方法
  3. JXLS导出Excel(模板导出)
  4. springCloud学习笔记系列(1)-负载均衡Ribbon
  5. 同一个页面,两次请求保证查询条件不变(题目不太相符,我比较渣,问题都不知道怎么表述!--)...
  6. 处理js两个数相乘的坑
  7. js添加菜单栏之后停留在那里
  8. 18岁的他从月薪2000到月薪11000经历了什么?
  9. PHP正则表达式规则及常用方法整理
  10. GC之二--GC是如何回收时的判断依据、shallow(浅) size、retained(保留) size、Deep(深)size...