Comment puis-je distinguer si Switch, Checkbox Value est modifié par l'utilisateur ou par programme (y compris par rétention)?

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

Comment mettre en œuvre la méthode isNotSetByUser()?

Solvek
la source
Je ne suis pas certain, mais je pense que si l'utilisateur le basculait, vous obtiendrez également un rappel onClick si vous définissez cet auditeur. Alors peut-être que vous pouvez définir un indicateur booléen dans le onClick de cette façon, vous pouvez le vérifier dans onCheckChanged pour voir si l'utilisateur a initié le changement.
FoamyGuy
J'ai une solution plus simple et claire: voir stackoverflow.com/a/41574200/3256989
ultraon

Réponses:

157

Réponse 2:

Une réponse très simple:

Utiliser sur OnClickListener au lieu de 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);
        }

    });

Désormais, vous ne récupérez que les événements de clic et vous n'avez pas à vous soucier des modifications programmatiques.


Réponse 1:

J'ai créé une classe wrapper (voir Decorator Pattern) qui gère ce problème de manière encapsulée:

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

CheckBox peut toujours être déclaré de la même manière en XML, mais utilisez ceci lors de l'initialisation de votre interface graphique dans le code:

BetterCheckBox myCheckBox;

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

Si vous souhaitez définir vérifié à partir du code sans déclencher l'écouteur, appelez à la myCheckBox.silentlySetChecked(someBoolean)place de setChecked.

anthropomo
la source
15
Pour a Switch, la réponse 1 fonctionne à la fois dans les cas du robinet et de la diapositive, tandis que la réponse 2 ne fonctionnera que dans le cas du robinet. Par préférence personnelle, j'ai demandé à ma classe d'étendre CheckBox/ Switchplutôt que de tenir une référence à une. Cela semble plus propre (notez que vous devez spécifier le nom complet du package dans le XML si vous faites cela).
howettl le
1
Merci pour cet anthropomo, je ne sais pas pourquoi je n'y ai pas pensé plus tôt, mais tu m'as fait gagner un temps précieux;). À votre santé !
CyberDandy
Je ne suis pas sûr de cela, mais si vous étendez SwitchCompat (en utilisant appcompat v7) pour obtenir le commutateur de conception matérielle, vous pouvez mettre fin aux nouvelles capacités de refonte et de teinte.
DNax
4
Les deux solutions présentent de graves défauts. Première solution: lorsque l'utilisateur fait glisser le commutateur, l'auditeur n'est pas déclenché. Deuxième solution: étant donné que setOnCheckedChangeListener effectue cette vérification nulle, il définit en fait l'ancien écouteur lorsqu'il existe un nouveau déjà défini.
Paul Woitaschek
Première solution: problème connu et semi-acceptable si vous voulez une solution concise et n'avez pas l'intention d'utiliser ce widget. Deuxième solution: modification de la vérification de la valeur NULL pour résoudre ce genre de cas de bord improbable. Maintenant, nous attribuons listenerà myListenerchaque fois listenern'est pas nul. Dans presque tous les cas, cela ne fait rien, mais si nous créons un nouvel auditeur pour une raison quelconque, ce n'est pas cassé.
anthropomo
38

Peut-être pouvez-vous vérifier isShown ()? Si TRUE - que c'est l'utilisateur. Travaille pour moi.

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();
        }
    }
});
Denis Babak
la source
1
Cela fonctionne (ce qui signifie pas de rappel inattendu) même si vous appelez "setChecked (isChecked)" dans onStart () ou onResume (). Cela pourrait donc être considéré comme une solution parfaite.
Alan Zhiliang Feng
1
Cela ne semble pas être une solution générale. Que faire si le bouton est affiché sur le moment mais que sa valeur est modifiée par rapport au code?
Pavel
1
Je ne comprends pas, comment 'isShown ()' fait-il la distinction entre les actions de l'utilisateur et les changements programmatiques? comme comment il peut dire que c'est une action de l'utilisateur si 'isShown ()' est vrai?
Muhammed Refaat
1
Cette solution ne fonctionne que dans des cas très spécifiques et repose sur un processus de création et de mise en page d'activités non documenté et non garanti. Il n'y a aucune garantie que cela ne se cassera pas à l'avenir et cela ne fonctionnera pas si une vue est déjà rendue.
Manuel du
26

À l'intérieur, onCheckedChanged()vérifiez simplement si l'utilisateur a réellement checked/uncheckedle bouton radio, puis faites les choses en conséquence comme suit:

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.   
    }
  }
});
adi
la source
Bonne solution, pas besoin de personnaliser la vue.
Aju
Je t'aime. : DDD
Vlad
12
cela ne fonctionne pas lorsque l'utilisateur bascule le commutateur en glissant / glissant
mohitum
1
En utilisant cette approche partout, mais en trouvant un cas, cela ne fonctionne pas car isPressedrenvoie faux, appareil Nokia avec TalkBack activé.
Mikhail
SwitchMaterial fonctionne sans problème. Bonne décision, merci!
R00We
25

Vous pouvez supprimer l'écouteur avant de le modifier par programme et l'ajouter à nouveau, comme indiqué dans le message SO suivant:

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

theCheck.setOnCheckedChangeListener(null);
theCheck.setChecked(false);
theCheck.setOnCheckedChangeListener(toggleButtonChangeListener);
Eli Kohen
la source
4

Essayez d'étendre CheckBox. Quelque chose comme ça (exemple incomplet):

public MyCheckBox extends CheckBox {

   private Boolean isCheckedProgramatically = false;

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

   public Boolean isNotSetByUser() {
      return isCheckedProgramatically;
   }

}
Romain Piel
la source
3

Il existe une autre solution simple qui fonctionne plutôt bien. L'exemple est pour 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;
    }
}
Invisé
la source
2

Question interessante. À ma connaissance, une fois que vous êtes dans l'auditeur, vous ne pouvez pas détecter quelle action a déclenché l'auditeur, le contexte ne suffit pas. Sauf si vous utilisez une valeur booléenne externe comme indicateur.

Lorsque vous cochez la case «par programme», définissez une valeur booléenne avant d'indiquer qu'il a été effectué par programme. Quelque chose comme:

private boolean boxWasCheckedProgrammatically = false;

....

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

Et dans votre auditeur, n'oubliez pas de réinitialiser l'état de la case à cocher:

@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;
}
Guillaume
la source
2

Essayez NinjaSwitch:

Appelez simplement setChecked(boolean, true)pour changer l'état vérifié du commutateur sans détection!

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);
        }
    }
}
Taeho Kim
la source
2

Si OnClickListenerest déjà défini et ne doit pas être écrasé, utilisez !buttonView.isPressed()as isNotSetByUser().

Sinon, la meilleure variante est d'utiliser à la OnClickListenerplace de OnCheckedChangeListener.

Pavel
la source
buttonView.isPressed () est une solution intéressante. Il y a un problème lorsque nous utilisons OnClickListener, lorsque l'utilisateur glisse sur le commutateur, nous n'obtiendrons pas de rappel.
Aju le
2

La réponse acceptée pourrait être un peu simplifiée pour ne pas conserver une référence à la case d'origine. Cela nous permet d'utiliser le SilentSwitchCompat(ou SilentCheckboxCompatsi vous préférez) directement dans le XML. Je l'ai également fait pour que vous puissiez définir le OnCheckedChangeListenersur nullsi vous le souhaitez.

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

Vous pouvez ensuite l'utiliser directement dans votre XML comme ceci (Remarque: vous aurez besoin du nom complet du package):

<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"/>

Ensuite, vous pouvez cocher la case en silence en appelant:

SilentCheckBox mySilentCheckBox = (SilentCheckBox) findViewById(R.id.my_check_box)
mySilentCheckBox.silentlySetChecked(someBoolean)
Petits
la source
1

Voici ma mise en œuvre

Code Java pour le commutateur personnalisé:

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

Code Kotlin équivalent:

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

Pour changer l'état du commutateur sans déclencher l'écouteur, utilisez:

swSelection.setCheckedSilently(contact.isSelected)

Vous pouvez surveiller le changement d'état normalement en:

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

À Kotlin:

 swSelection.setOnCheckedChangeListener{buttonView, isChecked -> run {
            contact.isSelected = isChecked
        }}
Thilina Kj
la source
1

Ma variante avec les fonctions d'extension Kotlin:

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

... malheureusement, nous devons passer onCheckedChangeListener à chaque fois car la classe CheckBox n'a pas de getter pour le champ mOnCheckedChangeListener ((

Usage:

checkbox.setCheckedSilently(true, myCheckboxListener)
Fedir Tsapana
la source
0

Créer une variable

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;          

}

Dans l'application lorsque vous le modifiez à la place de l'utilisateur, appelez notSetByUser(true)pour qu'il ne soit pas défini par l'utilisateur, sinon appelez notSetByUser(false)c'est-à - dire qu'il est défini par programme.

Enfin, dans votre écouteur d'événements, après avoir appelé isNotSetByUser (), assurez-vous de le remettre à nouveau à la normale.

Appelez cette méthode chaque fois que vous gérez cette action via l'utilisateur ou par programme. Appelez notSetByUser () avec la valeur appropriée.

Tvd
la source
0

Si la balise de la vue n'est pas utilisée, vous pouvez l'utiliser au lieu d'étendre la case à cocher:

        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
                    }

chaque fois que vous appelez quelque chose qui coche / décoche la case, appelez également ceci avant de cocher / décocher:

checkbox.setTag(true);
développeur android
la source
0

J'ai créé une extension avec RxJava PublishSubject, simple. Réagit uniquement sur les événements "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
}
Janusz Hain
la source