Doppelklick blockieren. Fahrrad?

Wo hat es angefangen?



Wieder einmal habe ich mich mit dem Legacy-Code beschäftigt und mit einem Kontextleck zu kämpfen. Ich habe die Doppelklick-Sperre für die Schaltfläche in der Anwendung aufgehoben. Ich musste herausfinden, was genau ich kaputt gemacht habe und wie es umgesetzt wurde. Angesichts der Tatsache, dass grundsätzlich für eine solche Sperre vorgeschlagen wird, entweder das UI-Element selbst zu deaktivieren oder nachfolgende Klicks für einen kurzen Zeitraum einfach zu ignorieren, erschien mir die vorhandene Lösung seitdem ziemlich interessant Code-Layouts. Es war jedoch weiterhin erforderlich, Listen mit Schaltflächen zu erstellen und eine Menge unterstützenden Codes zu schreiben. Erstellen Sie eine Instanz der Klasse, in der die Liste der Elemente gespeichert, gefüllt und in jedem Klick-Handler drei Methoden aufgerufen werden. Im Allgemeinen gibt es viele Orte, an denen Sie etwas vergessen oder verwirren können. Und ich erinnere mich nicht gern an irgendetwas. Jedes Mal, wenn ich denke, ich erinnere mich an etwas, stellt sich herausdass ich mich entweder falsch erinnere oder jemand es bereits anders überarbeitet hat.



Wir lassen die Frage aus, ob dies richtig ist oder ob wir nur wiederinterinterierbare Handler effizient in Hintergrundabläufe übertragen müssen. Wir machen einfach ein anderes Fahrrad, vielleicht etwas komfortabler.



Im Allgemeinen hat mich natürliche Faulheit zum Nachdenken gebracht, aber ist es möglich, auf all dies zu verzichten? Nun, um den Knopf zu blockieren und zu vergessen. Und dort wird es weiter funktionieren, wie es sollte. Zuerst kam der Gedanke, dass es wahrscheinlich bereits eine Art Bibliothek gab, die verbunden werden konnte, und dass nur eine Methode dieses Typs aufgerufen werden musste - sdelayMneHorosho ().



Aber auch hier bin ich eine Person in einem gewissen Sinne der alten Schule und mag daher keine unnötigen Abhängigkeiten. Der Zoo der Bibliotheken und der Codegenerierung macht mich verzweifelt und enttäuscht von der Menschheit. Nun, Oberflächen-Googeln fand nur typische Optionen mit Timern oder deren Variationen.



Zum Beispiel:



Eins



Zwei



Und so weiter ...



Trotzdem können Sie das Element wahrscheinlich nur in der ersten Zeile des Handlers ausschalten und dann wieder einschalten. Das einzige Problem ist, dass dies dann nicht immer trivial geschieht. In diesem Fall muss der Einschaltcode am Ende aller Optionen, die ein Tastendruck verursachen kann, aufgerufen werden. Es ist nicht verwunderlich, dass solche Entscheidungen nicht sofort gegoogelt wurden. Sie sind sehr kompliziert und es ist äußerst schwierig, sie zu warten.



Ich wollte es einfacher und universeller machen und mich daran erinnern, dass es so wenig wie möglich notwendig war.



Lösung aus dem Projekt



Wie gesagt, die bestehende Lösung wurde interessanterweise zusammengestellt, obwohl sie alle Nachteile bestehender Lösungen aufwies. Zumindest war es eine separate einfache Klasse. Außerdem konnten verschiedene Listen von Elementen deaktiviert werden, obwohl ich nicht sicher bin, ob dies sinnvoll ist.



Die ursprüngliche Klasse zum Blockieren von Doppelklicks
public class MultiClickFilter {
    private static final long TEST_CLICK_WAIT = 500;
    private ArrayList<View> buttonList = new ArrayList<>();
    private long lastClickMillis = -1;

    // User is responsible for setting up this list before using
    public ArrayList<View> getButtonList() {
        return buttonList;
    }

    public void lockButtons() {
        lastClickMillis = System.currentTimeMillis();
        for (View b : buttonList) {
            disableButton(b);
        }
    }

    public void unlockButtons() {
        for (View b : buttonList) {
            enableButton(b);
        }
    }

    // function to help prevent execution of rapid multiple clicks on drive buttons
    //
    public boolean isClickedLately() {
        return (System.currentTimeMillis() - lastClickMillis) < TEST_CLICK_WAIT;  // true will block execution of button function.
    }

    private void enableButton(View button) {
        button.setClickable(true);
        button.setEnabled(true);
    }

    private void disableButton(View button) {
        button.setClickable(false);
        button.setEnabled(false);
    }
}


:



public class TestFragment extends Fragment {

	<=======  ========>

	private MultiClickFilter testMultiClickFilter = new MultiClickFilter();

	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
		<=======  ========>

		testMultiClickFilter.getButtonList().add(testButton);
		testMultiClickFilter.getButtonList().add(test2Button);

		<=======  ========>

		testButton.setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View v) {
				if (testMultiClickFilter.isClickedLately()) {
					return;
				}

				testMultiClickFilter.lockButtons();
				startTestPlayback(v);
				testMultiClickFilter.unlockButtons();
			}
		});

		test2Button.setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View v) {
				if (testMultiClickFilter.isClickedLately()) {
					return;
				}

				testMultiClickFilter.lockButtons();
				loadTestProperties(v);
				testMultiClickFilter.unlockButtons();
			}
		});

		<=======  ========>
	}
	
	<=======  ========>
}




Die Klasse ist klein und im Prinzip ist klar, was sie tut. Kurz gesagt, um eine Schaltfläche für eine Aktivität oder ein Fragment zu blockieren, müssen Sie eine Instanz der MultiClickFilter- Klasse erstellen und ihre Liste mit UI-Elementen füllen, die blockiert werden müssen. Sie können mehrere Listen erstellen, aber in diesem Fall muss der Handler jedes Elements "wissen", welche Instanz des "Klickfilters" abgerufen werden soll.



Außerdem können Sie einen Klick nicht einfach ignorieren. Aus diesem Grund ist es unbedingt erforderlich, die gesamte Liste der Elemente zu sperren. Daher muss sie entsperrt werden. Dies führt dazu, dass jedem Handler zusätzlicher Code hinzugefügt wird. Ja, und in dem Beispiel, würde ich das setzen unlockButtons Methode in dem schließlich Block, aber man weiß nie ... Im Allgemeinen wirft diese Entscheidung Fragen auf.



Neue Lösung



Im Allgemeinen wurde als erste Prämisse akzeptiert, dass es wahrscheinlich keine Silberkugel geben wird:



  1. Das Trennen von Listen sperrbarer Schaltflächen ist nicht ratsam. Nun, ich konnte mir kein Beispiel vorstellen, das eine solche Trennung erfordert.
  2. Deaktivieren Sie kein Element (aktiviert / anklickbar), um Animationen und die allgemeine Lebendigkeit eines Elements zu erhalten
  3. Blockieren Sie den Klick in einem Handler, der dafür ausgelegt ist, weil Es wird davon ausgegangen, dass ein adäquater Benutzer nirgendwo wie von einem Maschinengewehr aus klickt. Um ein versehentliches "Abprallen" zu verhindern, reicht es aus, die Klickverarbeitung einfach für mehrere hundert Millisekunden "für alle" zu deaktivieren.


Idealerweise sollten wir also einen Punkt im Code haben, an dem die gesamte Verarbeitung stattfindet, und eine Methode, die von einer beliebigen Stelle im Projekt in einem beliebigen Handler zuckt und die Verarbeitung wiederholter Klicks blockiert. Nehmen wir an, dass unsere Benutzeroberfläche nicht bedeutet, dass der Benutzer mehr als zweimal pro Sekunde klickt. Nicht, wenn es notwendig ist, dann müssen Sie anscheinend separat auf die Leistung achten, aber hier haben wir einen einfachen Fall, so dass es mit zitternden Fingern unmöglich wäre, die Anwendung auf eine uninteressante Funktion fallen zu lassen. Und damit Sie nicht jedes Mal ein Dampfbad nehmen müssen, um die Leistung eines einfachen Übergangs von einer Aktivität zu einer anderen zu optimieren oder jedes Mal einen Fortschrittsdialog zu starten.



All dies wird bei uns im Haupt-Thread funktionieren, sodass wir uns nicht um die Synchronisierung kümmern müssen. Tatsächlich können wir die Prüfung in dieser Methode auch darauf übertragen, ob wir den Klick verarbeiten oder ignorieren müssen. Wenn es funktioniert, können wir das Blockierungsintervall anpassbar machen. In einem sehr schlechten Fall können Sie das Intervall für einen bestimmten Handler erhöhen.



Ist das möglich?



Die Implementierung erwies sich als überraschend einfach und prägnant.
package com.ai.android.common;

import android.os.Handler;
import android.os.Looper;

import androidx.annotation.MainThread;

public abstract class MultiClickFilter {
    private static final int DEFAULT_LOCK_TIME_MS = 500;
    private static final Handler uiHandler = new Handler(Looper.getMainLooper());

    private static boolean locked = false;

    @MainThread
    public static boolean clickIsLocked(int lockTimeMs) {
        if (locked)
            return true;

        locked = true;

        uiHandler.postDelayed(() -> locked = false, lockTimeMs);

        return false;
    }

    @MainThread
    public static boolean clickIsLocked() {
        return clickIsLocked(DEFAULT_LOCK_TIME_MS);
    }
}


:



public class TestFragment {

	<=======  ========>

	private ListView devicePropertiesListView;

	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
		devicePropertiesListView = view.findViewById(R.id.list_view);
		devicePropertiesListView.setOnItemClickListener(this::doOnItemClick);

		<=======  ========>

		return view;
	}

	private void doOnItemClick(AdapterView<?> adapterView, View view, int position, long id) {
		if (MultiClickFilter.clickIsLocked(1000 * 2))
			return;

		<=======  ========>
	}

	<=======  ========>
}




Im Großen und Ganzen müssen Sie jetzt nur noch die MultiClickFilter- Klasse zum Projekt hinzufügen und prüfen, ob sie zu Beginn jedes Klick- Handlers blockiert ist:



        if (MultiClickFilter.clickIsLocked())
            return;


Wenn der Klick verarbeitet werden soll, wird eine Sperre für die angegebene Zeit (oder standardmäßig) festgelegt. Mit dieser Methode können Sie nicht an Elementlisten denken, keine komplexen Prüfungen erstellen und die Verfügbarkeit von UI-Elementen nicht manuell verwalten. Ich schlage vor, diese Implementierung in den Kommentaren zu diskutieren. Vielleicht gibt es bessere Optionen?



All Articles