Android - 检测双击和三次点击视图

Android - Detect doubletap AND tripletap on view

我一直在尝试构建一个可以检测双击和三次敲击的敲击检测器。在我的努力失败后,我在网上搜索了很长时间以找到可以使用的东西,但没有运气!奇怪的是,像这样的图书馆如此稀缺。有什么帮助吗??

使用视图侦听器检测第一次点击视图对象,然后查看如何管理两次后退以在 whosebug.com 上退出 activity(使用处理程序 post 延迟).

Clicking the back button twice to exit an activity

你可以试试这样的方法。

尽管我通常建议不要使用三击作为一种模式,因为用户通常不习惯这种方式,因此除非与他们正确沟通,否则大多数人可能永远不知道他们可以三次点击视图。实际上在移动设备上双击也是如此,它并不总是一种在该环境中进行交互的直观方式。

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;
    }
});

我开发了一个更适合我需求的 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());
                }
        }
    }

}

这是一个 Kotlin 实现,可以检测任意数量的点击,并遵守 ViewConfiguration class 中的各种超时和溢出参数。我已尝试将事件处理程序中的堆分配最小化。

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
         }
     }
}