Jak odróżnić move I click w onTouchEvent ()?

W mojej aplikacji muszę obsługiwać zarówno zdarzenia move, jak i click.

Kliknięcie jest sekwencją jednej akcji ACTION_DOWN, kilku akcji ACTION_MOVE i jednej akcji ACTION_UP. Teoretycznie, jeśli otrzymasz Zdarzenie ACTION_DOWN, a następnie zdarzenie ACTION_UP - oznacza to, że użytkownik właśnie kliknął Twój widok.

Ale w praktyce ta sekwencja nie działa na niektórych urządzeniach. Na moim Samsungu Galaxy Gio dostaję takie sekwencje po prostu klikając Mój widok: ACTION_DOWN, kilka razy ACTION_MOVE, a następnie ACTION_UP. Czyli dostaję kilka nieoczekiwanych uruchomień OnTouchEvent z kodem akcji ACTION_MOVE. Nigdy (lub prawie nigdy) nie dostaję sekwencji ACTION_DOWN - > ACTION_UP.

Nie mogę również użyć OnClickListener, ponieważ nie podaje pozycji kliknięcia. W jaki sposób mogę wykryć Zdarzenie kliknięcia i odróżnić je od ruchu?

Author: Jonik, 2012-04-01

10 answers

Oto inne rozwiązanie, które jest bardzo proste i nie wymaga martwienia się o ruch palca. Jeśli bazujesz kliknięcie jako po prostu odległość przesunięta, to jak możesz odróżnić kliknięcie i długie kliknięcie.

Możesz dodać więcej sprytu do tego i uwzględnić przesuniętą odległość, ale jeszcze nie natknąłem się na przypadek, w którym odległość, którą użytkownik może przenieść w 200 milisekund, powinna stanowić ruch, a nie kliknięcie.

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;
    }
});
 116
Author: Stimsoni,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-10-22 17:09:16

Najlepsze wyniki uzyskałem biorąc pod uwagę:

  1. przede wszystkim, odległość przesunięta między ACTION_DOWN i ACTION_UP zdarzenia. Chciałem określić maksymalną dozwoloną odległość w density-indepenent pixels zamiast pikseli, aby lepiej obsługiwać różne ekrany. Na przykład 15 DP.
  2. wtórnie, Czas trwania pomiędzy wydarzeniami. jedna sekunda wydawało się dobre maksimum. (Niektórzy "klikają" dość "dokładnie", tzn. powoli; ja nadal chcę rozpoznaj to.)

Przykład:

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

Idea tutaj jest taka sama jak w rozwiązanie Gem , z tymi różnicami:

Aktualizacja (2015): sprawdź również dopracowaną wersję tego Gabriela.

 55
Author: Jonik,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:34:38

Biorąc prowadzenie Jonika zbudowałem nieco bardziej dopracowaną wersję, która nie rejestruje się jako kliknięcie, jeśli poruszysz palcem i wrócisz do miejsca przed puszczeniem:

Oto moje rozwiązanie:

/**
 * 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;
}
 29
Author: Gabriel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:26:22

Użyj detektora, działa i nie podniesie się w przypadku przeciągania

Pole:

private GestureDetector mTapDetector;

Initialize:

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

Klasa wewnętrzna:

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

Enjoy:)

 21
Author: Gil SH,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-11-01 14:05:15

Aby uzyskać jak najbardziej zoptymalizowane rozpoznawanie zdarzenia kliknięcia musimy wziąć pod uwagę 2 rzeczy:

  1. różnica czasu między ACTION_DOWN i ACTION_UP.
  2. różnica między x,y, gdy użytkownik dotyka i kiedy zwalnia palec.

Właściwie to łączę logikę podaną przez Stimsoniego i Neethirajana

Oto moje rozwiązanie:

        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
Author: Gem,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-07-28 19:12:42

Używając Gil SH answer, ulepszyłem ją implementując onSingleTapUp() zamiast onSingleTapConfirmed(). Jest znacznie szybszy i nie kliknie widoku, jeśli zostanie przeciągnięty/przeniesiony.

GestureTap:

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

Użyj go jak:

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;
    }
});
 6
Author: Hussein El Feky,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-01-08 23:53:34

Poniższy kod rozwiąże twój problem


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

 4
Author: Neethirajan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-02-20 14:39:16

Jest bardzo trudno, aby ACTION_DOWN wystąpiło bez action_move. Najmniejsze poruszenie palca na ekranie w innym miejscu niż miejsce pierwszego dotknięcia spowoduje wywołanie zdarzenia MOVE. Ponadto, wierzę, że zmiana nacisku palców spowoduje również zdarzenie MOVE. Użyłbym instrukcji if W metodzie Action_Move, aby spróbować określić odległość od pierwotnego ruchu w dół. jeśli ruch nastąpił poza określonym promieniem, Twój ruch akcja nastąpi. Prawdopodobnie nie jest to najlepszy, zasobooszczędny sposób, aby zrobić to, co starasz się, ale powinien działać.

 3
Author: testingtester,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-04-01 15:46:06

Jeśli chcesz reagować tylko na kliknięcie, użyj:

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

}
 1
Author: YTerle,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-05-11 10:27:01

Dodając do powyższych odpowiedzi, jeśli chcesz zaimplementować zarówno akcje onClick, jak i Drag, mój kod poniżej możesz. @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
Author: Rushi Ayyappa,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-12-02 06:11:32