Modifica il valore della casella di controllo senza attivare onCheckChanged


147

io ho setOnCheckedChangeListener implementato per il miocheckbox

C'è un modo che posso chiamare

checkbox.setChecked(false);

senza innescare il onCheckedChanged


Perché non usare semplicemente una bandiera vero / falso? È il modo più semplice per risolvere questo problema e richiede solo tre righe di codice aggiuntivo. Vedi la mia risposta qui sotto.
Ruchir Baronia,

Risposte:


280

No, non puoi farlo. Il onCheckedChangedmetodo viene chiamato direttamente da setChecked. Quello che puoi fare è il seguente:

mCheck.setOnCheckedChangeListener (null);
mCheck.setChecked (false);
mCheck.setOnCheckedChangeListener (mListener);

Vedi l' origine di CheckBox e l'implementazione di setChecked:

public void  setChecked(boolean checked) {
    if (mChecked != checked) {
        mChecked = checked;
        refreshDrawableState();

        // Avoid infinite recursions if setChecked() is called from a listener
        if (mBroadcasting) {
            return;
        }

        mBroadcasting = true;
        if (mOnCheckedChangeListener != null) {
            mOnCheckedChangeListener.onCheckedChanged(this, mChecked);
        }

        if (mOnCheckedChangeWidgetListener != null) {
            mOnCheckedChangeWidgetListener.onCheckedChanged(this, mChecked);
        }

        mBroadcasting = false;            
    }
}

6
Come proponi di ottenere mListener? Checkboxnon ha un getter per il suoOnCheckChangeListener
tir38

20
Beh, non c'è bisogno di sottovalutare semplicemente perché non capisci la soluzione. mListenerè un'implementazione OnCheckChangedListenerdell'interfaccia, creata dal programmatore. La mia risposta implica che il programmatore ha mantenuto un riferimento alla propria implementazione - mListener.
Ombra

Sarebbe inefficiente modificare il listener se si desidera utilizzare il metodo setChecked () ripetutamente?
Ren,

4
@Ren, la modifica del listener comporta solo l'impostazione di una proprietà CheckBoxnell'oggetto. Non direi che è inefficiente.
Ombra

Il documento dice "Chiamato quando il pulsante di opzione selezionato è cambiato. Quando la selezione è deselezionata, checkedId è -1". Questo è davvero fuorviante, o dovrebbe essere passato anche da IsChecked.
AA_PV,

69

Aggiungi questo codice all'interno di OnCheckedChangeListener:

if(!compoundButton.isPressed()) {
            return;
}

Questo ci aiuterà a capire che lo stato del checkBox meteo è stato modificato a livello di codice o dall'azione dell'utente.


11
Questo dovrebbe essere contrassegnato come una soluzione, funziona come un fascino!
Ashwin Balani,

5
attenzione! Questo rompe la modalità di accessibilità! isPressed () non è vero quando è attivato da un doppio tocco dalla modalità Assistente vocale.
A1m

21

Un altro modo possibile per raggiungere questo obiettivo è utilizzare un CheckBox personalizzato, che ti permetterà di scegliere se chiamare l'ascoltatore o meno:

public class CheckBox extends AppCompatCheckBox {
    private OnCheckedChangeListener mListener;

    public CheckBox(final Context context) {
        super(context);
    }

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

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

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

    public void setChecked(final boolean checked, final boolean alsoNotify) {
        if (!alsoNotify) {
            super.setOnCheckedChangeListener(null);
            super.setChecked(checked);
            super.setOnCheckedChangeListener(mListener);
            return;
        }
        super.setChecked(checked);
    }

    public void toggle(boolean alsoNotify) {
        if (!alsoNotify) {
            super.setOnCheckedChangeListener(null);
            super.toggle();
            super.setOnCheckedChangeListener(mListener);
        }
        super.toggle();
    }
}

Versione Kotlin, se preferisci:

class CheckBox @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : AppCompatCheckBox(context, attrs, defStyleAttr) {
    private var listener: CompoundButton.OnCheckedChangeListener? = null

    override fun setOnCheckedChangeListener(listener: CompoundButton.OnCheckedChangeListener?) {
        this.listener = listener
        super.setOnCheckedChangeListener(listener)
    }

    fun setChecked(checked: Boolean, alsoNotify: Boolean) {
        if (!alsoNotify) {
            super.setOnCheckedChangeListener(null)
            super.setChecked(checked)
            super.setOnCheckedChangeListener(listener)
            return
        }
        super.setChecked(checked)
    }

    fun toggle(alsoNotify: Boolean) {
        if (!alsoNotify) {
            super.setOnCheckedChangeListener(null)
            super.toggle()
            super.setOnCheckedChangeListener(listener)
        }
        super.toggle()
    }
}

utilizzo del campione:

checkBox.setChecked(true,false);

11

usi semplicemente setonclickListener, funzionerà benissimo e questo è un metodo molto semplice, grazie :)


35
onClick () non viene chiamato quando l'utente fa scorrere l'interruttore.
James,

2
In effetti, farlo non ti lascia alcun gestore quando l'utente trascina l'interruttore.
Victor G,

allora come otterrai il valore isChecked, ovvero vero o falso?
Abhi,

6

Utilizzando le estensioni di Kotlin con la risposta di @Shade:

fun CompoundButton.setCustomChecked(value: Boolean,listener: CompoundButton.OnCheckedChangeListener) {
     setOnCheckedChangeListener(null)
     isChecked = value
     setOnCheckedChangeListener(listener)
}

6

Per chiunque si imbatta in questo, un modo più semplice per farlo è semplicemente usare un tag sulla casella di controllo e quindi controllare quel tag sul suo listener (il codice è in Kotlin):

checkBox.tag = false
checkBox.setOnCheckedChangeListener{ buttonView, isChecked -> 
    if(checkBox.tag != true) {
        //Do some stuff
    } else {
        checkBox.tag = false
    }

Quindi quando accedi basta impostare il tag su true prima di impostare isChecked su true quando si desidera ignorare la modifica del valore:

checkBox.tag = true
checkBox.isChecked = true

È inoltre possibile mappare il tag su una chiave utilizzando il metodo setTag alternativo che richiede una chiave se si era preoccupati della comprensibilità. Ma se tutto è contenuto in una singola classe, alcune stringhe di commento saranno più che sufficienti per spiegare cosa sta succedendo.


4

È possibile utilizzare questa classe SafeCheckBox come casella di controllo:

public class SafeCheckBox extends AppCompatCheckBox implements CompoundButton.OnCheckedChangeListener {

    private OnSafeCheckedListener onSafeCheckedListener;

    private int mIgnoreListener = CALL_LISTENER;

    public static final int IGNORE = 0;
    public static final int CALL_LISTENER = 1;

    @Retention(RetentionPolicy.SOURCE)
    @IntDef({IGNORE, CALL_LISTENER})
    public @interface ListenerMode {
    }

    public SafeCheckBox(Context context) {
        super(context);
        init(context);
    }

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

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

    /**
     * @param checkState     change state of the checkbox to 
     * @param mIgnoreListener true to ignore the listener else listener will be  notified
     */
    public void setSafeCheck(boolean checkState, @ListenerMode int mIgnoreListener) {
        if (isChecked() == checkState) return; //already in the same state no need to fire listener. 

        if (onSafeCheckedListener != null) { // this to avoid a bug if the user listens for the event after using this method and in that case he will miss first check
            this.mIgnoreListener = mIgnoreListener;
        } else {
            this.mIgnoreListener = CALL_LISTENER;
        }
        setChecked(checkState);
    }

    private void init(Context context) {
        setOnCheckedChangeListener(this);
    }


    public OnSafeCheckedListener getOnSafeCheckedListener() {
        return onSafeCheckedListener;
    }

    public void setOnSafeCheckedListener(OnSafeCheckedListener onSafeCheckedListener) {
        this.onSafeCheckedListener = onSafeCheckedListener;
    }

    @Override
    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {

        if (onSafeCheckedListener != null)
            onSafeCheckedListener.onAlwaysCalledListener(buttonView, isChecked);// this has to be called before onCheckedChange
        if (onSafeCheckedListener != null && (mIgnoreListener == CALL_LISTENER)) {
            onSafeCheckedListener.onCheckedChanged(buttonView, isChecked);
        }
        mIgnoreListener = CALL_LISTENER;
    }

    /**
     * Listener that will be called when you want it to be called.
     * On checked change listeners are called even when the setElementChecked is called from code. :(
     */
    public interface OnSafeCheckedListener extends OnCheckedChangeListener {
        void onAlwaysCalledListener(CompoundButton buttonView, boolean isChecked);
    }
}
  • Quindi puoi chiamare: -

    setSafeCheck(true,ListenerMode.IGNORE);// OnCheckedChange listener will not be notified


3

Impostare null su changeListener prima di selezionare il pulsante di opzione. È possibile impostare nuovamente l'ascoltatore dopo aver controllato il pulsante di opzione.

radioGroup.setOnCheckedChangeListener(null);
radioGroup.check(R.id.radioButton);
radioGroup.setOnCheckedChangeListener(new 

RadioGroup.OnCheckedChangeListener() {
   @Override
   public void onCheckedChanged(RadioGroup radioGroup, @IdRes int i) {

   }
});

1
Fa il lavoro ed è molto più veloce e più semplice rispetto alle altre soluzioni qui.
PayToPwn,

3

La mia interpretazione, che penso sia la più semplice,
può essere utile)

public class ProgrammableSwitchCompat extends SwitchCompat {

    public boolean isCheckedProgrammatically = false;

    public ProgrammableSwitchCompat(final Context context) {
        super(context);
    }

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

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

    @Override
    public void setChecked(boolean checked) {
        isCheckedProgrammatically = false;
        super.setChecked(checked);
    }

    public void setCheckedProgrammatically(boolean checked) {
        isCheckedProgrammatically = true;
        super.setChecked(checked);
    }
}

usalo

@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean on) {
    if (((ProgrammableSwitchCompat) compoundButton).isCheckedProgrammatically) {
        return;
    }
    //...
    ((ProgrammableSwitchCompat) compoundButton).setCheckedProgrammatically(true);
    //...
    ((ProgrammableSwitchCompat) compoundButton).setCheckedProgrammatically(false);
    //...
}

l'uso attiverà la setChecked(boolean)funzione
che è tutto

Kotlin

class MyCheckBox @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = R.attr.switchStyle
) : AppCompatCheckBox(context, attrs, defStyleAttr) {

    var programmatically = false

    override fun setChecked(checked: Boolean) {
        programmatically = false
        super.setChecked(checked)
    }

    fun setCheckedProgrammatically(checked: Boolean) {
        programmatically = true
        super.setChecked(checked)
    }
}

2

Questa è una soluzione semplice che ho usato:
Definisci un listener personalizzato:

class CompoundButtonListener implements CompoundButton.OnCheckedChangeListener {

    boolean enabled = false;

    @Override
    public void onCheckedChanged(CompoundButton compoundButton, boolean checked) {

    }

    void enable() {
        enabled = true;
    }

    void disable() {
        enabled = false;
    }

    boolean isEnabled() {
        return enabled;
    }
}

Inizializzazione:

CompoundButtonListener checkBoxListener = new CompoundButtonListener() {
    @Override
    public void onCheckedChanged(CompoundButton compoundButton, boolean checked) {
        if (isEnabled()) {
            // Your code goes here
        }
    }
};
myCheckBox.setOnCheckedChangeListener(checkBoxListener);

Uso:

checkBoxListener.disable();

// Some logic based on which you will modify CheckBox state
// Example: myCheckBox.setChecked(true)

checkBoxListener.enable();

2

Cosa ne pensi di questo. Prova a usare Tag in View

mCheck.setTag("ignore");
mCheck.setChecked(true);
mCheck.setTag(null);

e

switch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
        @Override
        public void onCheckedChanged(CompoundButton compoundButton, boolean selected) {

            //If switch has a tag, ignore below
            if(compoundButton.getTag() != null)
                return; 

            if (selected) {
                // do something
            } else {
                // do something else
            }

        }
    });

2

Ho usato un ReentrantLock, e bloccarlo ogni volta che sto impostando isChecked:

// lock when isChecked is being set programmatically
val isBeingProgrammaticallySet = ReentrantLock()

// set isChecked programmatically
isBeingProgrammaticallySet.withLock()
{
    checkbox.isChecked = true
}

// do something only when preference is modified by user
checkbox.setOnCheckedChangeListener()
{
    _,isChecked ->
    if (isBeingProgrammaticallySet.isHeldByCurrentThread.not())
    {
        // do it
    }
}

1

Ho trovato tutte le risposte di cui sopra troppo complicate. Perché non creare semplicemente la tua bandiera con un semplice booleano?

Basta usare un semplice sistema di bandiera con un valore booleano. Crea boolean noListener. Ogni volta che vuoi accendere / spegnere l'interruttore senza eseguire alcun codice (in questo esempio, rappresentato come runListenerCode(), imposta semplicemente noListener=trueprima di chiamareswitch.setChecked(false/true)

switch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
           @Override
           public void onCheckedChanged(CompoundButton compoundButton, boolean selected) {
               if (!noListener) { //If we want to run our code like usual
                   runListenerCode();
               } else { //If we simply want the switch to turn off
                   noListener = false;
               }
           });

Soluzione molto semplice usando semplici flag. Alla fine, abbiamo impostato noListener=falseancora una volta in modo che il nostro codice continui a funzionare. Spero che questo ti aiuti!


1

Prova questo dovrebbe funzionare per te! Puoi usarlo anche con firebase!

Per ottenere i dati del Firebase! Usa questo!

databaseReference.child(user.getPhoneNumber()).child("Reqs").addValueEventListener(new ValueEventListener() {

        @Override
        public void onDataChange(DataSnapshot dataSnapshot) {
            SharedPreferences prefs = mContext.getSharedPreferences("uinfo", MODE_PRIVATE);
            String pno = prefs.getString("username", "No name defined");

            if(dataSnapshot.child(pno).getValue(String.class).equals("acc")){
                holder.acc.setChecked(true);
            }else{
                holder.acc.setChecked(false);
            }
        }

        @Override
        public void onCancelled(DatabaseError databaseError) {
            // Getting Post failed, log a message
            Log.w("dfs", "loadPost:onCancelled", databaseError.toException());
            // ...
        }
    });

Dopo ciò quando l'utente fa qualcosa!

holder.acc.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                if(isChecked) {
                    if(buttonView.isPressed()) {
                        //your code
                    }
                }
                else {
                    if(buttonView.isPressed()) {
                       //your code
                    }
                }
            }
        });

1

Non volevo davvero passare l'ascoltatore ogni volta che abbiamo impostato il controllo modificato, né l'utilizzo enabled come modo per determinare se dovremmo impostare il valore (cosa succede nel caso in cui l'interruttore sia già disabilitato quando si imposta il valore ?)

Invece sto facendo uso di tag con un ID e un paio di metodi di estensione che puoi chiamare:

fun CompoundButton.setOnCheckedWithoutCallingChangeListener(
    listener: (view: CompoundButton, checked: Boolean) -> Unit
) {
    setOnCheckedChangeListener { view, checked ->
        if (view.getTag(R.id.compound_button_checked_changed_listener_disabled) != true) {
            listener(view, checked)
        }
    }
    this.setTag(R.id.compound_button_enabled_checked_change_supported, true)
}

fun CompoundButton.setCheckedWithoutCallingListener(checked: Boolean) {
    check(this.getTag(R.id.compound_button_enabled_checked_change_supported) == true) {
        "Must set listener using `setOnCheckedWithoutCallingChangeListener` to call this method" 
    }

    setTag(R.id.compound_button_checked_changed_listener_disabled, true)
    isChecked = checked
    setTag(R.id.compound_button_checked_changed_listener_disabled, false)
}

Ora puoi chiamare setCheckedWithoutCallingListener(bool)e imporrà l'uso corretto dell'ascoltatore.

Puoi anche chiamare setChecked(bool)per licenziare l'ascoltatore se ne hai ancora bisogno


0

Immagino che usare la riflessione sia l'unico modo. Qualcosa come questo:

CheckBox cb = (CheckBox) findViewById(R.id.checkBox1);
try {
    Field field = CompoundButton.class.getDeclaredField("mChecked");
    field.setAccessible(true);
    field.set(cb, cb.isChecked());
    cb.refreshDrawableState();
    cb.invalidate();
} catch (Exception e) {
    e.printStackTrace();
}

Può funzionare fino a quando gli sviluppatori cambiano il nome del campo o, ad esempio, tirano su il metodo "isChecked" nella gerarchia ... o eseguono un altro refactoring ... Almeno aggiungi qualcosa di simileif(Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN){ /* do reflection */}
aeracode

È un'idea sbagliata incoraggiare a rompere l'API e scavare negli interni. Qualsiasi modifica all'implementazione provocherà il fallimento delle app.
mspanc,

0

La mia soluzione scritta in Java basata sulla risposta di @Chris:

chkParent.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                if(buttonView.getTag() != null){
                    buttonView.setTag(null);
                    return;
                }
                if(isChecked){
                    chkChild.setTag(true);
                    chkChild.setChecked(false);
                }
                else{
                    chkParent.setChecked(true);
                }
            }
});

chkChild.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                if(buttonView.getTag() != null){
                    buttonView.setTag(null);
                    return;
                }
                if(isChecked){
                    chkParent.setTag(true);
                    chkParent.setChecked(false);
                }
                else{
                    chkChild.setChecked(true);
                }
            }
});

2 caselle di controllo e sempre una sarà selezionata (una deve essere controllata inizialmente però). Impostazione del tag su blocchi veri nel listener CheckCheckChanged.


0
public void setCheckedChangeListenerSwitch() {

    switch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
        @Override
        public void onCheckedChanged(CompoundButton compoundButton, boolean selected) {

            if (selected) {
                // do something
            } else {
                // do something else
            }

        }
    });

}

// Update state & reset listener (so onCheckedNot called)
public void updateSwitchState(boolean state){

    switch.setOnCheckedChangeListener(null);
    switch.setChecked(state);
    setCheckedChangeListenerSwitch();

}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.