【发布时间】:2015-03-01 16:00:13
【问题描述】:
我一直在尝试构建一个可以检测双重和三重敲击的敲击检测器。在我的努力失败后,我在网上搜索了很长时间才能找到可以使用的东西但没有运气!奇怪的是,像这样的图书馆如此稀缺。有什么帮助吗??
【问题讨论】:
-
是的,我有......在这个例子中,如果触摸视图,无论如何都会注册一个水龙头。只有当它的持续时间是
我一直在尝试构建一个可以检测双重和三重敲击的敲击检测器。在我的努力失败后,我在网上搜索了很长时间才能找到可以使用的东西但没有运气!奇怪的是,像这样的图书馆如此稀缺。有什么帮助吗??
【问题讨论】:
你可以试试这样的。
虽然我通常建议不要将三次点击作为一种模式,因为这不是用户通常习惯的方式,因此除非与他们正确沟通,否则大多数人可能永远不会知道他们可以三次点击视图。实际上在移动设备上双击也是如此,在那种环境中进行交互并不总是一种直观的方式。
view.setOnTouchListener(new View.OnTouchListener() {
Handler handler = new Handler();
int numberOfTaps = 0;
long lastTapTimeMs = 0;
long touchDownMs = 0;
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
touchDownMs = System.currentTimeMillis();
break;
case MotionEvent.ACTION_UP:
handler.removeCallbacksAndMessages(null);
if ((System.currentTimeMillis() - touchDownMs) > ViewConfiguration.getTapTimeout()) {
//it was not a tap
numberOfTaps = 0;
lastTapTimeMs = 0;
break;
}
if (numberOfTaps > 0
&& (System.currentTimeMillis() - lastTapTimeMs) < ViewConfiguration.getDoubleTapTimeout()) {
numberOfTaps += 1;
} else {
numberOfTaps = 1;
}
lastTapTimeMs = System.currentTimeMillis();
if (numberOfTaps == 3) {
Toast.makeText(getApplicationContext(), "triple", Toast.LENGTH_SHORT).show();
//handle triple tap
} else if (numberOfTaps == 2) {
handler.postDelayed(new Runnable() {
@Override
public void run() {
//handle double tap
Toast.makeText(getApplicationContext(), "double", Toast.LENGTH_SHORT).show();
}
}, ViewConfiguration.getDoubleTapTimeout());
}
}
return true;
}
});
【讨论】:
case MotionEvent.ACTION_UP后面需要用到这一行:v.performClick();
getTapTimeout() 并非旨在作为点击的最大持续时间,而是“我们将等待查看触摸事件是点击还是点击的持续时间(以毫秒为单位)”滚动。如果用户在此间隔内没有移动,则认为是点击。 (引自developer.android.com/reference/android/view/…)。我发现上面的代码很难可靠地检测到轻拍,因为getTapTimeout() 太小而不能用作最大轻拍持续时间。使用 getLongPressTimeout() 对我来说效果更好。
这是一个 Kotlin 实现,它可以检测任意数量的点击,并遵守 ViewConfiguration 类中的各种超时和 slop 参数。我试图最小化事件处理程序中的堆分配。
import android.os.Handler
import android.view.MotionEvent
import android.view.View
import android.view.ViewConfiguration
import kotlin.math.abs
/*
* Detects an arbitrary number of taps in rapid succession
*
* The passed callback will be called for each tap, with two parameters:
* - the number of taps detected in rapid succession so far
* - a boolean flag indicating whether this is last tap of the sequence
*/
class MultiTapDetector(view: View, callback: (Int, Boolean) -> Unit) {
private var numberOfTaps = 0
private val handler = Handler()
private val doubleTapTimeout = ViewConfiguration.getDoubleTapTimeout().toLong()
private val tapTimeout = ViewConfiguration.getTapTimeout().toLong()
private val longPressTimeout = ViewConfiguration.getLongPressTimeout().toLong()
private val viewConfig = ViewConfiguration.get(view.context)
private var downEvent = Event()
private var lastTapUpEvent = Event()
data class Event(var time: Long = 0, var x: Float = 0f, var y: Float = 0f) {
fun copyFrom(motionEvent: MotionEvent) {
time = motionEvent.eventTime
x = motionEvent.x
y = motionEvent.y
}
fun clear() {
time = 0
}
}
init {
view.setOnTouchListener { v, event ->
when(event.action) {
MotionEvent.ACTION_DOWN -> {
if(event.pointerCount == 1) {
downEvent.copyFrom(event)
} else {
downEvent.clear()
}
}
MotionEvent.ACTION_MOVE -> {
// If a move greater than the allowed slop happens before timeout, then this is a scroll and not a tap
if(event.eventTime - event.downTime < tapTimeout
&& abs(event.x - downEvent.x) > viewConfig.scaledTouchSlop
&& abs(event.y - downEvent.y) > viewConfig.scaledTouchSlop) {
downEvent.clear()
}
}
MotionEvent.ACTION_UP -> {
val downEvent = this.downEvent
val lastTapUpEvent = this.lastTapUpEvent
if(downEvent.time > 0 && event.eventTime - event.downTime < longPressTimeout) {
// We have a tap
if(lastTapUpEvent.time > 0
&& event.eventTime - lastTapUpEvent.time < doubleTapTimeout
&& abs(event.x - lastTapUpEvent.x) < viewConfig.scaledDoubleTapSlop
&& abs(event.y - lastTapUpEvent.y) < viewConfig.scaledDoubleTapSlop) {
// Double tap
numberOfTaps++
} else {
numberOfTaps = 1
}
this.lastTapUpEvent.copyFrom(event)
// Send event
val taps = numberOfTaps
handler.postDelayed({
// When this callback runs, we know if it is the final tap of a sequence
// if the number of taps has not changed
callback(taps, taps == numberOfTaps)
}, doubleTapTimeout)
}
}
}
true
}
}
}
【讨论】:
我开发了 Iorgu solition 的高级版本,更适合我的需求:
public abstract class OnTouchMultipleTapListener implements View.OnTouchListener {
Handler handler = new Handler();
private boolean manageInActionDown;
private float tapTimeoutMultiplier;
private int numberOfTaps = 0;
private long lastTapTimeMs = 0;
private long touchDownMs = 0;
public OnTouchMultipleTapListener() {
this(false, 1);
}
public OnTouchMultipleTapListener(boolean manageInActionDown, float tapTimeoutMultiplier) {
this.manageInActionDown = manageInActionDown;
this.tapTimeoutMultiplier = tapTimeoutMultiplier;
}
/**
*
* @param e
* @param numberOfTaps
*/
public abstract void onMultipleTapEvent(MotionEvent e, int numberOfTaps);
@Override
public final boolean onTouch(View v, final MotionEvent event) {
if (manageInActionDown) {
onTouchDownManagement(v, event);
} else {
onTouchUpManagement(v, event);
}
return true;
}
private void onTouchDownManagement(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
touchDownMs = System.currentTimeMillis();
handler.removeCallbacksAndMessages(null);
if (numberOfTaps > 0 && (System.currentTimeMillis() - lastTapTimeMs) < ViewConfiguration.getTapTimeout() * tapTimeoutMultiplier) {
numberOfTaps += 1;
} else {
numberOfTaps = 1;
}
lastTapTimeMs = System.currentTimeMillis();
if (numberOfTaps > 0) {
final MotionEvent finalMotionEvent = MotionEvent.obtain(event); // to avoid side effects
handler.postDelayed(new Runnable() {
@Override
public void run() {
onMultipleTapEvent(finalMotionEvent, numberOfTaps);
}
}, (long) (ViewConfiguration.getDoubleTapTimeout() * tapTimeoutMultiplier));
}
break;
case MotionEvent.ACTION_UP:
break;
}
}
private void onTouchUpManagement(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
touchDownMs = System.currentTimeMillis();
break;
case MotionEvent.ACTION_UP:
handler.removeCallbacksAndMessages(null);
if ((System.currentTimeMillis() - touchDownMs) > ViewConfiguration.getTapTimeout()) {
numberOfTaps = 0;
lastTapTimeMs = 0;
break;
}
if (numberOfTaps > 0 && (System.currentTimeMillis() - lastTapTimeMs) < ViewConfiguration.getDoubleTapTimeout()) {
numberOfTaps += 1;
} else {
numberOfTaps = 1;
}
lastTapTimeMs = System.currentTimeMillis();
if (numberOfTaps > 0) {
final MotionEvent finalMotionEvent = MotionEvent.obtain(event); // to avoid side effects
handler.postDelayed(new Runnable() {
@Override
public void run() {
onMultipleTapEvent(finalMotionEvent, numberOfTaps);
}
}, ViewConfiguration.getDoubleTapTimeout());
}
}
}
}
【讨论】:
使用视图侦听器检测对视图对象的第一次点击,然后查看如何管理两次后按以退出 stackoverflow.com 上的活动(使用处理程序后延迟)。
【讨论】: