Clique duas vezes em bloqueio. Bicicleta?

Por onde começou



Mais uma vez cavando com o código legado e lutando com um vazamento de contexto, quebrei o bloqueio de clique duplo no botão no aplicativo. Eu tive que procurar exatamente o que quebrei e como foi implementado. Considerando que, basicamente, para esse bloqueio, propõe-se desativar o próprio elemento da interface do usuário ou simplesmente ignorar os cliques subsequentes por um curto período de tempo, a solução existente me pareceu bastante interessante do ponto de vista. layouts de código. Mas ainda era necessário criar listas de botões e escrever bastante código de suporte. Crie uma instância da classe que irá armazenar a lista de elementos, preenchê-la e chamar três métodos em cada manipulador de cliques. Em geral, existem muitos lugares onde você pode esquecer ou confundir algo. E eu não gosto de lembrar de nada. Toda vez que me parece que me lembro de algo, aconteceque eu me lembro incorretamente ou alguém já o refez de forma diferente.



Vamos deixar de fora dos colchetes a questão de saber se é correto fazer isso ou se você só precisa mover qualitativamente os manipuladores reintegráveis ​​para os fluxos de segundo plano. Vamos apenas fazer outra bicicleta, talvez um pouco mais confortável.



Em geral, a preguiça natural me fez pensar: é possível ficar sem tudo isso? Bem, para bloquear o botão e esquecer. E continuará a trabalhar lá como deveria. No começo, pensei que provavelmente já existisse alguma biblioteca que possa ser conectada e que apenas um método do tipo, sdelayMneHorosho (), precisará ser chamado.



Mas, novamente, sou um homem em um certo sentido da velha escola e, portanto, não gosto de vícios desnecessários. O zoológico de bibliotecas e geração de código me deixa desanimado e frustrado com a humanidade. Bem, o pesquisador de superfície encontrou apenas opções típicas com temporizadores ou suas variações.



Por exemplo:



Um



Dois



E assim por diante ...



Ainda assim, você pode simplesmente desligar o elemento pela primeira linha do manipulador e depois ativá-lo. O único problema é que nem sempre é trivial fazer isso mais tarde e, nesse caso, é necessário adicionar uma chamada ao código "ligar" no final de todas as variantes de execução, o que pode resultar do pressionamento do botão. Não é de surpreender que essas decisões não tenham sido imediatamente pesquisadas no Google. Eles são muito complicados e é extremamente difícil mantê-los.



Eu queria torná-lo mais simples, mais universal e lembrar que era necessário o mínimo possível.



Solução do projeto



Como eu disse, a solução existente foi montada de maneira interessante, embora tivesse todas as desvantagens das soluções existentes. Pelo menos era uma aula simples e separada. Também permitiu que diferentes listas de itens fossem desativadas, embora não tenha certeza se isso faz sentido.



Classe original de bloqueio de clique duplo
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();
			}
		});

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




A turma é pequena e, em princípio, fica claro o que faz. Em poucas palavras, para bloquear um botão em alguma atividade ou fragmento, você precisa criar uma instância da classe MultiClickFilter e preencher sua lista de elementos da interface do usuário que precisam ser bloqueados. Você pode fazer várias listas, mas, neste caso, o manipulador de cada elemento deve "saber" qual instância do "filtro de clique" deve ser puxada.



Além disso, ele não permite que você ignore o clique. Para fazer isso, é imperativo bloquear a lista inteira de elementos e, portanto, deve ser desbloqueado. Isso leva ao código adicional a ser adicionado a cada manipulador. Sim, e no exemplo, eu colocaria o método unlockButtons no bloco final, mas você nunca sabe ... Em geral, essa decisão levanta questões.



Nova solução



Em geral, percebendo que provavelmente não haverá algum tipo de bala de prata, os seguintes itens foram aceitos como premissas iniciais:



  1. Não é aconselhável separar as listas de botões bloqueados. Bem, eu não poderia apresentar nenhum exemplo que exigisse essa separação.
  2. Não desative o elemento (ativado / clicável) para preservar as animações e geralmente a vivacidade do elemento
  3. Bloqueie o clique em qualquer manipulador projetado para isso, porque pressupõe-se que um usuário adequado não clica em nenhum lugar como uma metralhadora e, para evitar "conversas" acidentais, basta desativar o processamento de cliques por algumas centenas de milissegundos "para todos"


Portanto, idealmente, devemos ter um ponto no código em que todo o processamento ocorre e um método que se move de qualquer lugar do projeto em qualquer manipulador e bloqueia o processamento de cliques repetidos. Suponha que nossa interface do usuário não implique que o usuário clique com mais frequência do que duas vezes por segundo. Se não for necessário, aparentemente você terá que prestar atenção ao desempenho separadamente, mas temos um caso simples, de modo que, com dedos trêmulos, seria impossível deixar o aplicativo em uma função desinteressante. Além disso, para que você não precise tomar banho de vapor sempre, otimizando o desempenho de uma simples transição de uma atividade para outra ou exibindo um diálogo de progresso a cada vez.



Tudo isso funcionará conosco no thread principal, portanto, não precisamos nos preocupar com a sincronização. De fato, também podemos transferir a verificação para saber se precisamos processar o clique ou ignorá-lo, neste mesmo método. Bem, se possível, seria possível tornar o intervalo de bloqueio personalizável. Para que, em um caso muito ruim, você possa aumentar o intervalo para um manipulador específico.



É possível?



A implementação acabou surpreendentemente simples e concisa.
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;

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

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




Em geral, agora você só precisa adicionar a classe MultiClickFilter ao projeto e verificar se ela está bloqueada no início de cada manipulador de cliques:



        if (MultiClickFilter.clickIsLocked())
            return;


Se um clique for processado, um bloco será definido por um tempo especificado (ou por padrão). O método permitirá não pensar em listas de elementos, não criar verificações complexas e não gerenciar manualmente a disponibilidade dos elementos da interface do usuário. Sugiro discutir essa implementação nos comentários, talvez haja melhores opções?



All Articles