Bagaimana saya bisa membedakan apakah Switch, Nilai Kotak Centang diubah oleh pengguna atau secara terprogram (termasuk menurut retensi)?


110
setOnCheckedChangeListener(new OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                // How to check whether the checkbox/switch has been checked
                // by user or it has been checked programatically ?

                if (isNotSetByUser())
                    return;
                handleSetbyUser();
            }
        });

Bagaimana cara menerapkan metode isNotSetByUser()?


Saya tidak yakin, tapi menurut saya jika pengguna mengaktifkannya maka Anda juga akan mendapatkan callback onClick jika menyetel listener tersebut. Jadi, mungkin Anda bisa menyetel tapi boolean flag di onClick dengan cara itu Anda bisa memeriksanya di onCheckChanged untuk melihat apakah pengguna memulai perubahan.
FoamyGuy


Saya memiliki solusi yang lebih sederhana dan jelas: lihat stackoverflow.com/a/41574200/3256989
ultraon

Jawaban:


157

Jawaban 2:

Jawaban yang sangat sederhana:

Gunakan di OnClickListener, bukan OnCheckedChangeListener

    someCheckBox.setOnClickListener(new OnClickListener(){

        @Override
        public void onClick(View v) {
            // you might keep a reference to the CheckBox to avoid this class cast
            boolean checked = ((CheckBox)v).isChecked();
            setSomeBoolean(checked);
        }

    });

Sekarang Anda hanya mengambil peristiwa klik dan tidak perlu khawatir tentang perubahan terprogram.


Jawaban 1:

Saya telah membuat kelas pembungkus (lihat Pola Dekorator) yang menangani masalah ini dengan cara yang dikemas:

public class BetterCheckBox extends CheckBox {
    private CompoundButton.OnCheckedChangeListener myListener = null;
    private CheckBox myCheckBox;

    public BetterCheckBox(Context context) {
        super(context);
    }

    public BetterCheckBox(Context context, CheckBox checkBox) {
        this(context);
        this.myCheckBox = checkBox;
    }

    // assorted constructors here...    

    @Override
    public void setOnCheckedChangeListener(
        CompoundButton.OnCheckedChangeListener listener){
        if(listener != null) {
            this.myListener = listener;
        }
        myCheckBox.setOnCheckedChangeListener(listener);
    }

    public void silentlySetChecked(boolean checked){
        toggleListener(false);
        myCheckBox.setChecked(checked);
        toggleListener(true);
    }

    private void toggleListener(boolean on){
        if(on) {
            this.setOnCheckedChangeListener(myListener);
        }
        else {
            this.setOnCheckedChangeListener(null);
        }
    }
}

Kotak Centang masih dapat dinyatakan sama dalam XML, tetapi gunakan ini saat menginisialisasi GUI Anda dalam kode:

BetterCheckBox myCheckBox;

// later...
myCheckBox = new BetterCheckBox(context,
    (CheckBox) view.findViewById(R.id.my_check_box));

Jika Anda ingin menyetel diperiksa dari kode tanpa memicu pemroses, panggil myCheckBox.silentlySetChecked(someBoolean)saja setChecked.


15
Untuk a Switch, jawaban 1 berfungsi di kotak ketuk dan geser, sedangkan jawaban 2 hanya akan berfungsi di kotak ketuk. Sebagai preferensi pribadi, saya membuat kelas saya diperpanjang CheckBox/ Switchdaripada memegang referensi ke salah satunya. Terasa lebih bersih seperti itu (perhatikan bahwa Anda harus menentukan nama paket lengkap di XML jika Anda melakukannya).
howettl

1
Terima kasih untuk anthropomo ini, saya tidak tahu mengapa saya tidak memikirkannya sebelumnya, tetapi Anda menghemat waktu saya yang berharga;). Bersulang !
CyberDandy

Saya tidak yakin tentang ini, tetapi jika Anda memperluas SwitchCompat (menggunakan appcompat v7) untuk mendapatkan sakelar desain material, Anda dapat mengakhiri kemampuan desain ulang dan pewarnaan yang baru.
DNax

4
Kedua solusi tersebut memiliki kekurangan yang serius. Solusi pertama: Saat pengguna menyeret sakelar, pendengar tidak akan diaktifkan. Solusi Kedua: Karena setOnCheckedChangeListener melakukan pemeriksaan null itu sebenarnya menyetel listener lama ketika ada set baru yang sudah ada.
Paul Woitaschek

Solusi pertama: masalah yang diketahui dan semi-dapat diterima jika Anda menginginkan solusi yang ringkas dan tidak bermaksud menggunakan widget itu. Solusi kedua: ubah pemeriksaan nol untuk mengatasi kasus tepi yang tidak mungkin semacam itu. Sekarang kita menetapkan listeneruntuk myListenersetiap kali listenertidak null. Dalam hampir semua kasus ini tidak melakukan apa-apa, tetapi jika kita membuat pendengar baru karena suatu alasan, itu tidak rusak.
anthropomo

38

Mungkin Anda dapat memeriksa isShown ()? Jika BENAR - daripada pengguna itu. Bekerja untuk saya.

setOnCheckedChangeListener(new OnCheckedChangeListener() {
    @Override
    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
        if (myCheckBox.isShown()) {// makes sure that this is shown first and user has clicked/dragged it
                  doSometing();
        }
    }
});

1
Ia bekerja (artinya tidak ada panggilan balik tak terduga) bahkan Anda memanggil "setChecked (isChecked)" di onStart () atau onResume (). Sehingga bisa dianggap sebagai solusi yang tepat.
Alan Zhiliang Feng

1
Sepertinya bukan solusi umum. Bagaimana jika tombol ditampilkan pada saat itu tetapi nilainya berubah dari kode?
Pavel

1
Saya tidak mengerti, bagaimana 'isShown ()' membedakan antara tindakan pengguna dan perubahan terprogram? seperti bagaimana cara mengatakan itu adalah tindakan pengguna jika 'isShown ()' benar?
Muhammed Refaat

1
Solusi ini hanya berfungsi dalam kasus yang sangat spesifik dan bergantung pada proses pembuatan dan tata letak aktivitas yang tidak berdokumen dan tidak dijamin. Tidak ada jaminan ini tidak akan rusak di masa mendatang dan tidak berfungsi jika tampilan sudah dirender.
Manuel

26

Di dalam, onCheckedChanged()periksa saja apakah pengguna benar checked/unchecked- benar memiliki tombol radio dan kemudian lakukan hal-hal yang sesuai sebagai berikut:

mMySwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
 @Override
 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
   if (buttonView.isPressed()) {
       // User has clicked check box
    }
   else
    {
       //triggered due to programmatic assignment using 'setChecked()' method.   
    }
  }
});

Solusi bagus, tidak perlu menyesuaikan tampilan.
Aju

Aku cinta kamu. : DDD
Vlad

12
ini tidak berfungsi ketika pengguna mengaktifkan sakelar dengan menggesek / menggeser
mohitum

1
Menggunakan pendekatan ini di mana-mana tetapi menemukan kasus itu tidak berfungsi sebagai isPressedpengembalian palsu, perangkat Nokia dengan TalkBack aktif.
Mikhail

SwitchMaterial bekerja tanpa masalah. Keputusan yang bagus, terima kasih!
R00We

25

Anda dapat menghapus pendengar sebelum mengubahnya secara terprogram dan menambahkannya lagi, seperti yang dijawab di posting SO berikut:

https://stackoverflow.com/a/14147300/1666070

theCheck.setOnCheckedChangeListener(null);
theCheck.setChecked(false);
theCheck.setOnCheckedChangeListener(toggleButtonChangeListener);

4

Coba perluas Kotak Centang. Sesuatu seperti itu (bukan contoh lengkap):

public MyCheckBox extends CheckBox {

   private Boolean isCheckedProgramatically = false;

   public void setChecked(Boolean checked) {
       isCheckedProgramatically = true;
       super.setChecked(checked);
   }

   public Boolean isNotSetByUser() {
      return isCheckedProgramatically;
   }

}

3

Ada solusi sederhana lain yang berfungsi dengan baik. Contohnya adalah untuk Switch.

public class BetterSwitch extends Switch {
  //Constructors here...

    private boolean mUserTriggered;

    // Use it in listener to check that listener is triggered by the user.
    public boolean isUserTriggered() {
        return mUserTriggered;
    }

    // Override this method to handle the case where user drags the switch
    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        boolean result;

        mUserTriggered = true;
        result = super.onTouchEvent(ev);
        mUserTriggered = false;

        return result;
    }

    // Override this method to handle the case where user clicks the switch
    @Override
    public boolean performClick() {
        boolean result;

        mUserTriggered = true;
        result = super.performClick();
        mUserTriggered = false;

        return result;
    }
}

2

Pertanyaan menarik. Sepengetahuan saya, setelah Anda menjadi pendengar, Anda tidak dapat mendeteksi tindakan apa yang telah memicu pendengar, konteksnya saja tidak cukup. Kecuali Anda menggunakan nilai boolean eksternal sebagai indikator.

Jika Anda mencentang kotak "secara terprogram", setel nilai boolean sebelumnya untuk menunjukkan bahwa itu dilakukan secara terprogram. Sesuatu seperti:

private boolean boxWasCheckedProgrammatically = false;

....

// Programmatic change:
boxWasCheckedProgrammatically = true;
checkBoxe.setChecked(true)

Dan di listener Anda, jangan lupa untuk mengatur ulang status kotak centang:

@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
    if (isNotSetByUser()) {
        resetBoxCheckSource();
        return;
    }
    doSometing();
}

// in your activity:
public boolean isNotSetByUser() {
    return boxWasCheckedProgrammatically;
}

public void resetBoxCheckedSource() {
    this.boxWasCheckedProgrammatically  = false;
}

2

Coba NinjaSwitch:

Panggil saja setChecked(boolean, true)untuk mengubah status centang sakelar tanpa terdeteksi!

public class NinjaSwitch extends SwitchCompat {

    private OnCheckedChangeListener mCheckedChangeListener;

    public NinjaSwitch(Context context) {
        super(context);
    }

    public NinjaSwitch(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public NinjaSwitch(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public void setOnCheckedChangeListener(OnCheckedChangeListener listener) {
        super.setOnCheckedChangeListener(listener);
        mCheckedChangeListener = listener;
    }

    /**
     * <p>Changes the checked state of this button.</p>
     *
     * @param checked true to check the button, false to uncheck it
     * @param isNinja true to change the state like a Ninja, makes no one knows about the change!
     */
    public void setChecked(boolean checked, boolean isNinja) {
        if (isNinja) {
            super.setOnCheckedChangeListener(null);
        }
        setChecked(checked);
        if (isNinja) {
            super.setOnCheckedChangeListener(mCheckedChangeListener);
        }
    }
}

2

Jika OnClickListenersudah disetel dan tidak boleh ditimpa, gunakan !buttonView.isPressed()sebagai isNotSetByUser().

Jika tidak varian terbaik adalah dengan menggunakan OnClickListenerbukan OnCheckedChangeListener.


buttonView.isPressed () adalah solusi yang bagus. Ada masalah saat kami menggunakan OnClickListener, saat pengguna menggeser sakelar, kami tidak akan mendapatkan panggilan balik.
Aju

2

Jawaban yang diterima dapat disederhanakan sedikit untuk tidak mempertahankan referensi ke kotak centang asli. Ini membuatnya jadi kita bisa menggunakan SilentSwitchCompat(atau SilentCheckboxCompatjika Anda lebih suka) langsung di XML. Saya juga membuatnya sehingga Anda dapat menyetel OnCheckedChangeListenerke nulljika Anda ingin melakukannya.

public class SilentSwitchCompat extends SwitchCompat {
  private OnCheckedChangeListener listener = null;

  public SilentSwitchCompat(Context context) {
    super(context);
  }

  public SilentSwitchCompat(Context context, AttributeSet attrs) {
    super(context, attrs);
  }

  @Override
  public void setOnCheckedChangeListener(OnCheckedChangeListener listener) {
    super.setOnCheckedChangeListener(listener);
    this.listener = listener;
  }

  /**
   * Check the {@link SilentSwitchCompat}, without calling the {@code onCheckChangeListener}.
   *
   * @param checked whether this {@link SilentSwitchCompat} should be checked or not.
   */
  public void silentlySetChecked(boolean checked) {
    OnCheckedChangeListener tmpListener = listener;
    setOnCheckedChangeListener(null);
    setChecked(checked);
    setOnCheckedChangeListener(tmpListener);
  }
}

Anda kemudian dapat menggunakan ini secara langsung di XML Anda seperti itu (Catatan: Anda akan membutuhkan nama paket secara keseluruhan):

<com.my.package.name.SilentCheckBox
      android:id="@+id/my_check_box"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:textOff="@string/disabled"
      android:textOn="@string/enabled"/>

Kemudian Anda dapat mencentang kotak secara diam-diam dengan menelepon:

SilentCheckBox mySilentCheckBox = (SilentCheckBox) findViewById(R.id.my_check_box)
mySilentCheckBox.silentlySetChecked(someBoolean)

1

Inilah implementasi saya

Kode Java untuk Custom Switch:

public class CustomSwitch extends SwitchCompat {

private OnCheckedChangeListener mListener = null;

public CustomSwitch(Context context) {
    super(context);
}

public CustomSwitch(Context context, AttributeSet attrs) {
    super(context, attrs);
}

public CustomSwitch(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
}

@Override
public void setOnCheckedChangeListener(@Nullable OnCheckedChangeListener listener) {
    if(listener != null && this.mListener != listener) {
        this.mListener = listener;
    }
    super.setOnCheckedChangeListener(listener);
}

public void setCheckedSilently(boolean checked){
    this.setOnCheckedChangeListener(null);
    this.setChecked(checked);
    this.setOnCheckedChangeListener(mListener);
}}

Kode Kotlin yang Setara:

class CustomSwitch : SwitchCompat {

private var mListener: CompoundButton.OnCheckedChangeListener? = null

constructor(context: Context) : super(context) {}

constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {}

constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {}

override fun setOnCheckedChangeListener(@Nullable listener: CompoundButton.OnCheckedChangeListener?) {
    if (listener != null && this.mListener != listener) {
        this.mListener = listener
    }
    super.setOnCheckedChangeListener(listener)
}

fun setCheckedSilently(checked: Boolean) {
    this.setOnCheckedChangeListener(null)
    this.isChecked = checked
    this.setOnCheckedChangeListener(mListener)
}}

Untuk mengubah status sakelar tanpa memicu penggunaan pemroses:

swSelection.setCheckedSilently(contact.isSelected)

Anda dapat memantau perubahan status seperti biasa dengan:

swSelection.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
   @Override
   public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
      // Do something
   }       
 });

Di Kotlin:

 swSelection.setOnCheckedChangeListener{buttonView, isChecked -> run {
            contact.isSelected = isChecked
        }}

1

Varian saya dengan fungsi ekstensi Kotlin:

fun CheckBox.setCheckedSilently(isChecked: Boolean, onCheckedChangeListener: CompoundButton.OnCheckedChangeListener) {
    if (isChecked == this.isChecked) return
    this.setOnCheckedChangeListener(null)
    this.isChecked = isChecked
    this.setOnCheckedChangeListener(onCheckedChangeListener)
}

... sayangnya kita perlu meneruskan onCheckedChangeListener setiap saat karena kelas CheckBox tidak mendapatkan bidang mOnCheckedChangeListener ((

Pemakaian:

checkbox.setCheckedSilently(true, myCheckboxListener)

0

Buat variabel

boolean setByUser = false;  // Initially it is set programmatically


private void notSetByUser(boolean value) {
   setByUser = value;
}
// If user has changed it will be true, else false 
private boolean isNotSetByUser() {
   return setByUser;          

}

Dalam aplikasi ketika Anda mengubahnya, bukan pengguna, panggil notSetByUser(true)jadi itu tidak diatur oleh pengguna, jika tidak panggil notSetByUser(false)misalnya itu diatur oleh program.

Terakhir, di event listener Anda, setelah memanggil isNotSetByUser (), pastikan Anda mengubahnya kembali ke normal.

Panggil metode ini setiap kali Anda menangani tindakan itu baik melalui pengguna atau secara terprogram. Panggil notSetByUser () dengan nilai yang sesuai.


0

Jika tag tampilan tidak digunakan, Anda dapat menggunakannya sebagai ganti memperluas kotak centang:

        checkBox.setOnCheckedChangeListener(new OnCheckedChangeListener() {

                @Override
                public void onCheckedChanged(final CompoundButton buttonView, final boolean isChecked) {
                    if (buttonView.getTag() != null) {
                        buttonView.setTag(null);
                        return;
                    }
                    //handle the checking/unchecking
                    }

setiap kali Anda memanggil sesuatu yang mencentang / tidak mencentang kotak centang, panggil juga ini sebelum mencentang / menghapus centang:

checkbox.setTag(true);

0

Saya telah membuat ekstensi dengan RxJava PublishSubject, yang sederhana. Bereaksi hanya pada acara "OnClick".

/**
 * Creates ClickListener and sends switch state on each click
 */
fun CompoundButton.onCheckChangedByUser(): PublishSubject<Boolean> {
    val onCheckChangedByUser: PublishSubject<Boolean> = PublishSubject.create()
    setOnClickListener {
        onCheckChangedByUser.onNext(isChecked)
    }
    return onCheckChangedByUser
}
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.