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 implémenter la méthode isNotSetByUser()
?
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 liés aux clics et vous n'avez pas à vous soucier des modifications de programme.
Réponse 1:
J'ai créé une classe de wrapper (voir Motif de décorateur) qui traite 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é identique en XML, mais utilisez-le pour initialiser 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 une vérification à partir du code sans déclencher l'écouteur, appelez myCheckBox.silentlySetChecked(someBoolean)
au lieu de setChecked
.
Peut-être que vous pouvez 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();
}
}
});
Vous pouvez supprimer l'auditeur avant de le modifier par programme et l'ajouter à nouveau, comme indiqué dans l'article SO suivant:
https://stackoverflow.com/a/14147300/1666070
theCheck.setOnCheckedChangeListener(null);
theCheck.setChecked(false);
theCheck.setOnCheckedChangeListener(toggleButtonChangeListener);
Dans onCheckedChanged (), il suffit de vérifier si l’utilisateur a bien coché/décoché le bouton radio, puis procédez 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.
}
}
});
Essayez d'étendre CheckBox. Quelque chose comme ça (exemple non complet):
public MyCheckBox extends CheckBox {
private Boolean isCheckedProgramatically = false;
public void setChecked(Boolean checked) {
isCheckedProgramatically = true;
super.setChecked(checked);
}
public Boolean isNotSetByUser() {
return isCheckedProgramatically;
}
}
Il existe une autre solution simple qui fonctionne assez 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;
}
}
Question interessante. À ma connaissance, une fois que vous êtes dans l'écouteur, vous ne pouvez pas détecter l'action qui l'a déclenché, le contexte n'est pas suffisant. 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 pour indiquer que cela a été fait 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;
}
Si OnClickListener
est déjà défini et ne doit pas être remplacé, utilisez !buttonView.isPressed()
en tant que isNotSetByUser()
.
Sinon, la meilleure variante consiste à utiliser OnClickListener
au lieu de OnCheckedChangeListener
.
La réponse acceptée pourrait être un peu simplifiée pour ne pas conserver une référence à la case à cocher d'origine. Cela nous permet donc d'utiliser la variable SilentSwitchCompat
(ou SilentCheckboxCompat
si vous préférez) directement dans le fichier XML. J'ai également fait en sorte que vous puissiez définir la OnCheckedChangeListener
sur null
si 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 utiliser ceci directement dans votre XML comme suit (Remarque: vous aurez besoin du nom complet du paquet):
<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)
Essayez NinjaSwitch
:
Il suffit d'appeler setChecked(boolean, true)
pour modifier l'état vérifié du commutateur sans être détecté!
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);
}
}
}
Voici ma mise en place
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'utilisation de l'écouteur:
swSelection.setCheckedSilently(contact.isSelected)
Vous pouvez surveiller le changement d'état comme d'habitude 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
}}
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 chaque fois onCheckedChangeListener car la classe CheckBox n’a pas de getter pour le champ mOnCheckedChangeListener
Usage:
checkbox.setCheckedSilently(true, myCheckboxListener)
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 que ce ne soit pas défini par l'utilisateur, sinon appelez notSetByUser(false)
c'est-à-dire qu'il soit défini par programme.
Enfin, dans votre écouteur d'événement, après avoir appelé isNotSetByUser (), assurez-vous de le redéfinir sur normal.
Appelez cette méthode chaque fois que vous gérez cette action via l'utilisateur ou par programme. Appelez le notSetByUser () avec la valeur appropriée.
J'ai créé une extension avec PublishSubject
, une version simple de RxJava. 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
}
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);