Modifier la valeur de la case à cocher sans déclencher onCheckChanged


147

J'ai setOnCheckedChangeListenerimplémenté pour moncheckbox

Y a-t-il un moyen que je puisse appeler

checkbox.setChecked(false);

sans déclencher le onCheckedChanged


Pourquoi ne pas simplement utiliser un simple drapeau vrai / faux? C'est le moyen le plus simple de résoudre ce problème et cela ne prend que trois lignes de code supplémentaires. Voir ma réponse ci-dessous.
Ruchir Baronia

Réponses:


280

Non, tu ne peux pas le faire. La onCheckedChangedméthode est appelée directement à partir de setChecked. Voici ce que vous pouvez faire:

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

Voir la source de CheckBox et l'implémentation de 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
Comment proposez-vous d'obtenir mListener? Checkboxn'a pas de getter pour sonOnCheckChangeListener
tir38

20
Eh bien, pas besoin de voter simplement parce que vous ne comprenez pas la solution. mListenerest une implémentation de l' OnCheckChangedListenerinterface, qui a été créée par le programmeur. Ma réponse implique que le programmeur a maintenu une référence à sa propre implémentation - mListener.
Ombre le

Serait-il inefficace de changer l'écouteur si vous souhaitez utiliser la méthode setChecked () de manière répétitive?
Ren

4
@Ren, la modification de l'écouteur implique uniquement la définition d'une propriété dans l' CheckBoxobjet. Je ne dirais pas que c'est inefficace.
Ombre le

Le document lit "Appelé lorsque le bouton radio coché a changé. Lorsque la sélection est effacée, vérifiéId est -1". Ceci est vraiment trompeur, il devrait également avoir le isChecked.
AA_PV

69

Ajoutez ce code dans OnCheckedChangeListener:

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

Cela nous aidera à comprendre que l'état de la case à cocher météo a été modifié par programme ou par action de l'utilisateur.


11
Cela devrait être marqué comme une solution, fonctionne comme un charme!
Ashwin Balani

5
être conscient! Cela rompt le mode d'accessibilité! isPressed () n'est pas vrai lorsqu'il est déclenché par une double pression depuis le mode assistant vocal.
A1m

21

Un autre moyen possible d'y parvenir est d'utiliser une case à cocher personnalisée, qui vous permettra de choisir si vous voulez que l'écouteur soit appelé ou non:

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();
    }
}

Version Kotlin, si vous préférez:

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()
    }
}

utilisation de l'échantillon:

checkBox.setChecked(true,false);

11

vous utilisez simplement setonclickListener, cela fonctionnera très bien et c'est une méthode très simple, merci :)


35
onClick () n'est pas appelé lorsque l'utilisateur fait glisser le commutateur.
James

2
En effet, cela vous laisse sans gestionnaire lorsque l'utilisateur fait glisser la bascule.
Victor G du

alors comment vous obtiendrez la valeur isChecked c'est-à-dire vrai ou faux?
Abhi

6

Utilisation des extensions de Kotlin avec la réponse @Shade:

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

6

Pour tous ceux qui trébuchent sur cela, un moyen plus simple de le faire est d'utiliser simplement une balise sur la case à cocher, puis de vérifier cette balise sur son auditeur (le code est en Kotlin):

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

Ensuite, lors de l'accès, définissez simplement la balise sur true avant de définir isChecked sur true lorsque vous souhaitez ignorer le changement de valeur:

checkBox.tag = true
checkBox.isChecked = true

Vous pouvez également mapper la balise à une clé en utilisant la méthode alternative setTag qui nécessite une clé si vous vous inquiétez de la compréhensibilité. Mais si tout est contenu dans une seule classe, quelques chaînes de commentaires seront plus que suffisantes pour expliquer ce qui se passe.


4

Vous pouvez utiliser cette classe SafeCheckBox comme case à cocher:

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);
    }
}
  • Ensuite, vous pouvez appeler: -

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


3

Définissez null sur changeListener avant de cocher le bouton radio. Vous pouvez définir à nouveau l'auditeur après avoir coché le bouton radio.

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

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

   }
});

1
Fait le travail et c'est beaucoup plus rapide et plus simple que les autres solutions ici.
PayToPwn

3

Mon interprétation que je pense être la plus simple
peut être 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);
    }
}

utilise le

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

l'utilisation déclenchera la setChecked(boolean)fonction
qui est tout

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

C'est une solution simple que j'ai utilisée:
définir un écouteur personnalisé:

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;
    }
}

Initialisation:

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

Usage:

checkBoxListener.disable();

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

checkBoxListener.enable();

2

Que dis-tu de ça. Essayez d'utiliser la balise dans la vue

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

et

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

J'ai utilisé un ReentrantLock, et je le verrouille chaque fois que je règle 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

J'ai trouvé toutes les réponses ci-dessus bien trop compliquées. Pourquoi ne pas simplement créer votre propre drapeau avec un simple booléen?

Utilisez simplement un système d'indicateur simple avec un booléen. Créez boolean noListener. Chaque fois que vous souhaitez activer / désactiver votre interrupteur sans exécuter de code (dans cet exemple, représenté par runListenerCode(), il suffit de définir noListener=trueavant d'appelerswitch.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;
               }
           });

Solution très simple utilisant des drapeaux simples. À la fin, nous réglons noListener=falseà nouveau pour que notre code continue de fonctionner. J'espère que cela t'aides!


1

Essayez celui-ci devrait fonctionner pour vous! Vous pouvez également l'utiliser avec Firebase!

Pour obtenir des données Firebase! Utilisez ceci!

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());
            // ...
        }
    });

Après cela, lorsque l'utilisateur fait quelque chose!

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

Je ne voulais pas vraiment avoir à passer l'auditeur à chaque fois que nous définissons coché changé, ni utiliser enabledcomme moyen de déterminer si nous devons définir la valeur (que se passe-t-il dans le cas où le commutateur est déjà désactivé lors de la définition de la valeur ?)

Au lieu de cela, j'utilise des balises avec un identifiant et quelques méthodes d'extension que vous pouvez appeler:

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)
}

Vous pouvez maintenant appeler setCheckedWithoutCallingListener(bool)et cela imposera l'utilisation correcte de l'auditeur.

Vous pouvez également appeler setChecked(bool)pour renvoyer l'auditeur si vous en avez encore besoin


0

Je suppose que l'utilisation de la réflexion est le seul moyen. Quelque chose comme ça:

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();
}

Peut fonctionner jusqu'à ce que les développeurs changent le nom du champ ou, par exemple, tirent la méthode "isChecked" dans la hiérarchie ... ou font une autre refactorisation ... Au moins ajouter quelque chose commeif(Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN){ /* do reflection */}
aeracode

C'est une mauvaise idée d'encourager à casser l'API et à creuser dans les internes. Toute modification de l'implémentation entraînera l'échec des applications.
mspanc

0

Ma solution écrite en java basée sur la réponse @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 cases à cocher et toujours une sera cochée (une doit être cochée au départ). Définition de la balise sur true blocs onCheckedChanged listener.


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();

}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.