Pemblokiran klik dua kali. Sepeda?

Bagaimana awalnya



Sekali lagi menggali dengan kode warisan dan berjuang dengan kebocoran konteks, saya memecahkan kunci klik dua kali pada tombol dalam aplikasi. Saya harus mencari apa tepatnya yang saya pecahkan dan bagaimana penerapannya. Mengingat fakta bahwa pada dasarnya untuk kunci seperti itu diusulkan untuk menonaktifkan elemen UI itu sendiri atau hanya mengabaikan klik berikutnya untuk waktu yang singkat, solusi yang ada bagi saya agak menarik karena tata letak kode. Tetapi masih diperlukan membuat daftar tombol dan menulis cukup banyak kode pendukung. Buat instance kelas yang akan menyimpan daftar elemen, mengisinya, memanggil tiga metode di setiap handler klik. Secara umum, ada banyak tempat di mana Anda bisa melupakan atau membingungkan sesuatu. Dan saya tidak suka mengingat apa pun. Setiap kali saya merasa saya ingat sesuatu, ternyatabaik yang saya ingat salah, atau seseorang sudah dibuat ulang secara berbeda.



Mari kita tinggalkan tanda kurung pertanyaan apakah itu benar untuk melakukan ini atau apakah Anda hanya perlu secara kualitatif memindahkan penangan yang dapat diintegrasikan ke aliran latar belakang. Kami hanya akan membuat motor lain, mungkin sedikit lebih nyaman.



Secara umum, kemalasan alami membuat saya berpikir, tetapi mungkinkah melakukannya tanpa semua ini? Nah, untuk memblokir tombol dan lupakan. Dan itu akan terus bekerja sebagaimana mestinya. Pada awalnya, muncul pemikiran bahwa mungkin sudah ada semacam perpustakaan yang dapat dihubungkan dan bahwa hanya satu metode dari jenis yang harus dipanggil - sdelayMneHorosho ().



Tetapi sekali lagi, saya adalah orang dalam arti tertentu dari sekolah lama dan karena itu tidak suka kecanduan yang tidak perlu. Kebun binatang perpustakaan dan pembuatan kode membuat saya sedih dan kecewa terhadap kemanusiaan. Nah, googling permukaan hanya menemukan opsi tipikal dengan timer atau variasinya.



Sebagai contoh:



Satu



Dua



Dan seterusnya ...



Juga, Anda mungkin bisa mematikan elemen dengan baris pertama di handler, dan kemudian menyalakannya. Satu-satunya masalah adalah bahwa hal itu tidak selalu sepele untuk dilakukan nanti, dan dalam hal ini perlu untuk menambahkan panggilan ke kode "aktif" di akhir semua varian eksekusi yang dapat disebabkan oleh menekan tombol. Tidak mengherankan bahwa saya tidak mencari solusi seperti itu dengan cepat. Mereka sangat rumit dan sangat sulit untuk mempertahankannya.



Saya ingin membuatnya lebih sederhana, lebih universal, dan mengingat itu perlu sesedikit mungkin.



Solusi dari proyek



Seperti yang saya katakan, solusi yang ada disatukan dengan menarik, meskipun memiliki semua kelemahan dari solusi yang ada. Setidaknya itu adalah kelas sederhana yang terpisah. Itu juga memungkinkan daftar item yang berbeda untuk dinonaktifkan, meskipun saya tidak yakin apakah itu masuk akal.



Kelas asli untuk memblokir klik ganda
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();
			}
		});

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




Kelasnya kecil dan, pada prinsipnya, jelas apa yang dilakukannya. Singkatnya, untuk memblokir tombol pada beberapa aktivitas atau fragmen, Anda perlu membuat turunan dari kelas MultiClickFilter dan mengisi daftar elemen UI yang perlu diblokir. Anda dapat membuat beberapa daftar, tetapi dalam hal ini, pawang dari setiap elemen harus "tahu" instance dari "clickfilter" yang akan ditarik.



Selain itu, itu tidak memungkinkan Anda untuk mengabaikan klik. Untuk melakukan ini, Anda harus memblokir seluruh daftar elemen, dan kemudian, karena itu, harus dibuka kuncinya. Ini mengarah ke kode tambahan yang harus ditambahkan ke setiap penangan. Ya, dan dalam contoh, saya akan meletakkan metode unlockButtons di blok akhirnya, tapi Anda tidak pernah tahu ... Secara umum, keputusan ini menimbulkan pertanyaan.



Solusi baru



Secara umum, menyadari bahwa mungkin tidak akan ada semacam peluru perak, berikut ini diterima sebagai tempat awal:



  1. Tidak disarankan untuk memisahkan daftar tombol yang bisa dikunci. Yah, saya tidak bisa memberikan contoh yang membutuhkan pemisahan seperti itu.
  2. Jangan menonaktifkan elemen (diaktifkan / dapat diklik) untuk menjaga animasi dan keaktifan elemen secara umum
  3. Blokir klik pada penangan mana pun yang dirancang untuk ini, karena diasumsikan bahwa pengguna yang memadai tidak mengklik di mana saja seolah-olah dari senapan mesin, dan untuk mencegah "bouncing" yang tidak disengaja, cukup dengan hanya mematikan pemrosesan klik selama beberapa ratus milidetik "untuk semua orang"


Jadi, idealnya, kita harus memiliki satu poin dalam kode di mana semua pemrosesan terjadi dan satu metode yang akan bergerak-gerak dari mana saja dalam proyek dalam penangan apa pun dan memblokir pemrosesan klik berulang. Mari kita asumsikan bahwa UI kita tidak menyiratkan bahwa pengguna mengklik lebih dari dua kali per detik. Tidak, jika perlu, Anda mungkin harus memberikan perhatian khusus pada kinerja, tetapi casing kami sederhana, sehingga dengan jari yang gemetar tidak mungkin untuk menjatuhkan aplikasi pada fungsi yang tidak dapat dimasuki kembali. Dan juga, sehingga Anda tidak perlu khawatir tentang mengoptimalkan kinerja transisi sederhana dari satu aktivitas ke aktivitas lainnya setiap kali atau mem-flash dialog progres setiap saat.



Semua ini akan bekerja dengan kami di utas utama, jadi kami tidak perlu khawatir tentang sinkronisasi. Bahkan, kita dapat mentransfer cek apakah kita perlu menangani klik atau mengabaikannya dalam metode ini. Nah, jika itu berhasil, maka kita bisa membuat interval pemblokiran dapat disesuaikan. Sehingga dalam kasus yang sangat buruk, Anda dapat meningkatkan interval untuk penangan tertentu.



Apa itu mungkin?



Implementasinya ternyata sangat sederhana dan ringkas.
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;

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

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




Secara umum, sekarang Anda hanya perlu menambahkan kelas MultiClickFilter ke proyek dan memeriksa apakah itu diblokir di awal setiap klik penangan:



        if (MultiClickFilter.clickIsLocked())
            return;


Jika klik akan diproses, blok akan ditetapkan untuk waktu yang ditentukan (atau secara default). Metode ini memungkinkan Anda untuk tidak memikirkan daftar elemen, tidak membangun pemeriksaan kompleks dan tidak mengontrol ketersediaan elemen UI secara manual. Saya sarankan membahas implementasi ini di komentar, mungkin ada opsi yang lebih baik?



All Articles