Подтвердить что ты не робот

Как различать перемещение и щелчок в onTouchEvent()?

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

Щелчок представляет собой последовательность из одного действия 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, потому что он не дает позиции щелчка. Итак, как я могу определить событие click и отличать его от перемещения?

4b9b3361

Ответ 1

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

Вы можете добавить в него больше умений и включить перемещение расстояния, но мне еще предстоит встретить экземпляр, когда расстояние, которое пользователь может перемещать за 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;
    }
});

Ответ 2

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

  • В основном, расстояние перемещалось между событиями ACTION_DOWN и ACTION_UP. Я хотел бы указать максимальное допустимое расстояние в плотности нечувствительных пикселей, а не пикселей, чтобы лучше поддерживать разные экраны. Например, 15 DP.
  • Вторично продолжительность между событиями. Одна секунда показала хороший максимум. (Некоторые люди "click" довольно "тощие", т.е. Медленно, я все еще хочу это признать.)

Пример:

/**
 * 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 solution, с этими отличиями:

Обновление (2015): также проверьте Габриэль точно настроенную версию этого.

Ответ 3

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

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

/**
 * 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;
}

Ответ 4

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

поле:

private GestureDetector mTapDetector;

Initialize:

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

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

Ответ 5

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

  • Разница во времени между ACTION_DOWN и ACTION_UP.
  • Разница между 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;
        }
    });

Ответ 6

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

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

Ответ 7

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

Ответ 8

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

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

}

Ответ 9

Добавляя к вышеуказанным ответам, если вы хотите реализовать как действия 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;
    }

Ответ 10

Используя ответ 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;
    }
});