安卓多点触控

ops/2024/11/1 3:26:47/

对于普通的点击事件,调用View对象的setOnClickListener()方法注册点击事件的监听即可,但是如果要处理更加复杂的触控事件时,这种方式就无法满足我们的要求了,此时我们就可以监听所有触摸事件,自行处理触摸事件。

1. 注册触摸事件监听

调用View对象的setOnTouchListener()方法注册触控事件的监听,即可监听触控事件。然后实现View.OnTouchListener接口,在接口的onTouch()方法中处理触摸事件。

2. 触摸事件的种类

主要触摸事件及触发时间如下(以下时间都在MotionEvent中定义):

事件类型触发时间
ACTION_DOWN屏幕上唯一一个手指按下时触发
ACTION_POINTER_DOWN屏幕上任一非唯一手指按下时触发
ACTION_POINTER_UP屏幕上任一非唯一手指抬起时触发
ACTION_UP屏幕上最后一个手指抬起时触发
ACTION_MOVE任意手指移动时触发

注:ACTION_POINTER_1_UPACTION_POINTER_1_DOWNACTION_POINTER_2_UPACTION_POINTER_2_DOWNACTION_POINTER_3_UPACTION_POINTER_3_DOWN已经被废弃,不建议使用。

3. MotionEvent相关方法说明

方法说明
getAction()返回触摸事件的种类。不建议使用,建议用getActionMasked()代替
getActionMasked()返回触摸事件的种类
getActionIndex()获取当前触摸事件的索引。对ACTION_MOVE事件无效,因为ACTION_MOVE事件的getActionIndex()始终返回0
getPointerId(pointerIndex)获取pointerIndex索引对应的pointerId
findPointerIndex(pointerId)获取pointerId对应的索引pointerIndex
getX(pointerIndex)获取pointerIndex相对于当前view左上角的x坐标
getX()等价于getX(0)
getY(pointerIndex)获取pointerIndex相对于当前view左上角的y坐标
getY()等价于getY(0)
getRawX(pointerIndex)获取pointerIndex相对于屏幕左上角的x坐标
getRawX()等价于getRawX(0)
getRawY(pointerIndex)获取pointerIndex相对于屏幕左上角的y坐标
getRawY()等价于getRawY(0)

4. pointerIndex与pointerId

对于一个触摸事件,我们最关心的是触摸点位置和该触摸点对应的手指,对于ACTION_DOWNACTION_POINTER_DOWNACTION_POINTER_UPACTION_UP,通过getActionIndex()即可获取事件索引pointerIndex,然后通过getPointerId(pointerIndex)即可获取当前事件的手指id。但是对于ACTION_MOVE事件,我们无法通过getActionIndex()获取当前事件的索引pointerIndex,因为ACTION_MOVE事件中,getActionIndex()始终返回0

为了将ACTION_MOVE事件的触摸点与pointerId关联起来,我们需要保存当前屏幕上所有的pointerId,然后用findPointerIndex(pointerId)获取pointerId对应的pointerIndex,然后使用getX(pointerIndex)getY(pointerIndex)等方法获取触点位置。

pointerIndex与pointerId生成与变化规则

pointerId

生成规则

手指按下时,从0开始递增寻找,以第一个未被使用的数字作为pointerId。

变化规则

在手指移动过程中不会发生变化,直到手指抬起,回收该pointerId。

pointerIndex

生成规则

初始pointerIndex = pointerId

变化规则

当有手指抬起时,该手指的pointerIndex后的所有手指的pointerIndex -= 1;当有手指按下时,由于pointerIndex = pointerId,所有pointerIndex >= pointerId的手指的pointerIndex += 1

总的来说,pointerIndex与pointerId生成与变化规则可以用以下代码来理解:

package com.example.study.controller;import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;public class PointerCollection {private List<Pointer> pointers;public PointerCollection() {pointers = new ArrayList<>();}/*** 手指按下时,给当前触控点生成pointerId** @return 生成的pointerId*/public int pressDown() {int index = 0;for (; index < pointers.size(); index++) {if (pointers.get(index).pointerId != index) {break;}}pointers.add(index, new Pointer(index));return index;}/*** 手指抬起时,回收触控点** @param pointerId 触控点Id* @return 回收的触控点*/public int pressUp(int pointerId) {Iterator<Pointer> iterator = pointers.iterator();while (iterator.hasNext()) {Pointer pointer = iterator.next();if (pointer.pointerId == pointerId) {iterator.remove();break;}}return pointerId;}/*** 获取pointerId对应的索引pointerIndex** @param pointerId pointerId* @return pointerIndex*/public int findPointerIndex(int pointerId) {for (int index = 0; index < pointers.size(); index++) {if (pointers.get(index).pointerId == pointerId) {return index;}}return -1;}class Pointer {int pointerId;public Pointer(int pointerId) {this.pointerId = pointerId;}}public String toString() {StringBuilder sb = new StringBuilder();for (int pointerIndex = 0; pointerIndex < pointers.size(); pointerIndex++) {sb.append("{pointerId: " + pointers.get(pointerIndex).pointerId + ", pointerIndex:" + pointerIndex + "}  ");}return sb.toString();}
}

5. 多点触控示例

Listener类

package org.tao.hetools.listeners;import android.util.Log;
import android.view.MotionEvent;
import android.view.View;import java.util.HashMap;
import java.util.Locale;
import java.util.Map;public class MulTouchListener implements View.OnTouchListener {private Map<Integer, float[]> pointerMap = new HashMap<>();@Overridepublic boolean onTouch(View view, MotionEvent event) {updatePointer(event);return true;}private void updatePointer(MotionEvent event) {int actionMasked = event.getActionMasked();int actionIndex = event.getActionIndex();int pointerId = event.getPointerId(actionIndex);switch (actionMasked) {case MotionEvent.ACTION_DOWN:pointerMap.clear();case MotionEvent.ACTION_POINTER_DOWN:pointerMap.put(pointerId, new float[]{event.getX(actionIndex), event.getY(actionIndex)});Log.i("记录按压事件", String.format("第%d根手指按下", pointerId));return;case MotionEvent.ACTION_POINTER_UP:pointerMap.remove(pointerId);Log.i("记录按压事件", String.format("第%d根手指抬起", pointerId));return;case MotionEvent.ACTION_UP:pointerMap.clear();Log.i("记录按压事件", String.format("所有手指抬起", pointerId));return;case MotionEvent.ACTION_MOVE:break;default:return;}StringBuilder sb = new StringBuilder("所有手指位置信息如下    ");for (Map.Entry<Integer, float[]> entry : pointerMap.entrySet()) {int pointerIndex = event.findPointerIndex(entry.getKey());float[] currentPointer = {event.getX(pointerIndex), event.getY(pointerIndex)};entry.setValue(currentPointer);sb.append(String.format(Locale.CHINESE, "%d:(%1.4f, %1.4f)  ", entry.getKey(),event.getX(pointerIndex),event.getY(pointerIndex)));}Log.i("记录移动事件", sb.toString());}
}

Activity类

package org.tao.hetools.activities;import android.os.Bundle;
import android.view.View;import androidx.activity.ComponentActivity;
import androidx.annotation.Nullable;import org.tao.hetools.R;
import org.tao.hetools.listeners.MulTouchListener;public class TouchListenerActivity extends ComponentActivity {private View view;@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);this.setContentView(R.layout.activity_touch_listener);view = findViewById(R.id.touch_listener_view);view.setOnTouchListener(new MulTouchListener());}
}

Activity布局

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"><FrameLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"><SurfaceViewandroid:id="@+id/touch_listener_view"android:layout_width="match_parent"android:layout_height="match_parent"android:background="@color/white" /></FrameLayout></RelativeLayout>

参考文章

  1. 【朝花夕拾】Android自定义View篇之(八)多点触控(上)基础知识总结

http://www.ppmy.cn/ops/130017.html

相关文章

谷歌seo发外链真的能提升排名吗?

那肯定是可以的&#xff0c;但很多SEO新人总以为&#xff0c;只要发几条外链&#xff0c;排名就会蹭蹭往上涨。这样的想法太过理想化了。虽然外链确实是提升排名的重要因素之一&#xff0c;但这并不意味着你随便发几条外链就能见效 优质的外链重要&#xff0c;但前提你能找到多…

GFF: Gated Fully Fusion for Semantic Segmentation门控融合语义分割-论文阅读笔记

摘要&#xff1a; 语义分割通过对每个像素密集预测其类别&#xff0c;生成对场景的全面理解。深度卷积神经网络的高级特征已经在语义分割任务中证明了它们的有效性&#xff0c;然而高级特征的粗分辨率经常导致对小/薄物体的结果不佳&#xff0c;而这些物体的细节信息非常重要。…

多浏览器同步测试工具的设计与实现

在做Web兼容测试时&#xff0c;测试人员往往需要在不同浏览器上重复执行相同的操作。 现有自动化录制手段&#xff0c;其实是后置的对比&#xff0c;效率与反馈都存在延迟&#xff0c;执行过程相对是黑盒的&#xff0c;过程中如果测试人员没细化到具体的校验点&#xff0c;即使…

JVM 类加载器

字节码的结构 魔数u4 cafe babe 版本u4 52 java8 常量池计数器u2 从1开始&#xff0c;0索引留给不需要的情况 常量池 表 #1 -> #计数器-1 类标识符 u2 public final abstrat class annotion interface 之类 类索引u2 名字 父类索引u2 父类名字 接口计数器 u2 接口数…

HTTP的初步了解

目录 前言 一、HTTP协议的基本概念 1.1、请求格式 1.2、响应格式 二、HTTP链接问题 前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; HTTP协议是超文本传输协议 HTTP的短连接&#xff1a;建立连接——数据传输——关闭连接 HTTP的长连接&#xff1a;…

Go语言八股(Ⅲ)

什么是rune类型&#xff1f; rune类型是Go语言的一种特殊数字类型&#xff0c;在builtin/builtin.go文件中&#xff0c;它的定义为&#xff1a;type rune int32&#xff1b;官方对它的解释是&#xff1a;rune是类型int32的别名&#xff0c;在所有方面都等价于它&#xff0c;用…

vite乾坤 vite-plugin-qiankun 报错 ReferenceError: ReadableStream is not defined

今天新接入一个子应用&#xff0c;发现其他子项目都可以运行&#xff0c;改造代码都差不多。我新的项目却报错 ReferenceError: ReadableStream is not defined断点发现是有个库版本不对&#xff0c;上github搜到了问题。 https://github.com/tengmaoqing/vite-plugin-qiankun…

创建一个基于Java的图书馆管理系统

创建一个基于Java的图书馆管理系统是一个涉及多个步骤的过程。包括项目结构、数据库设计、配置文件、DAO层、Service层、Controller层和前端页面的示例。 1. 需求分析 明确系统的主要功能需求&#xff0c;例如&#xff1a; 用户注册与登录图书信息管理&#xff08;增删改查&…