Android - Detect doubletap AND tripletap on view

后端 未结 4 1809
渐次进展
渐次进展 2021-02-03 10:07

I\'ve been trying to build a tap detector that can detect both double and tripe tap. After my efforts failed I searched a long time on the net to find something ready to use but

相关标签:
4条回答
  • 2021-02-03 10:33

    Use the view listener to detect first tap on the view object,then see how to manage twice back pressed to exit an activity on stackoverflow.com (use a handler post delay).

    Clicking the back button twice to exit an activity

    0 讨论(0)
  • 2021-02-03 10:46

    Here is a Kotlin implementation that can detect an arbitrary number of taps, and respects the various timeout and slop parameters found in the ViewConfiguration class. I have tried to minimise heap allocations in the event handlers.

    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
             }
         }
    }
    
    0 讨论(0)
  • 2021-02-03 10:48

    I developed an advanced version of the Iorgu solition that suits better my needs:

    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());
                    }
            }
        }
    
    }
    
    0 讨论(0)
  • 2021-02-03 10:58

    You can try something like this.

    Though I would generally recommend against using triple taps as a pattern as it is not something users are generally used to, so unless it's properly communicated to them, most might never know they can triple tap a view. Same goes for double taping actually on mobile devices, it's not always an intuitive way to interact in that environment.

    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;
        }
    });
    
    0 讨论(0)
提交回复
热议问题