Как отличить перемещение и нажатие на onTouchEvent ()? - PullRequest
68 голосов
/ 01 апреля 2012

В моем приложении мне нужно обрабатывать события перемещения и щелчка.

Щелчок - это последовательность одного действия ACTION_DOWN, нескольких действий ACTION_MOVE и одного действия ACTION_UP. Теоретически, если вы получаете событие ACTION_DOWN, а затем событие ACTION_UP - это означает, что пользователь только что щелкнул по вашему представлению.

Но на практике эта последовательность не работает на некоторых устройствах. На моем Samsung Galaxy Gio я получаю такие последовательности, просто нажимая на мой View: ACTION_DOWN, несколько раз на ACTION_MOVE, затем на ACTION_UP. То есть Я получаю несколько неожиданных срабатываний OnTouchEvent с кодом действия ACTION_MOVE. Я никогда (или почти никогда) не получаю последовательность ACTION_DOWN -> ACTION_UP.

Я также не могу использовать OnClickListener, потому что он не дает позицию щелчка. Так, как я могу обнаружить событие щелчка и отличить его от движения?

Ответы [ 10 ]

94 голосов
/ 04 апреля 2013

Вот еще одно решение, которое очень просто и не требует, чтобы вы беспокоились о перемещении пальца.Если вы основываете клик как простое расстояние, то как вы можете различить клик и длинный клик.

Вы могли бы добавить больше умов и включить пройденное расстояние, но мне еще не приходилосьслучай, когда расстояние, которое пользователь может пройти за 200 миллисекунд, должно составлять движение, а не щелчок.

setOnTouchListener(new OnTouchListener() {
    private static final int MAX_CLICK_DURATION = 200;
    private long startClickTime;

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN: {
                startClickTime = Calendar.getInstance().getTimeInMillis();
                break;
            }
            case MotionEvent.ACTION_UP: {
                long clickDuration = Calendar.getInstance().getTimeInMillis() - startClickTime;
                if(clickDuration < MAX_CLICK_DURATION) {
                    //click event has occurred
                }
            }
        }
        return true;
    }
});
49 голосов
/ 05 ноября 2013

Я получил лучшие результаты, учитывая:

  1. В основном, расстояние перемещалось между ACTION_DOWN и ACTION_UP событиями. Я хотел указать максимально допустимое расстояние в зависимых от плотности пикселях , а не в пикселях, чтобы лучше поддерживать различные экраны. Например, 15 DP .
  2. Во-вторых, длительность между событиями. Одна секунда показалось хорошим максимумом. (Некоторые люди «щелкают» довольно «скупо», то есть медленно; я все еще хочу это признать.)

Пример: * * один тысяча двадцать-одна

/**
 * Max allowed duration for a "click", in milliseconds.
 */
private static final int MAX_CLICK_DURATION = 1000;

/**
 * Max allowed distance to move during a "click", in DP.
 */
private static final int MAX_CLICK_DISTANCE = 15;

private long pressStartTime;
private float pressedX;
private float pressedY;

@Override
public boolean onTouchEvent(MotionEvent e) {
     switch (e.getAction()) {
        case MotionEvent.ACTION_DOWN: {
            pressStartTime = System.currentTimeMillis();                
            pressedX = e.getX();
            pressedY = e.getY();
            break;
        }
        case MotionEvent.ACTION_UP: {
            long pressDuration = System.currentTimeMillis() - pressStartTime;
            if (pressDuration < MAX_CLICK_DURATION && distance(pressedX, pressedY, e.getX(), e.getY()) < MAX_CLICK_DISTANCE) {
                // Click event has occurred
            }
        }     
    }
}

private static float distance(float x1, float y1, float x2, float y2) {
    float dx = x1 - x2;
    float dy = y1 - y2;
    float distanceInPx = (float) Math.sqrt(dx * dx + dy * dy);
    return pxToDp(distanceInPx);
}

private static float pxToDp(float px) {
    return px / getResources().getDisplayMetrics().density;
}

Идея здесь та же, что и в решении Gem , с такими отличиями:

Обновление (2015): также ознакомьтесь с Тонко настроенная версия Габриэля .

26 голосов
/ 29 апреля 2015

Взяв Отчёт Йоника Я построил немного более точно настроенную версию, которая не регистрируется как щелчок, если вы двигаете пальцем, а затем возвращаетесь на место, прежде чем отпустить:

Так вот мое решение:

/**
 * Max allowed duration for a "click", in milliseconds.
 */
private static final int MAX_CLICK_DURATION = 1000;

/**
 * Max allowed distance to move during a "click", in DP.
 */
private static final int MAX_CLICK_DISTANCE = 15;

private long pressStartTime;
private float pressedX;
private float pressedY;
private boolean stayedWithinClickDistance;

@Override
public boolean onTouchEvent(MotionEvent e) {
     switch (e.getAction()) {
        case MotionEvent.ACTION_DOWN: {
            pressStartTime = System.currentTimeMillis();                
            pressedX = e.getX();
            pressedY = e.getY();
            stayedWithinClickDistance = true;
            break;
        }
        case MotionEvent.ACTION_MOVE: {
            if (stayedWithinClickDistance && distance(pressedX, pressedY, e.getX(), e.getY()) > MAX_CLICK_DISTANCE) {
                stayedWithinClickDistance = false;
            }
            break;
        }     
        case MotionEvent.ACTION_UP: {
            long pressDuration = System.currentTimeMillis() - pressStartTime;
            if (pressDuration < MAX_CLICK_DURATION && stayedWithinClickDistance) {
                // Click event has occurred
            }
        }     
    }
}

private static float distance(float x1, float y1, float x2, float y2) {
    float dx = x1 - x2;
    float dy = y1 - y2;
    float distanceInPx = (float) Math.sqrt(dx * dx + dy * dy);
    return pxToDp(distanceInPx);
}

private static float pxToDp(float px) {
    return px / getResources().getDisplayMetrics().density;
}
15 голосов
/ 04 августа 2016

Используйте детектор, он работает, и он не будет подниматься при перетаскивании

Поле:

private GestureDetector mTapDetector;

Инициализировать:

mTapDetector = new GestureDetector(context,new GestureTap());

Внутренний класс:

class GestureTap extends GestureDetector.SimpleOnGestureListener {
    @Override
    public boolean onDoubleTap(MotionEvent e) {

        return true;
    }

    @Override
    public boolean onSingleTapConfirmed(MotionEvent e) {
        // TODO: handle tap here
        return true;
    }
}

onTouch:

@Override
public boolean onTouch(View v, MotionEvent event) {
    mTapDetector.onTouchEvent(event);
    return true;
}

Наслаждайтесь:)

6 голосов
/ 28 июля 2013

Чтобы получить наиболее оптимизированное распознавание события клика, мы должны рассмотреть 2 вещи:

  1. Разница во времени между ACTION_DOWN и ACTION_UP.
  2. Разница между x, y при касании пользователя и отпускании пальца.

На самом деле я объединяю логику Стимсони и Нетираджана

Итак, вот мое решение:

        view.setOnTouchListener(new OnTouchListener() {

        private final int MAX_CLICK_DURATION = 400;
        private final int MAX_CLICK_DISTANCE = 5;
        private long startClickTime;
        private float x1;
        private float y1;
        private float x2;
        private float y2;
        private float dx;
        private float dy;

        @Override
        public boolean onTouch(View view, MotionEvent event) {
            // TODO Auto-generated method stub

                    switch (event.getAction()) 
                    {
                        case MotionEvent.ACTION_DOWN: 
                        {
                            startClickTime = Calendar.getInstance().getTimeInMillis();
                            x1 = event.getX();
                            y1 = event.getY();
                            break;
                        }
                        case MotionEvent.ACTION_UP: 
                        {
                            long clickDuration = Calendar.getInstance().getTimeInMillis() - startClickTime;
                            x2 = event.getX();
                            y2 = event.getY();
                            dx = x2-x1;
                            dy = y2-y1;

                            if(clickDuration < MAX_CLICK_DURATION && dx < MAX_CLICK_DISTANCE && dy < MAX_CLICK_DISTANCE) 
                                Log.v("","On Item Clicked:: ");

                        }
                    }

            return  false;
        }
    });
4 голосов
/ 20 февраля 2013

Ниже код решит вашу проблему


    @Override
        public boolean onTouchEvent(MotionEvent event) {
            switch(event.getAction()) {
                case(MotionEvent.ACTION_DOWN):
                    x1 = event.getX();
                    y1 = event.getY();
                    break;
                case(MotionEvent.ACTION_UP): {
                    x2 = event.getX();
                    y2 = event.getY();
                    dx = x2-x1;
                    dy = y2-y1;

                if(Math.abs(dx) > Math.abs(dy)) 
                {
                    if(dx>0) move(1); //right
                    else if (dx == 0) move(5); //click
                    else move(2); //left
                } 
                else 
                {
                    if(dy>0) move(3); // down
                    else if (dy == 0) move(5); //click
                    else move(4); //up
                }
                }
            }
            return true;
        }

3 голосов
/ 01 апреля 2012

ACTION_DOWN очень сложно без ACTION_MOVE. Малейшее движение пальца по экрану в месте, отличном от того, где произошло первое касание, вызовет событие MOVE. Кроме того, я считаю, что изменение давления пальца также вызовет событие MOVE. Я бы использовал оператор if в методе Action_Move, чтобы попытаться определить расстояние, на которое произошло движение от первоначального движения DOWN. если перемещение произошло за пределами заданного радиуса, произойдет действие MOVE. Это, вероятно, не самый лучший и эффективный способ сделать то, что вы пытаетесь, но это должно сработать.

1 голос
/ 09 января 2017

Используя ответ Gil SH, я улучшил его, добавив onSingleTapUp() вместо onSingleTapConfirmed().Это намного быстрее и не будет щелкать по представлению, если перетаскивать / перемещать.

GestureTap:

public class GestureTap extends GestureDetector.SimpleOnGestureListener {
    @Override
    public boolean onSingleTapUp(MotionEvent e) {
        button.performClick();
        return true;
    }
}

Используйте его как:

final GestureDetector gestureDetector = new GestureDetector(getApplicationContext(), new GestureTap());
button.setOnTouchListener(new View.OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        gestureDetector.onTouchEvent(event);
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                return true;
            case MotionEvent.ACTION_UP:
                return true;
            case MotionEvent.ACTION_MOVE:
                return true;
        }
        return false;
    }
});
1 голос
/ 02 декабря 2016

В дополнение к ответам, приведенным выше, если вы хотите реализовать действия как onClick, так и Drag, то мой код ниже может вам помочь.Получение помощи от @Stimsoni:

     // assumed all the variables are declared globally; 

    public boolean onTouch(View view, MotionEvent event) {

      int MAX_CLICK_DURATION = 400;
      int MAX_CLICK_DISTANCE = 5;


        switch (event.getAction())
        {
            case MotionEvent.ACTION_DOWN: {
                long clickDuration1 = Calendar.getInstance().getTimeInMillis() - startClickTime;


                    startClickTime = Calendar.getInstance().getTimeInMillis();
                    x1 = event.getX();
                    y1 = event.getY();


                    break;

            }
            case MotionEvent.ACTION_UP:
            {
                long clickDuration = Calendar.getInstance().getTimeInMillis() - startClickTime;
                x2 = event.getX();
                y2 = event.getY();
                dx = x2-x1;
                dy = y2-y1;

                if(clickDuration < MAX_CLICK_DURATION && dx < MAX_CLICK_DISTANCE && dy < MAX_CLICK_DISTANCE) {
                    Toast.makeText(getApplicationContext(), "item clicked", Toast.LENGTH_SHORT).show();
                    Log.d("clicked", "On Item Clicked:: ");

               //    imageClickAction((ImageView) view,rl);
                }

            }
            case MotionEvent.ACTION_MOVE:

                long clickDuration = Calendar.getInstance().getTimeInMillis() - startClickTime;
                x2 = event.getX();
                y2 = event.getY();
                dx = x2-x1;
                dy = y2-y1;

                if(clickDuration < MAX_CLICK_DURATION && dx < MAX_CLICK_DISTANCE && dy < MAX_CLICK_DISTANCE) {
                    //Toast.makeText(getApplicationContext(), "item clicked", Toast.LENGTH_SHORT).show();
                  //  Log.d("clicked", "On Item Clicked:: ");

                    //    imageClickAction((ImageView) view,rl);
                }
                else {
                    ClipData clipData = ClipData.newPlainText("", "");
                    View.DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(view);

                    //Toast.makeText(getApplicationContext(), "item dragged", Toast.LENGTH_SHORT).show();
                    view.startDrag(clipData, shadowBuilder, view, 0);
                }
                break;
        }

        return  false;
    }
1 голос
/ 11 мая 2016

Если вы хотите реагировать только на клик, используйте:

if (event.getAction() == MotionEvent.ACTION_UP) {

}
...