Come distinguere tra spostare e fare clic in onTouchEvent()?

Nella mia applicazione, ho bisogno di gestire sia spostare e fare clic su eventi.

Un clic del mouse è una sequenza di uno ACTION_DOWN azione, diversi ACTION_MOVE azioni e una ACTION_UP azione. In teoria, se si ottiene un ACTION_DOWN evento e, quindi, un ACTION_UP evento – che significa che l’utente ha appena cliccato vostro punto di Vista.

Ma in pratica, questa sequenza non funziona su alcuni dispositivi. Sul mio Samsung Galaxy Gio ottenere tali sequenze quando, cliccando solo il mio punto di Vista: ACTION_DOWN, più volte ACTION_MOVE, quindi ACTION_UP. I. e. Ho alcuni standard forse non all’altezza OnTouchEvent fuochi con ACTION_MOVE codice di azione. Non ho mai (o quasi mai) ottenere la sequenza di ACTION_DOWN -> ACTION_UP.

Anche io non riesco a utilizzare OnClickListener perché non fornisce la posizione di un clic. Così come è possibile rilevare evento click e la differenziano da spostare?

  • hai provato usando onTouch invece di onTouchEvent, così facendo si avrà un riferimento alla vista, così è possibile registrare i valori e vedere se il clic che è ACTION_DOWN e ACTION_UP vengono richiamati o non…
InformationsquelleAutor Anastasia | 2012-04-01

 

10 Replies
  1. 98

    Ecco un’altra soluzione è molto semplice e non ha bisogno di preoccuparsi, il dito viene spostato. Se si basa un clic semplicemente la distanza di spostamento quindi come si può distinguere un clic e un clic lungo.

    Si potrebbe mettere più fanno male in questo e includono la distanza percorsa, ma io sono ancora imbattuto in un caso in cui la distanza che un utente può spostare in 200 millisecondi dovrebbe costituire un movimento opposto in un click.

    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;
        }
    });
    • Perché non utilizzare ViewConfiguration.getLongPressTimeout() come durata massima di un click?
    • Per premere a lungo si potrebbe sicuramente utilizzare questo metodo, ma questo è solo un clic standard.
    • perfetto
    • Effettivamente potrebbe essere migliore per utilizzare l’evento.getEventTime() -evento.getDownTime() per calcolare fare clic durata
    • Perché dovrebbe essere meglio?
    • perché non c’è bisogno di avere un separato campo e il lavoro con il calendario. Hai solo bisogno di sottrarre un numero da un altro.
    • Utilizzare System.currentTimeMillis() invece di Calendar.getInstance().getTimeInMillis()
    • Utilizzando la distanza tra ACTION_DOWN e ACTION_UP è più affidabile.
    • i commenti mi hanno aiutato. Siamo in grado di controllare la stessa cosa con l’evento.getEventTime() -evento.getDownTime() nel metodo onClick senza l’utilizzo di calendario.

  2. 49

    Ho ottenuto i migliori risultati, prendendo in considerazione:

    1. Principalmente, il distanza spostato tra ACTION_DOWN e ACTION_UP eventi. Ho voluto specificare il max consentito distanza in densità indepenent pixel invece di pixel, per supportare meglio le diverse schermate. Per esempio, 15 DP.
    2. Secondariamente, il durata tra gli eventi. Un secondo sembrato il massimo. (Alcune persone “fare clic su” abbastanza “thorougly”, cioè lentamente; ho ancora voglia di riconoscere.)

    Esempio:

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

    L’idea qui è la stessa Gemma soluzione, con queste differenze:

    • Calcola l’effettivo Distanza euclidea tra i due punti.
    • Questo utilizza dp invece di px.

    Aggiornamento (2015): anche verificare Gabriel versione ottimizzata di questo.

    • Ottima risposta!!! Questo ha consentito di risparmiare un po ‘ di ricerca, grazie !
    • Una domanda per questa risposta, pensi che sarebbe meglio registrare la distanza dal di dentro evento move? Se comincio con il mio dito in vista, ho potuto scorrere fino alla parte superiore dello schermo e verso il basso all’interno di un secondo e sarebbe ancora contare un click. Se questo è stato registrato all’interno dell’azione di spostamento, è possibile impostare un valore booleano se caso mai superato il clic regione.
    • E ‘stato un po’, ma penso che ho appena trovato questo per essere “abbastanza buono” per le mie esigenze. Sentitevi liberi di sperimentare se ottieni risultati migliori con questo approccio, e se è così, prendere in considerazione la pubblicazione di una nuova risposta 🙂
    • Penso che ho avuto un po ‘ più specifico caso di utilizzo di più persone, ho un punto di vista che può essere trascinato verso l’alto dalla parte inferiore alla parte superiore dello schermo, o l’intestazione potrebbe essere cliccato per attivare la posizione. Questo significava che, se si trascinò fino poi di nuovo giù in fretta, era ancora riconoscere click. Grazie, però, la soluzione è stata di quasi il 100% di quello che mi serviva, ho appena fatto alcune modifiche per ottenere me c’. Vi posto il mio codice dopo stasera come alternativa di risposta per le persone che hanno bisogno di un po ‘ più di precisione.
    • Ciao Jonik, nel pxToDp() funzione, dove è getResources() dovrebbe essere definito? Sembra che il codice è destinato a essere messo in un custom sottoclasse di View così sembra essere cercando di accesso View#getResources() che non può dal pxToDp() è statico mentre View#getResources() non è. Forse rimuovere il modificatore static o passare Contesto come parametro?
    • Oh, e il onTouchEvent() manca un valore di ritorno. Sarebbe utile se ci fai vedere cosa scrivere c’ (return true;? return false;? return super.onTouchEvent(e);?)
    • basta sostituire context.getResouces() con Resources.getSystem()
    • implementa OnTouchListener Interfaccia, ma modificare onTouchEvent(MotionEvent e) con onTouchEvent(View v,MotionEvent e) anche voi potete v. getCOntext() per pxToDP

  3. 26

    Prendendo Jonik di piombo ho costruito un po ‘ più sintonizzato versione, che non registra come fare se si sposta il dito e poi tornare a posto prima di lasciare andare:

    Ecco la mia soluzione:

    /**
     * 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. 16

    Utilizzare il rilevatore funziona e non aumenta in caso di trascinamento

    Campo:

    private GestureDetector mTapDetector;

    Inizializzare:

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

    Classe interna:

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

    Godere 🙂

    • Soluzione preferita. Tuttavia, se voglio usare un Touch/Gesto ascoltatore per più viste, devo tenere traccia dell’ultima vista durante l’ultima onTouch evento così mi sa che vista la onSingleTapConfirmed evento è stato da. Sarebbe bello se il MotionEvent memorizzato la vista per voi.
    • Per chi può avere problemi di esecuzione di questo codice, è necessario impostare il valore di ritorno della funzione onTouch(View v, MotionEvent evento) a “true” per fare questo lavoro. In realtà, penso che si consumano, gli eventi e che deve restituire true.
    • Bello. Funziona. Grazie
    • Soluzione perfetta la Ringrazio molto, Veramente è una bella soluzione…
  5. 6

    Per ottenere più ottimizzato il riconoscimento di evento click Dobbiamo considerare 2 cose:

    1. Differenza di tempo tra ACTION_DOWN e ACTION_UP.
    2. Differenza tra x,y quando l’utente touch e quando rilasciare il dito.

    In realtà ho combinare la logica data da Stimsoni e Neethirajan

    Ecco la mia soluzione:

            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;
            }
        });
    • Molto bello, per me ha funzionato, grazie.
    • +1, buon approccio. Sono andato con un soluzione simile, eccetto l’uso di dp invece di px per MAX_CLICK_DISTANCE, e il calcolo della distanza di un po ‘ diverso.
    • Che è buono, ma è necessario aggiungere la Matematica.abs dx e dy..
    • queste variabile dichiarata di cui sopra devono essere dichiarati a livello globale.
    • Nella maggior parte dei casi si imposta il onTouchListener una volta sulla vista. Il setOnTouchListener() prende anonimo classe di riferimento. Dove le variabili sono globali all’interno della classe. Penso che funzionerà a meno che a qualcuno di chiamare setOnTouchListener() di nuovo con altra istanza di ascoltatore.
    • il mio touchListener è quella interiore e di queste variabili ha distrutto per la seconda volta.Ho avuto modo di dichiarare in tutto il mondo, e questo ha funzionato.

  6. 4

    Di seguito il codice per risolvere il problema

     
    @Override 
    public boolean onTouchEvent(MotionEvent event) { 
    switch(event.getAction()) { 
    caso(MotionEvent.ACTION_DOWN): 
    x1 = evento.getX(); 
    y1 = evento.getY(); 
    break; 
    caso(MotionEvent.ACTION_UP): { 
    x2 = evento.getX(); 
    y2 = evento.getY(); 
    dx = x2-x1; 
    dy = y2-y1; 
    
    if(Math.abs(dx) > per la Matematica.abs(dy)) 
    { 
    se(dx>0) spostamento(1); //a destra 
    else if (x == 0) spostamento(5); //fare clic su 
    altra mossa(2); //a sinistra 
    } 
    altro 
    { 
    se(dy>0) spostare(3); //down 
    else if (f == 0) spostamento(5); //fare clic su 
    altro move(4); //fino 
    } 
    } 
    } 
    return true; 
    } 
    
    
    • si prega di spiegare il codice…
  7. 3

    È molto difficile per un ACTION_DOWN a verificarsi senza un ACTION_MOVE che si verificano. La minima contrazione del dito sullo schermo in un punto diverso rispetto a quello in cui il primo contatto si è verificato attiverà l’evento di SPOSTAMENTO. Inoltre, credo che un cambiamento nella pressione di un dito, anche innescare il movimento di evento. Vorrei utilizzare un’istruzione if in Action_Move metodo di prova per determinare la distanza lo spostamento si è verificato dall’originale movimento verso il BASSO. se la mossa si è verificato al di fuori di qualche raggio, la vostra azione di movimento si sarebbe verificato. Probabilmente non è il migliore, efficiente in termini di risorse modo per fare ciò che il vostro cercando ma dovrebbe funzionare.

    • Grazie, ho anche previsto qualcosa del genere, ma il pensiero che qualcuno conosce altre soluzioni. Hai anche expirienced tale comportamento (down->move->, mentre cliccando)?
    • Sì, sto lavorando su un gioco che ha coinvolto il multitouch e ho fatto qualche test approfonditi per quanto riguarda poitners e e Move_Events. Questa è stata una delle conclusioni che mi è venuta durante il test (che Action_Down evento diventa rapidamente cambiato Action_Move. Contento di aver potuto aiutare.
    • Ok, va bene che non sono il solo 🙂 non ho trovato qualcuno con questo problema su stackoverflow o da qualche altra parte. Grazie.
    • Su che dispositivi hai visto un tale comportamento? Ho notato questo su Samsung Galaxy Ace e Gio.
    • L’ho visto personalmente su un Samsung Captivate e un Galaxy S II, ma immagino che la sua, probabilmente come questa per quasi tutti i dispositivi
  8. 1

    Se si vuole reagire a scegliere, utilizzare:

    if (event.getAction() == MotionEvent.ACTION_UP) {
    
    }
  9. 1

    Aggiunta alle risposte di cui sopra ,se si desidera implementare sia onClick e Trascinare le azioni quindi il mio codice qui di seguito potete ragazzi. L’assunzione di alcuni dei aiutare da @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. 1

    Utilizzando Gil SH risposta, ho migliorato mediante l’attuazione di onSingleTapUp() piuttosto che onSingleTapConfirmed(). È molto più veloce e non fare clic su visualizzazione, se trascinato/spostato.

    GestureTap:

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

    Come:

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

Lascia un commento