【问题标题】:Android - Detect doubletap AND tripletap on viewAndroid - 在视图中检测双击和三次点击
【发布时间】:2015-03-01 16:00:13
【问题描述】:

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

【问题讨论】:

标签: android touch detect tap


【解决方案1】:

你可以试试这样的。

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

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

【讨论】:

  • 使用这个 checkstyle 插件建议:onTouch 应该在检测到点击时调用 View#performClick
  • 其实case MotionEvent.ACTION_UP后面需要用到这一行:v.performClick();
  • 您真的应该只在方法开始时调用 currentTimeMillis() 一次,将其设置为变量,并在整个过程中使用它。多次调用它可能会导致每次使用时的值略有不同并导致细微的错误
  • 这很有帮助,但请注意 getTapTimeout() 并非旨在作为点击的最大持续时间,而是“我们将等待查看触摸事件是点击还是点击的持续时间(以毫秒为单位)”滚动。如果用户在此间隔内没有移动,则认为是点击。 (引自developer.android.com/reference/android/view/…)。我发现上面的代码很难可靠地检测到轻拍,因为getTapTimeout() 太小而不能用作最大轻拍持续时间。使用 getLongPressTimeout() 对我来说效果更好。
【解决方案2】:

这是一个 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
         }
     }
}

【讨论】:

    【解决方案3】:

    我开发了 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());
                    }
            }
        }
    
    }
    

    【讨论】:

      【解决方案4】:

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

      Clicking the back button twice to exit an activity

      【讨论】:

      • 这与我问的无关
      • 它被称为逻辑,这就是他与你的关系,他还试图帮助@user2484359 .. 如果他有足够的代表,这将是一个评论
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-05-03
      • 1970-01-01
      • 1970-01-01
      • 2012-12-30
      • 2012-06-05
      • 1970-01-01
      • 2011-01-14
      相关资源
      最近更新 更多