¿Cómo puedo distinguir si Switch, Checkbox Value se cambia por usuario o programáticamente (incluso por retención)?

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

¿Cómo implementar el método isNotSetByUser()?

Solvek
fuente
No estoy seguro, pero creo que si el usuario lo activa, también obtendrás una devolución de llamada onClick si configuras ese oyente. Entonces, tal vez pueda configurar un indicador booleano en el onClick de esa manera puede verificarlo en onCheckChanged para ver si el usuario inició el cambio.
FoamyGuy
Tengo una solución más simple y clara: consulte stackoverflow.com/a/41574200/3256989
ultraon

Respuestas:

157

Respuesta 2:

Una respuesta muy simple:

Usar en OnClickListener en lugar 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);
        }

    });

Ahora solo selecciona eventos de clic y no tiene que preocuparse por los cambios programáticos.


Respuesta 1:

He creado una clase contenedora (ver Patrón de decorador) que maneja este problema de forma encapsulada:

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 aún se puede declarar de la misma manera en XML, pero utilícelo al inicializar su GUI en el código:

BetterCheckBox myCheckBox;

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

Si desea establecer marcado desde el código sin activar el oyente, llame en myCheckBox.silentlySetChecked(someBoolean)lugar de setChecked.

antropomo
fuente
15
Para a Switch, la respuesta 1 funciona tanto en el caso de toque como en el de diapositiva, mientras que la respuesta 2 solo funcionará en el caso de toque. Como preferencia personal, hice que mi clase se extendiera CheckBox/ en Switchlugar de tener una referencia a una. Se siente más limpio de esa manera (tenga en cuenta que debe especificar el nombre completo del paquete en el XML si lo hace).
howettl
1
Gracias por este antropomo, no sé por qué no lo pensé antes, pero me ahorraste un tiempo precioso;). Salud !
CyberDandy
No estoy seguro de esto, pero si extiende SwitchCompat (usando appcompat v7) para obtener el cambio de diseño de material, puede terminar las nuevas capacidades de rediseño y tintado.
DNax
4
Ambas soluciones tienen graves defectos. Primera solución: cuando el usuario arrastra el interruptor, el oyente no es despedido. Segunda solución: Debido a que setOnCheckedChangeListener hace esa verificación nula, realmente establece el antiguo oyente cuando ya hay un nuevo establecido.
Paul Woitaschek
Primera solución: problema conocido y semi-aceptable si desea una solución concisa y no tiene la intención de usar ese widget. Segunda solución: cambió la verificación nula para abordar ese tipo de caso extremo improbable. Ahora asignamos listenera myListenersiempre listenerque no sea nulo. En casi todos los casos esto no hace nada, pero si hacemos un nuevo oyente por alguna razón, eso no está roto.
anthropomo
38

Tal vez puedas comprobar isShown ()? Si es VERDADERO, entonces es usuario. Funciona para mi.

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
fuente
1
Funciona (es decir, sin devolución de llamada inesperada) incluso si llama a "setChecked (isChecked)" en onStart () o onResume (). Por lo que podría considerarse una solución perfecta.
Alan Zhiliang Feng
1
Parece que no es una solución general. ¿Qué pasa si el botón se muestra en el momento pero su valor se cambia del código?
Pavel
1
No lo entiendo, ¿cómo 'isShown ()' se distingue entre acciones del usuario y cambios programáticos? ¿cómo puede decir que es una acción del usuario si 'isShown ()' es verdadero?
Muhammed Refaat
1
Esta solución funciona solo en casos muy específicos y se basa en un proceso de creación y diseño de actividades no documentado y no garantizado. No hay garantía de que esto no se rompa en el futuro y no funciona si una vista ya está renderizada.
Manuel
26

En el interior, onCheckedChanged()solo verifique si el usuario tiene realmente checked/uncheckedel botón de opción y luego haga las cosas en consecuencia de la siguiente manera:

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
fuente
Buena solución, no es necesario personalizar la vista.
Aju
te quiero. : DDD
Vlad
12
esto no funciona cuando el usuario cambia el interruptor deslizando / deslizando
mohitum
1
Usando este enfoque en todas partes, pero encontré un caso, no funciona ya que isPresseddevuelve falso, dispositivo Nokia con TalkBack activado.
Mikhail
SwitchMaterial funciona sin problema. ¡Buena decisión, gracias!
R00We
25

Puede eliminar el oyente antes de cambiarlo programáticamente y agregarlo nuevamente, como se responde en la siguiente publicación SO:

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

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

Intente extender CheckBox. Algo así (ejemplo no completo):

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
fuente
3

Hay otra solución sencilla que funciona bastante bien. El ejemplo es para 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;
    }
}
Invisado
fuente
2

Interesante pregunta. Que yo sepa, una vez que estás en el oyente, no puedes detectar qué acción ha desencadenado al oyente, el contexto no es suficiente. A menos que utilice un valor booleano externo como indicador.

Cuando marque la casilla "programáticamente", establezca un valor booleano antes para indicar que se hizo mediante programación. Algo como:

private boolean boxWasCheckedProgrammatically = false;

....

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

Y en su oyente, no olvide restablecer el estado de la casilla de verificación:

@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
fuente
2

Prueba NinjaSwitch:

¡Simplemente llame setChecked(boolean, true)para cambiar el estado verificado del interruptor sin ser detectado!

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
fuente
2

Si OnClickListenerya está configurado y no debe sobrescribirse, use !buttonView.isPressed()como isNotSetByUser().

De lo contrario, la mejor variante es usar en OnClickListenerlugar de OnCheckedChangeListener.

Pavel
fuente
buttonView.isPressed () es una buena solución. Hay un problema cuando usamos OnClickListener, cuando el usuario desliza el interruptor, no recibiremos la devolución de llamada.
Aju
2

La respuesta aceptada podría simplificarse un poco para no mantener una referencia a la casilla de verificación original. Esto hace que podamos usar SilentSwitchCompat(o SilentCheckboxCompatsi lo prefiere) directamente en el XML. También lo hice para que pueda establecer el OnCheckedChangeListenerque nullsi el deseo de hacerlo.

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

Luego puede usar esto directamente en su XML así (Nota: necesitará el nombre completo del paquete):

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

Luego, puede marcar la casilla en silencio llamando a:

SilentCheckBox mySilentCheckBox = (SilentCheckBox) findViewById(R.id.my_check_box)
mySilentCheckBox.silentlySetChecked(someBoolean)
Pequeños
fuente
1

Aquí está mi implementación

Código Java para conmutador personalizado:

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

Código Kotlin equivalente:

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

Para cambiar el estado del interruptor sin activar el uso del oyente:

swSelection.setCheckedSilently(contact.isSelected)

Puede supervisar el cambio de estado normalmente mediante:

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

En Kotlin:

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

Mi variante con funciones de extensión de Kotlin:

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

... desafortunadamente tenemos que pasar onCheckedChangeListener cada vez porque la clase CheckBox no tiene getter para el campo mOnCheckedChangeListener ((

Uso:

checkbox.setCheckedSilently(true, myCheckboxListener)
Fedir Tsapana
fuente
0

Crea una 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;          

}

En la aplicación, cuando lo cambie en lugar del usuario, llame notSetByUser(true)para que no lo configure el usuario, de lo contrario, llamenotSetByUser(false) es decir, lo establezca el programa.

Por último, en su detector de eventos, después de llamar a isNotSetByUser (), asegúrese de volver a cambiarlo a normal.

Llame a este método siempre que esté manejando esa acción a través del usuario o mediante programación. Llame a notSetByUser () con el valor apropiado.

Tvd
fuente
0

Si no se usa la etiqueta de la vista, puede usarla en lugar de extender la casilla de verificación:

        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
                    }

cada vez que llame a algo que marque / desmarque la casilla de verificación, también llámelo antes de marcar / desmarcar:

checkbox.setTag(true);
desarrollador de Android
fuente
0

He creado una extensión con RxJava PublishSubject, simple. Reacciona solo en eventos "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
fuente