安卓触摸事件的传递
setOnTouchListener()
返回值的副作用(触摸事件是否继续往下或往后传递)如下:
返回值 | 效果 | 是否往下层view传递 | 是否往当前view的后续监听传递 |
---|---|---|---|
true | 该pointer离开屏幕前的后续所有触摸事件都会传递给该TouchListener | 否 | 否 |
false | 该pointer离开屏幕前的后续所有触摸事件都不会再传递给该TouchListener | 是 | 是 |
注:
- 如果view设置了
setOnClickListener
、setOnLongClickListener
,效果等同于在setOnTouchListener()
执行完setOnClickListener
、setOnLongClickListener
的业务逻辑后返回true
; - 并非所有view都允许触摸事件往下传递,如
Button
及其子类就不允许触摸事件向下传递,应该是默认实现了setOnClickListener
; - 触摸事件执行先后顺序为
setOnTouchListener -> setOnLongClickListener -> setOnClickListener
。
触摸事件的传递可以用以下代码理解:
package com.example.study.controller;
import java.lang.reflect.Field;
import java.text.SimpleDateFormat;
import java.util.*;
/**
* 安卓处理触摸事件示意(为方便理解,假设只有一个手指pointer触摸屏幕)
*/
public class TouchEventProcess {
// 长按的时间
private static final long LONG_CLICK_TIME_MILLIS = 500L;
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
private boolean touch = false;
private boolean longClick = false;
private Timer timer;
private ViewListener viewListener;
private List<View> list = new ArrayList<>();
/**
* 在这里设置触摸监听
*/
public TouchEventProcess() {
viewListener = new ViewListener();
viewListener.setOnTouchListener((view, event) -> {
System.out.println(DATE_FORMAT.format(new Date()) + " process touch event:" + MotionEvent.getEventName(event.actionMasked));
return false;
});
viewListener.setOnLongClickListener(view -> {
System.out.println(DATE_FORMAT.format(new Date()) + " process long click event");
return false;
});
viewListener.setOnClickListener(view -> {
System.out.println(DATE_FORMAT.format(new Date()) + " process click event");
});
}
/**
* 多个view
*
* @param event
*/
public void processTouchEvent(MotionEvent event) {
for (View view : list) {
if (processTouchEventInView(view, event)) {
return;
}
System.out.println("=====touc event trans to next view=====");
}
}
public boolean processTouchEventInView(View view, MotionEvent event) {
// 如果当前view最终返回的是false,不再响应当前pointer的触摸事件
if (!viewListener.hasAnyListener()) {
reset(event, "no process");
return false;
}
if (viewListener.getOnTouchListener() != null) {
touch = viewListener.getOnTouchListener().onTouch(view, event);
}
if (touch) {
reset(event, "process touch");
return true;
}
if (viewListener.getOnLongClickListener() != null) {
if (event.actionMasked == MotionEvent.ACTION_DOWN) {
timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
longClick = viewListener.getOnLongClickListener().onLongClick(view);
timer.cancel();
}
}, LONG_CLICK_TIME_MILLIS);
}
}
if (longClick) {
reset(event, "process long click");
return viewListener.hasClickListener();
}
if (viewListener.getOnClickListener() != null) {
if (event.actionMasked == MotionEvent.ACTION_UP) {
viewListener.getOnClickListener().onClick(view);
}
}
reset(event, "process end");
return viewListener.hasClickListener();
}
private void reset(MotionEvent event, String msg) {
// 抬起手指pointer时重置
if (event.actionMasked != MotionEvent.ACTION_UP) {
return;
}
touch = false;
longClick = false;
if (timer != null) {
timer.cancel();
}
System.out.println(DATE_FORMAT.format(new Date()) + " reset by " + msg);
}
public interface OnTouchListener extends Listener {
boolean onTouch(View view, MotionEvent event);
}
public interface OnLongClickListener extends Listener {
boolean onLongClick(View view);
}
public interface OnClickListener extends Listener {
void onClick(View view);
}
public interface Listener {
}
static class ViewListener {
private OnTouchListener onTouchListener;
private OnLongClickListener onLongClickListener;
private OnClickListener onClickListener;
public OnTouchListener getOnTouchListener() {
return onTouchListener;
}
public void setOnTouchListener(OnTouchListener onTouchListener) {
this.onTouchListener = onTouchListener;
}
public OnLongClickListener getOnLongClickListener() {
return onLongClickListener;
}
public void setOnLongClickListener(OnLongClickListener onLongClickListener) {
this.onLongClickListener = onLongClickListener;
}
public OnClickListener getOnClickListener() {
return onClickListener;
}
public void setOnClickListener(OnClickListener onClickListener) {
this.onClickListener = onClickListener;
}
public boolean hasAnyListener() {
return onTouchListener != null || onLongClickListener != null || onClickListener != null;
}
public boolean hasClickListener() {
return onLongClickListener != null || onClickListener != null;
}
}
static class View {
}
static class MotionEvent {
public static int ACTION_DOWN = 0;
public static int ACTION_MOVE = 1;
public static int ACTION_UP = 2;
int actionMasked;
public MotionEvent(int actionMasked) {
this.actionMasked = actionMasked;
}
public static String getEventName(int actionMasked) {
for (Field field : MotionEvent.class.getFields()) {
try {
if ((int) field.get(MotionEvent.class) == actionMasked) {
return field.getName();
}
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
return "unknow";
}
}
public static void main(String[] args) {
TouchEventProcess touchEventProcess = new TouchEventProcess();
touchEventProcess.list.add(new View());
touchEventProcess.list.add(new View());
touchEventProcess.list.add(new View());
touchEventProcess.processTouchEvent(new MotionEvent(MotionEvent.ACTION_DOWN));
touchEventProcess.processTouchEvent(new MotionEvent(MotionEvent.ACTION_MOVE));
long click = 100L;
long longClick = 1000L;
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
touchEventProcess.processTouchEvent(new MotionEvent(MotionEvent.ACTION_UP));
timer.cancel();
}
}, longClick); // 调整delay即可切换短按长按
}
}
验证代码
可用以下代码验证触摸事件的传递:
布局文件touch_event_test.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/touch_test_0"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#F0F0F0"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/white"
android:padding="5dp"
android:text="return true"
android:textSize="32sp"
android:textStyle="bold" />
<!--第一层-->
<LinearLayout
android:id="@+id/touch_test_1_3"
android:layout_width="300dp"
android:layout_height="300dp"
android:background="#7A7374">
<!--第二层-->
<LinearLayout
android:id="@+id/touch_test_1_2"
android:layout_width="200dp"
android:layout_height="200dp"
android:background="#1BA784">
<!--第三层-->
<TextView
android:id="@+id/touch_test_1_1"
android:layout_width="100dp"
android:layout_height="100dp"
android:background="#EB507E"
android:gravity="bottom|right"
android:padding="5dp"
android:text="1_1"
android:textSize="16sp"
android:textStyle="bold" />
<TextView
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_gravity="bottom|right"
android:gravity="bottom|right"
android:padding="5dp"
android:text="1_2"
android:textSize="16sp"
android:textStyle="bold" />
</LinearLayout>
<TextView
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_gravity="bottom|right"
android:gravity="bottom|right"
android:padding="5dp"
android:text="1_3"
android:textSize="16sp"
android:textStyle="bold" />
</LinearLayout>
<!--分割线-->
<ImageView
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="5dp"
android:layout_marginBottom="5dp"
android:background="#000000" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/white"
android:padding="5dp"
android:text="return false"
android:textSize="32sp"
android:textStyle="bold" />
<!--第一层-->
<LinearLayout
android:id="@+id/touch_test_2_3"
android:layout_width="300dp"
android:layout_height="300dp"
android:background="#7A7374">
<!--第二层-->
<LinearLayout
android:id="@+id/touch_test_2_2"
android:layout_width="200dp"
android:layout_height="200dp"
android:background="#1BA784">
<!--第三层-->
<TextView
android:id="@+id/touch_test_2_1"
android:layout_width="100dp"
android:layout_height="100dp"
android:background="#EB507E"
android:gravity="bottom|right"
android:padding="5dp"
android:text="2_1"
android:textSize="16sp"
android:textStyle="bold" />
<TextView
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_gravity="bottom|right"
android:gravity="bottom|right"
android:padding="5dp"
android:text="2_2"
android:textSize="16sp"
android:textStyle="bold" />
</LinearLayout>
<TextView
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_gravity="bottom|right"
android:gravity="bottom|right"
android:padding="5dp"
android:text="2_3"
android:textSize="16sp"
android:textStyle="bold" />
</LinearLayout>
</LinearLayout>
TouchEventTestActivity.java
package org.tao.hetools.activities;
import android.annotation.SuppressLint;
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Toast;
import androidx.activity.ComponentActivity;
import androidx.annotation.Nullable;
import org.tao.hetools.R;
public class TouchEventTestActivity extends ComponentActivity {
private static final String TAG = "TouchEventTestActivity";
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.touch_event_test);
initView();
}
@SuppressLint("ClickableViewAccessibility")
private void initView() {
View _0 = findViewById(R.id.touch_test_0);
View _1_1 = findViewById(R.id.touch_test_1_1);
View _1_2 = findViewById(R.id.touch_test_1_2);
View _1_3 = findViewById(R.id.touch_test_1_3);
View _2_1 = findViewById(R.id.touch_test_2_1);
View _2_2 = findViewById(R.id.touch_test_2_2);
View _2_3 = findViewById(R.id.touch_test_2_3);
_0.setOnTouchListener((view, event) -> {
showToast(event, "_0");
return true;
});
// return true
_1_1.setOnTouchListener((view, event) -> {
showToast(event, "_1_1");
return true;
});
// 设置click longClick事件会对触摸事件在view之间的传递有影响,下同
// _1_1.setOnClickListener(view -> Toast.makeText(this, "_1_1 clicked", Toast.LENGTH_SHORT).show());
// _1_1.setOnLongClickListener(view -> {
// Toast.makeText(this, "_1_1 long clicked", Toast.LENGTH_SHORT).show();
// return true;
// });
_1_2.setOnTouchListener((view, event) -> {
showToast(event, "_1_2");
return true;
});
_1_3.setOnTouchListener((view, event) -> {
showToast(event, "_1_3");
return true;
});
// return false
_2_1.setOnTouchListener((view, event) -> {
showToast(event, "_2_1");
return false;
});
// _2_1.setOnClickListener(view -> Toast.makeText(this, "_2_1 clicked", Toast.LENGTH_SHORT).show());
// _2_1.setOnLongClickListener(view -> {
// Toast.makeText(this, "_2_1 long clicked", Toast.LENGTH_SHORT).show();
// return true;
// });
_2_2.setOnTouchListener((view, event) -> {
showToast(event, "_2_2");
return false;
});
_2_3.setOnTouchListener((view, event) -> {
showToast(event, "_2_3");
return false;
});
}
private void showToast(MotionEvent event, String msg) {
Log.i(TAG, msg + " " + event.getActionMasked());
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN -> Toast.makeText(this, msg + " press down", Toast.LENGTH_SHORT).show();
case MotionEvent.ACTION_UP -> Toast.makeText(this, msg + " press up", Toast.LENGTH_SHORT).show();
}
}
}