对于普通的点击事件,调用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_UP、ACTION_POINTER_1_DOWN、ACTION_POINTER_2_UP、ACTION_POINTER_2_DOWN、ACTION_POINTER_3_UP、ACTION_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_DOWN、ACTION_POINTER_DOWN、ACTION_POINTER_UP、ACTION_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生成与变化规则可以用以下代码来理解:
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 + "}, ");
}
if(sb.length() > 1) {
sb.delete(sb.length() - 2, sb.length());
}
sb.append("]");
return sb.toString();
}
}
5. 多点触控示例
Listener类
package com.example.study.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<>();
@Override
public 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 com.example.study.activities;
import android.os.Bundle;
import android.view.View;
import androidx.activity.ComponentActivity;
import androidx.annotation.Nullable;
import com.example.study.R;
import com.example.study.listeners.MulTouchListener;
public class TouchListenerActivity extends ComponentActivity {
private View view;
@Override
protected 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">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<SurfaceView
android:id="@+id/touch_listener_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white" />
</FrameLayout>
</RelativeLayout>
231

被折叠的 条评论
为什么被折叠?



