Schaltfläche im hervorgehobenen Zustand mit touchListener und clickListener belassen


10

Ich habe ein Problem damit, dass mein Button in einem hervorgehobenen Zustand bleibt, nachdem ich Folgendes getan habe:

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

    }
}

In Bezug auf den obigen Code erwarte ich bei der Verwendung, dass der Klick auf die Schaltfläche von der Berührung verarbeitet wird, und durch die Rückgabe von "true" sollte die Behandlung beim touchListener beendet werden.

Dies ist jedoch nicht der Fall. Die Schaltfläche bleibt in einem hervorgehobenen Zustand, obwohl der Klick aufgerufen wird.

Was ich bekomme ist:

Test - calling onClick
Test - Performing click

Wenn ich dagegen den folgenden Code verwende, wird auf die Schaltfläche geklickt, es werden dieselben Ausdrucke gedruckt, aber die Schaltfläche bleibt nicht in einem hervorgehobenen Zustand hängen:

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

    }
}

Ich bin etwas verwirrt darüber, was die Antwortkette für das Touch-Ereignis ist. Ich vermute, dass es ist:

1) TouchListener

2) ClickListener

3) ParentViews

Kann das auch jemand bestätigen?


Was Sie eigentlich tun möchten, ist es Handhabung durch Berühren oder Ändern der Farbe beim einfachen Drücken?
Haider Saleem

Ich möchte eine Logik in Kontakt ausführen und dann performClick aufrufen, damit die Farbe der Schaltfläche nicht geändert wird.
Whitebear

@ Whitebear Bitte überprüfen Sie die Antwort unten. Vielleicht kann ich weitere Informationen hinzufügen.
GensaGames

Dies kann Ihnen helfen, den Ablauf von Berührungsereignissen zu verstehen. Es ist nicht klar, was Sie auftreten möchten. Möchten Sie einen Klick-Handler und den Klick ausführen? Möchten Sie, dass die Schaltfläche von ihrer ursprünglichen Farbe in den vom Farbfilter festgelegten Zustand und dann wieder in ihre ursprüngliche Farbe zurückkehrt?
Cheticamp

Lassen Sie mich erklären, was ich meine. Ich habe einen touchListener und einen clickListener auf der Schaltfläche. Die Berührung geht dem Klick nach Priorität voraus und gibt true zurück, wenn das Ereignis behandelt wurde. Dies bedeutet, dass niemand anderes damit umgehen sollte. Dies ist genau das, was ich durch Berühren mache, den Klick handhabe und true zurückgebe, aber die Schaltfläche bleibt weiterhin hervorgehoben, obwohl der On-Click-Listener aufgerufen wird und der Ablauf korrekt ausgeführt wird.
Whitebear

Antworten:


10

Solche Anpassungen erfordern keine programmgesteuerten Änderungen. Sie können es einfach in xmlDateien tun . Löschen Sie zunächst die von setOnTouchListenerIhnen angegebene Methode onCreatevollständig. Definieren Sie als Nächstes eine Auswahlfarbe im res/colorVerzeichnis wie folgt. (Wenn das Verzeichnis nicht existiert, erstellen Sie es)

res / color / 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>

Stellen Sie es nun auf das app:backgroundTintAttribut der Schaltfläche ein :

<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" />


Visuelles Ergebnis:

Geben Sie hier die Bildbeschreibung ein



BEARBEITET: (um das Problem mit Berührungsereignissen zu beheben)

Insgesamt betrachtet beginnt der Ablauf des Berührungsereignisses mit dem Activity, fließt dann zum Layout (vom übergeordneten zu den untergeordneten Layouts) und dann zu den Ansichten. (LTR-Fluss im folgenden Bild)

Geben Sie hier die Bildbeschreibung ein

Wenn das Berührungsereignis die Zielansicht erreicht, kann die Ansicht das Ereignis verarbeiten und dann entscheiden, ob es an die vorherigen Layouts / Aktivitäten übergeben werden soll oder nicht (Rückgabe falseder trueIn- onTouchMethode). (RTL-Fluss im obigen Bild)

Schauen wir uns nun den Quellcode der Ansicht an, um einen tieferen Einblick in die Berührungsereignisflüsse zu erhalten. dispatchTouchEventWenn Sie sich die Implementierung von ansehen, werden Sie feststellen, dass die Ansicht nicht aufgerufen wird , wenn Sie eine OnTouchListenerfür die Ansicht festlegen und dann truein ihrer onTouchMethode zurückkehren onTouchEvent.

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

Schauen Sie sich nun die onTouchEventMethode an , bei der sich die Ereignisaktion befindet MotionEvent.ACTION_UP. Wir sehen, dass dort eine Perform-Click-Aktion stattfindet. Wenn Sie also truein die OnTouchListener's zurückkehren onTouchund folglich die ' s nicht aufrufen onTouchEvent, rufen Sie die OnClickListener's nicht auf onClick.

Es gibt ein weiteres Problem beim Nichtaufrufen von onTouchEvent, das mit dem Druckzustand zusammenhängt und den Sie in der Frage erwähnt haben. Wie wir im folgenden Codeblock sehen können, gibt es eine Instanz UnsetPressedStatedieser Aufrufe, wenn sie ausgeführt werden. Wenn Sie nicht aufrufen, bleibt die Ansicht im gedrückten Zustand hängen und ihr zeichnbarer Zustand ändert sich nicht. 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);
    }
}


In Bezug auf die obigen Beschreibungen können Sie den Code ändern, indem Sie sich setPressed(false)selbst aufrufen , um den Zeichenstatus zu ändern, in dem sich die Ereignisaktion befindet 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;
    }
});

Das ist nicht was ich für meinen Freund suche. Ich möchte die Verhaltensänderung in beiden oben beschriebenen Situationen verstehen. Wenn ich etwas näher erläutern kann, lassen Sie es mich bitte wissen. Ich möchte weder den Touch-Handler noch den Click-Handler löschen. Bitte überprüfen Sie meinen Kommentar zur obigen Antwort.
Whitebear

@ Whitebear: Ich habe die Antwort aktualisiert. Bitte schau es dir an, Alter.
Aminographie

Das ist eine gute detaillierte Antwort, und ich würde sie akzeptieren. Ein paar 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.Hinweise zum Ändern: In meinem Fall wird der onClick aufgerufen. mUnsetPressedState prüft, ob es null ist, bevor es auf false gesetzt wird, und auch die ausführbare Datei kann nicht ausgeführt werden, wenn wir unter Druck stehen. Ich verstehe nicht ganz, wie Sie daraus schließen, dass es auf false gesetzt werden sollte
Whitebear

Das onClickwird aufgerufen, weil Sie anrufen v.performClick();. Bitte überprüfen Sie den obigen Code im MotionEvent.ACTION_UPAbschnitt noch einmal, setPressed(false)wird trotzdem aufgerufen, ob mUnsetPressedStatenull ist oder nicht, ob prepressedwahr ist oder nicht. Der Unterschied liegt in der Art des Anrufs setPressed(false), der über post/ postDelayedoder direkt erfolgen kann.
Aminographie

2

Sie spielen herum touchund focusEreignisse. Beginnen wir mit dem Verständnis des Verhaltens mit derselben Farbe. Standardmäßig wird Selectordem Buttonin Android als Hintergrund zugewiesen . Wenn Sie also einfach die Hintergrundfarbe ändern, ist make statisch (die Farbe ändert sich nicht). Aber es ist kein natives Verhalten.

Selector könnte so aussehen.

<?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>

Wie Sie oben sehen können, gibt es Zustand focusedund Zustand pressed. Durch die Einstellung onTouchListenerwerden Berührungsereignisse behandelt, die nichts damit zu tun haben focus.

Selectorof the Button sollte das focusEreignis touchwährend des Klickereignisses auf der Schaltfläche ersetzen . Aber im ersten Teil Ihres Codes haben Sie Ereignisse für die abgefangen touch(Rückgabe true vom Rückruf). Der Farbwechsel kann nicht weiter fortgesetzt werden und friert mit derselben Farbe ein. Und deshalb funktioniert die zweite Variante (ohne Abfangen) einwandfrei, und das ist Ihre Verwirrung.

AKTUALISIEREN

Alles, was Sie tun müssen, ist, Verhalten und Farbe für die zu ändern Selector. Zum Beispiel. durch Verwendung des nächsten Hintergrunds für die Button. UND entfernen Sie überhaupt onTouchListeneraus Ihrer Implementierung.

<?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>

Wie würden Sie das erste Beispiel ändern, um dann gut zu funktionieren?
Whitebear

Ich möchte den Touch Listener nicht aus meiner Implementierung entfernen. Ich möchte mit dem Touch-Handler Klicks auf eine bestimmte Ansicht abfangen und diese dann selbst (mit performClick) behandeln und vom Touch-Listener true zurückgeben, um den anderen Handlern mitzuteilen, dass keine weitere Behandlung erfolgt. In meinem Beispiel werden die Protokolle einwandfrei gedruckt, die Schaltfläche bleibt jedoch weiterhin hervorgehoben.
Whitebear

@ Whitebear Das hast du in deinen Fragen nicht erwähnt. Auf jede Weise können Sie so viele onTouchListeners verwenden, wie Sie möchten. Sie müssen einfach kein Ereignis verbrauchen return true.
GensaGames

@Whitebear ODER Entfernen Sie den Selektor und stellen Sie die Rohfarbe auf die Schaltfläche über ein backgroundColor.
GensaGames

Leute, Sie sprechen immer noch nicht das an, was ich im ursprünglichen Beitrag geschrieben habe.
Whitebear

0

Wenn Sie der Schaltfläche einen Hintergrund zuweisen, ändert sich die Farbe beim Klicken nicht.

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

und setzen Sie es als Hintergrund für Ihre Schaltfläche

android:background="@color/myColor"

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.