Double-cliquez sur verrou. Vélo?

Où est-ce que ça a commencé



Encore une fois, en creusant avec le code hérité et en luttant contre une fuite de contexte, j'ai cassé le verrou du double-clic sur le bouton de l'application. J'ai dû chercher exactement ce que j'ai cassé et comment cela a été mis en œuvre. Étant donné que, fondamentalement, pour un tel verrou, il est proposé de désactiver l'élément d'interface lui-même ou simplement d'ignorer les clics suivants pendant une courte période, la solution existante m'a semblé plutôt intéressante car dispositions de code. Mais cela nécessitait toujours de créer des listes de boutons et d'écrire beaucoup de code de support. Créez une instance de la classe qui stockera la liste des éléments, remplissez-la, appelez trois méthodes dans chaque gestionnaire de clic. En général, il existe de nombreux endroits où vous pouvez oublier ou confondre quelque chose. Et je n'aime me souvenir de rien. Chaque fois qu'il me semble que je me souviens de quelque chose, il s'avèresoit je me souviens mal, soit quelqu'un l'a déjà refait différemment.



Nous laissons de côté la question de savoir s'il est juste de faire cela ou si nous devons simplement transférer efficacement les gestionnaires réinterchangeables vers les flux d'arrière-plan. Nous allons juste faire un autre vélo, peut-être un peu plus confortable.



En général, la paresse naturelle m'a fait réfléchir, est-il possible de se passer de tout cela? Eh bien, pour bloquer le bouton et oublier. Et il continuera à fonctionner comme il se doit. Au début, on a pensé qu'il y avait probablement déjà une sorte de bibliothèque qui pouvait être connectée et qu'une seule méthode du type devrait être appelée - sdelayMneHorosho ().



Mais encore une fois, je suis une personne dans un certain sens de la vieille école et donc n'aime pas les addictions inutiles. Le zoo des bibliothèques et de la génération de code me rend découragé et déçu de l'humanité. Eh bien, la recherche sur Google en surface n'a trouvé que des options typiques avec des minuteries ou leurs variations.



Par exemple:



One



Two



Et ainsi de suite ...



En outre, vous pouvez probablement simplement désactiver l'élément avec la première ligne du gestionnaire, puis l'activer. Le seul problème est qu'il n'est pas toujours trivial de le faire plus tard, et dans ce cas il faut ajouter un appel au code "on-on" à la fin de toutes les variantes d'exécution qui peuvent être provoquées en appuyant sur le bouton. Il n'est pas surprenant que de telles décisions n'aient pas été googlé tout de suite. Ils sont très compliqués et il est extrêmement difficile de les maintenir.



Je voulais le rendre plus simple, plus universel et me souvenir que c'était le moins nécessaire possible.



Solution du projet



Comme je l'ai dit, la solution existante était organisée de manière intéressante, même si elle présentait toutes les lacunes des solutions existantes. Au moins, c'était une classe simple séparée. Il a également permis de créer différentes listes d'éléments déconnectés, même si je ne suis pas sûr que cela ait du sens.



La classe d'origine pour bloquer le double-clic
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();
			}
		});

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




La classe est petite et en principe, il est clair ce qu'elle fait. En un mot, pour verrouiller le bouton sur une activité ou un fragment, vous devez créer une instance de la classe MultiClickFilter et remplir sa liste avec les éléments d'interface utilisateur qui doivent être bloqués. Vous pouvez faire plusieurs listes, mais dans ce cas, le gestionnaire de chaque élément doit "savoir" quelle instance du "clickfilter" tirer.



De plus, il ne vous permet pas d'ignorer simplement un clic. Pour ce faire, vous devez obligatoirement bloquer la liste entière des éléments, puis, par conséquent, elle doit être déverrouillée. Cela conduit à du code supplémentaire qui doit être ajouté à chaque gestionnaire. Et dans l'exemple, je mettrais la méthode unlockButtons dans le bloc finally, mais on ne sait jamais ... En général, cette décision soulève des questions.



Nouvelle solution



En général, réalisant qu'il n'y aura probablement pas une sorte de solution miracle, il a été accepté comme prémisse initiale:



  1. Il n'est pas conseillé de séparer les listes de boutons bloqués. Eh bien, je ne pouvais penser à aucun exemple nécessitant une telle séparation.
  2. Ne désactivez pas l'élément (activé / cliquable) pour conserver les animations et généralement la vivacité de l'élément
  3. Bloquez un clic dans tout gestionnaire prévu à cet effet, car on suppose qu'un utilisateur adéquat ne clique nulle part comme à partir d'une mitrailleuse, et pour éviter les "bavardages" accidentels, il suffit simplement de désactiver le traitement des clics pendant quelques centaines de millisecondes "pour tout le monde"


Donc, idéalement, nous devrions avoir un point dans le code où tout le traitement a lieu et une méthode qui se déplacera de n'importe où dans le projet dans n'importe quel gestionnaire et bloquera le traitement des clics répétés. Supposons que notre interface utilisateur n'implique pas que l'utilisateur clique plus de deux fois par seconde. Non, si cela est nécessaire, alors, apparemment, vous devrez porter une attention particulière aux performances, mais notre cas est simple, de sorte qu'avec des doigts tremblants, il ne serait pas possible de laisser tomber l'application sur une fonction non réinterprétable. Et aussi, pour que vous n'ayez pas à vous soucier d'optimiser les performances d'une simple transition d'une activité à l'autre à chaque fois ou de flasher un dialogue de progression à chaque fois.



Tout cela fonctionnera pour nous dans le thread principal, nous n'avons donc pas à nous soucier de la synchronisation. De plus, en fait, nous pouvons transférer la vérification pour savoir si nous devons gérer le clic ou l'ignorer dans cette méthode même. Eh bien, si possible, il serait possible de personnaliser l'intervalle de blocage. Ainsi, dans un très mauvais cas, vous pouvez augmenter l'intervalle pour un gestionnaire particulier.



C'est possible?



La mise en œuvre était étonnamment simple et concise.
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;

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

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




En gros, il vous suffit maintenant d'ajouter la classe MultiClickFilter au projet et de vérifier si elle est bloquée au début de chaque gestionnaire de clic:



        if (MultiClickFilter.clickIsLocked())
            return;


Si un clic doit être traité, un bloc sera défini pour une durée spécifiée (ou par défaut). La méthode permettra de ne pas penser aux listes d'éléments, de ne pas construire de vérifications complexes et de ne pas gérer manuellement la disponibilité des éléments d'interface. Je suggère de discuter de cette implémentation dans les commentaires, peut-être existe-t-il de meilleures options?



All Articles