Android - 在视图上检测双击和三击

时间:2015-01-03 16:45:50

标签: android touch detect tap

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

5 个答案:

答案 0 :(得分:34)

你可以尝试这样的事情。

虽然我一般建议不要使用三重点击作为模式,因为它不是用户通常习惯的东西,所以除非它正确地传达给他们,否则大多数人可能永远不会知道他们可以三重点击视图。实际上在移动设备上进行双重录制也是如此,在该环境中进行交互并不总是直观的方式。

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

答案 1 :(得分:2)

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

}

答案 2 :(得分:0)

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

Clicking the back button twice to exit an activity

答案 3 :(得分:0)

这里是Kotlin的实现,可以检测任意数量的抽头,并遵守ViewConfiguration类中发现的各种超时和倾斜参数。我试图最小化事件处理程序中的堆分配。

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

答案 4 :(得分:-4)

Android不支持双击,这是有原因的。当您触摸Android屏幕时,Android会在短时间内向OS发送一系列触发器,表示x次,这被解释为单击。如果你进行双击,你将在几乎相同的时间跨度内再次发送相同的x +/- d个触发器。所以这两个水龙头都会混淆不清,而Android很难理解它是双击还是单击。因此它有onlongclick听众。