Pulsante lasciato nello stato evidenziato con touchListener e clickListener


10

Sto riscontrando un problema con il mio pulsante che rimane in uno stato evidenziato, dopo aver effettuato le seguenti operazioni:

public class MainActivity extends AppCompatActivity {

    @SuppressLint("ClickableViewAccessibility")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        AppCompatButton button = (AppCompatButton) findViewById(R.id.mybutton);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.d("Test", "calling onClick");
            }
        });
        button.setOnTouchListener(new View.OnTouchListener() {

            public boolean onTouch(View v, MotionEvent event) {
                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN: {
                        v.getBackground().setColorFilter(0xe0f47521,PorterDuff.Mode.SRC_ATOP);
                        v.invalidate();
                        break;
                    }
                    case MotionEvent.ACTION_UP: {
                        v.getBackground().clearColorFilter();
                        v.invalidate();
                        v.performClick();
                        Log.d("Test", "Performing click");
                        return true;
                    }
                }
                return false;
            }
        });

    }
}

Per quanto riguarda il codice sopra, quando lo utilizzo, mi aspetto che il clic sul pulsante venga gestito dal tocco e, restituendo "vero", la gestione dovrebbe arrestarsi sul touchListener.

Ma non è così. Il pulsante rimane in uno stato evidenziato, anche se viene chiamato il clic.

Quello che ottengo è:

Test - calling onClick
Test - Performing click

d'altra parte, se sto usando il seguente codice, si fa clic sul pulsante, le stesse stampe, ma il pulsante non finisce bloccato in uno stato evidenziato:

public class MainActivity extends AppCompatActivity {

    @SuppressLint("ClickableViewAccessibility")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        AppCompatButton button = (AppCompatButton) findViewById(R.id.mybutton);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.d("Test", "calling onClick");
            }
        });
        button.setOnTouchListener(new View.OnTouchListener() {

            public boolean onTouch(View v, MotionEvent event) {
                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN: {
                        v.getBackground().setColorFilter(0xe0f47521,PorterDuff.Mode.SRC_ATOP);
                        v.invalidate();
                        break;
                    }
                    case MotionEvent.ACTION_UP: {
                        v.getBackground().clearColorFilter();
                        v.invalidate();
                        // v.performClick();
                        Log.d("Test", "Performing click");
                        return false;
                    }
                }
                return false;
            }
        });

    }
}

Sono un po 'confuso su quale sia la catena di responder all'evento touch. La mia ipotesi è che sia:

1) TouchListener

2) ClickListener

3) ParentViews

Qualcuno può confermare anche questo?


Quello che realmente vuoi fare, è maneggiarlo al tocco o cambiare colore semplicemente premendo?
Haider Saleem,

Voglio eseguire un po 'di logica in contatto e quindi chiamare performClick e per non cambiare il colore del pulsante.
Whitebear,

@BiancoBear Si prega di controllare la risposta qui sotto. Forse posso aggiungere ulteriori informazioni.
GensaGames

Questo potrebbe aiutarti a comprendere il flusso di eventi tattili. Non è chiaro cosa vuoi che accada. Vuoi un gestore di clic e fare il clic di esecuzione? Vuoi che il pulsante passi dal suo colore iniziale allo stato impostato dal filtro colore e poi al suo colore iniziale?
Cheticamp,

Lasciami spiegare cosa intendo. Ho un touchListener e un clickListener sul pulsante. Il tocco precede il clic per priorità e restituisce vero se ha gestito l'evento, il che significa che nessun altro dovrebbe gestirlo. Questo è esattamente ciò che sto facendo attraverso il tocco, gestendo il clic e restituendo true, ma il pulsante rimane ancora evidenziato anche se viene chiamato il listener onclick e il flusso viene eseguito correttamente.
Whitebear,

Risposte:


10

Tali personalizzazioni non richiedono modifiche a livello di programmazione. Puoi farlo semplicemente in xmlfile. Prima di tutto, elimina completamente il setOnTouchListenermetodo fornito onCreate. Quindi, definire un colore di selezione nella res/colordirectory come il seguente. (se la directory non esiste, creala)

res / colore / button_tint_color.xml

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:color="#e0f47521" android:state_pressed="true" />
    <item android:color="?attr/colorButtonNormal" android:state_pressed="false" />
</selector>

Ora, impostalo sul pulsante app:backgroundTint :

<androidx.appcompat.widget.AppCompatButton
    android:id="@+id/mybutton"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Button"
    app:backgroundTint="@color/button_tint_color" />


Risultato visivo:

inserisci qui la descrizione dell'immagine



EDITED: (per risolvere il problema dell'evento touch)

Da un punto di vista generale, il flusso dell'evento touch inizia dal Activity , quindi scorre verso il layout (dal layout principale a quello secondario) e quindi verso le viste. (Flusso LTR nella figura seguente)

inserisci qui la descrizione dell'immagine

Quando l'evento touch raggiunge la vista target, la vista può gestire l'evento quindi decidere di passarlo ai layout / attività precedenti o meno (ritorno falsedi trueinonTouch metodo). (Flusso RTL nella figura sopra)

Ora diamo un'occhiata al codice sorgente di View per ottenere una visione più approfondita dei flussi di eventi touch. Dando un'occhiata all'implementazione di dispatchTouchEvent, vedremo che se imposti un OnTouchListeneralla vista e poi ritorni truenel suo onTouchmetodo, il onTouchEventdella vista non verrà chiamato.

public boolean dispatchTouchEvent(MotionEvent event) {
    // removed lines for conciseness...
    boolean result = false;    
    // removed lines for conciseness...
    if (onFilterTouchEventForSecurity(event)) {
        // removed lines for conciseness...
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) { // <== right here!
            result = true;
        }
        if (!result && onTouchEvent(event)) {
            result = true;
        }
    }
    // removed lines for conciseness...
    return result;
}

Ora, guarda il onTouchEventmetodo in cui si trova l'azione dell'evento MotionEvent.ACTION_UP. Vediamo che l'azione perform-click avviene lì. Quindi, ritornando truein OnTouchListener's onTouche di conseguenza non chiamando il onTouchEvent, causa non chiamare il OnClickListener' sonClick .

C'è un altro problema con il non chiamare il onTouchEvent, che è legato allo stato premuto e che hai menzionato nella domanda. Come possiamo vedere nel seguente blocco di codice, c'è un'istanza di UnsetPressedStatequelle chiamate quando viene eseguita. Il risultato della mancata chiamata è che la vista si blocca nello stato premuto e il suo stato disegnabile non cambia. setPressed(false)setPressed(false)

public boolean onTouchEvent(MotionEvent event) {
    // removed lines for conciseness...
    if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
        switch (action) {
            case MotionEvent.ACTION_UP:
                // removed lines for conciseness...
                if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                    // removed lines for conciseness...
                    if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                        // removed lines for conciseness...
                        if (!focusTaken) {
                            // Use a Runnable and post this rather than calling
                            // performClick directly. This lets other visual state
                            // of the view update before click actions start.
                            if (mPerformClick == null) {
                                mPerformClick = new PerformClick();
                            }
                            if (!post(mPerformClick)) {
                                performClickInternal();
                            }
                        }
                    }
                    if (mUnsetPressedState == null) {
                        mUnsetPressedState = new UnsetPressedState();
                    }
                    if (prepressed) {
                        postDelayed(mUnsetPressedState,
                                ViewConfiguration.getPressedStateDuration());
                    } else if (!post(mUnsetPressedState)) {
                        // If the post failed, unpress right now
                        mUnsetPressedState.run();
                    }
                    // removed lines for conciseness...
                }
                // removed lines for conciseness...
                break;
            // removed lines for conciseness...
        }
        return true;
    }
    return false;
}

UnsetPressedState :

private final class UnsetPressedState implements Runnable {
    @Override
    public void run() {
        setPressed(false);
    }
}


Per quanto riguarda le descrizioni di cui sopra, puoi modificare il codice chiamando setPressed(false)te stesso per cambiare lo stato di disegno in cui è l'azione dell'evento MotionEvent.ACTION_UP:

button.setOnTouchListener(new View.OnTouchListener() {

    public boolean onTouch(View v, MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN: {
                v.getBackground().setColorFilter(0xe0f47521,PorterDuff.Mode.SRC_ATOP);
                v.invalidate();
                break;
            }
            case MotionEvent.ACTION_UP: {
                v.getBackground().clearColorFilter();
                // v.invalidate();
                v.setPressed(false);
                v.performClick();
                Log.d("Test", "Performing click");
                return true;
            }
        }
        return false;
    }
});

Questo non è quello che sto cercando il mio amico. Sto cercando di capire il cambiamento nel comportamento in entrambe le situazioni che ho descritto sopra. Se c'è qualcosa che posso elaborare, per favore fatemelo sapere. Non voglio eliminare il gestore touch né il gestore clic. Si prega di controllare il mio commento alla risposta sopra.
Whitebear,

@ Whitehear: ho aggiornato la risposta. Per favore, dai un'occhiata, amico.
aminografia

Questa è una buona risposta dettagliata e la accetterei. Alcuni suggerimenti da modificare: Now, look at the onTouchEvent method where the event action is MotionEvent.ACTION_UP. We see that perform-click action happens there. So, returning true in the OnTouchListener's onTouch and consequently not calling the onTouchEvent, causes not calling the OnClickListener's onClick.Nel mio caso viene chiamato onClick. mUnsetPressedState controlla se è null prima di impostare su false e anche il runnable non è sicuro di essere eseguito se siamo prestampati. Non capisco bene come si deduca che dovrebbe essere impostato su false
Whitebear,

La onClicksi chiama perché si sta chiamando v.performClick();. Si prega di controllare di nuovo il codice sopra nella MotionEvent.ACTION_UPsezione, setPressed(false)si chiama comunque, che mUnsetPressedStatesia nullo o no, che prepressedsia vero o no. La differenza sta nel modo di chiamare setPressed(false)che potrebbe essere attraverso post/ postDelayedo direttamente.
aminografia,

2

Stai scherzando touched focuseventi. Cominciamo con la comprensione del comportamento con lo stesso colore. Per impostazione predefinita, viene Selectorassegnato come sfondo a Buttonin Android. Quindi semplicemente cambiando il colore di sfondo, make è statico (il colore non cambierà). Ma non è un comportamento nativo.

Selector potrebbe assomigliare a questo.

<?xml version="1.0" encoding="utf-8"?> 
  <selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:state_focused="true"
        android:state_pressed="true"
        android:drawable="@drawable/bgalt" />

    <item
        android:state_focused="false"
        android:state_pressed="true"
        android:drawable="@drawable/bgalt" />

    <item android:drawable="@drawable/bgnorm" />
</selector>

Come puoi vedere sopra, c'è stato focusede stato pressed. Impostando onTouchListenergestirai eventi touch, che non hanno nulla a che fare confocus .

Selectordel pulsante dovrebbe sostituire l' focusevento con touchdurante l'evento click sul pulsante. Ma nella prima parte del codice hai intercettato gli eventi per il touch(ritorno vero dal callback). Il cambio colore non può procedere oltre e si blocca con lo stesso colore. Ed è per questo che la seconda variante (senza intercettazione) funziona bene e questa è la tua confusione.

AGGIORNARE

Tutto quello che devi fare è cambiare comportamento e colore per il Selector. Per es. usando lo sfondo successivo per il Button. E rimuovi onTouchListenerdalla tua implementazione.

<?xml version="1.0" encoding="utf-8"?> 
  <selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:state_pressed="true"
        android:drawable="@color/color_pressed" />

    <item android:drawable="@color/color_normal" />
</selector>

Come cambieresti il ​​primo esempio per funzionare bene allora?
Whitebear,

Non sto cercando di rimuovere l'ascoltatore touch dalla mia implementazione. poiché voglio catturare i clic su una vista specifica usando il gestore del tocco, quindi gestirli da solo (usando performClick) e restituire true dall'ascoltatore del tocco per dire agli altri gestori che non viene eseguita alcuna ulteriore gestione. I registri sono stampati bene nel mio esempio, ma il pulsante rimane ancora evidenziato.
Whitebear,

@ Whitehear Non l'hai menzionato nelle tue domande. In ogni caso, puoi usare quanti onTouchListeners vuoi. Non hai solo bisogno di consumare un evento, entro return true.
GensaGames

@Whitebear O Rimuovere il selettore e impostare il colore grezzo sul pulsante tramite backgroundColor.
GensaGames

ragazzi, non state ancora affrontando ciò che ho scritto nel post originale ..
Whitebear,

0

se si assegna uno sfondo al pulsante, non cambierà il colore al clic.

 <color name="myColor">#000000</color>

e impostalo come backgrount sul tuo pulsante

android:background="@color/myColor"

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.