Come posso distinguere se Switch, Checkbox Value viene modificato dall'utente o in modo programmatico (incluso per conservazione)?


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

Come implementare il metodo isNotSetByUser()?


Non ne sono sicuro, ma penso che se l'utente lo attivasse, otterresti anche una richiamata onClick se imposti quell'ascoltatore. Quindi forse puoi impostare un flag booleano in onClick in questo modo puoi controllarlo in onCheckChanged per vedere se l'utente ha avviato la modifica.
FoamyGuy


Ho la soluzione più semplice e chiaro: vedere stackoverflow.com/a/41574200/3256989
ultraon

Risposte:


157

Risposta 2:

Una risposta molto semplice:

Utilizzare su OnClickListener invece di 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);
        }

    });

Ora raccogli solo eventi di clic e non devi preoccuparti delle modifiche programmatiche.


Risposta 1:

Ho creato una classe wrapper (vedi Decorator Pattern) che gestisce questo problema in modo incapsulato:

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 può ancora essere dichiarato lo stesso in XML, ma usalo quando inizializzi la tua GUI nel codice:

BetterCheckBox myCheckBox;

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

Se desideri impostare il controllo dal codice senza attivare l'ascoltatore, chiama myCheckBox.silentlySetChecked(someBoolean)invece di setChecked.


15
Per a Switch, la risposta 1 funziona sia in caso di tocco che di diapositiva, mentre la risposta 2 funzionerà solo in caso di tocco. Come preferenza personale, ho fatto in modo che la mia classe estendesse CheckBox/ Switchpiuttosto che mantenere un riferimento a uno. Sembra più pulito in questo modo (si noti che è necessario specificare il nome completo del pacchetto nell'XML se lo si fa).
howettl

1
Grazie per questo antropomo, non so perché non ci ho pensato prima, ma mi hai risparmiato del tempo prezioso;). Saluti !
CyberDandy

Non ne sono sicuro, ma se estendi SwitchCompat (utilizzando appcompat v7) per ottenere l'interruttore di progettazione del materiale, potresti terminare le nuove funzionalità di riprogettazione e colorazione.
DNax

4
Entrambe le soluzioni hanno gravi difetti. Prima soluzione: quando l'utente trascina l'interruttore, l'ascoltatore non viene licenziato. Seconda soluzione: poiché setOnCheckedChangeListener esegue quel controllo nullo, imposta effettivamente il vecchio listener quando è già impostato un nuovo.
Paul Woitaschek

Prima soluzione: problema noto e semi-accettabile se si desidera una soluzione concisa e non si intende utilizzare quel widget. Seconda soluzione: modificato il controllo null per affrontare quel tipo di caso limite improbabile. Ora assegniamo listenera myListenerogni volta che listenernon è nullo. In quasi tutti i casi questo non fa nulla, ma se creiamo un nuovo ascoltatore per qualche motivo, non si rompe.
anthropomo

38

Forse puoi controllare isShown ()? Se VERO, allora è l'utente. Per me va bene.

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

1
Funziona (non significa richiamata inaspettata) anche se chiami "setChecked (isChecked)" in onStart () o onResume (). Quindi potrebbe essere considerata una soluzione perfetta.
Alan Zhiliang Feng

1
Sembra non una soluzione generale. Cosa succede se il pulsante viene mostrato al momento ma il suo valore è cambiato dal codice?
Pavel

1
Non capisco, come fa 'isShown ()' a distinguere tra azioni dell'utente e modifiche programmatiche? come può dire che è un'azione dell'utente se 'isShown ()' è vero?
Muhammed Refaat

1
Questa soluzione funziona solo in casi molto specifici e si basa su un processo di creazione e layout di attività non documentato e non garantito. Non vi è alcuna garanzia che ciò non si interrompa in futuro e non funziona se una vista è già stata renderizzata.
Manuel

26

All'interno del onCheckedChanged()basta controllare se l'utente ha effettivamente checked/uncheckedil pulsante di opzione e quindi fare le cose di conseguenza come segue:

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

Buona soluzione, non è necessario personalizzare la vista.
Aju

Ti amo. : DDD
Vlad

12
questo non funziona quando l'utente sta attivando l'interruttore scorrendo / scorrendo
mohitum

1
Utilizzando questo approccio ovunque, ma ho trovato un caso, non funziona in quanto isPressedrestituisce falso, il dispositivo Nokia con TalkBack attivo.
Mikhail

SwitchMaterial funziona senza problemi. Buona decisione, grazie!
R00We

25

Puoi rimuovere l'ascoltatore prima di modificarlo in modo programmatico e aggiungerlo di nuovo, come risposto nel seguente post SO:

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

theCheck.setOnCheckedChangeListener(null);
theCheck.setChecked(false);
theCheck.setOnCheckedChangeListener(toggleButtonChangeListener);

4

Prova ad estendere CheckBox. Qualcosa del genere (esempio non completo):

public MyCheckBox extends CheckBox {

   private Boolean isCheckedProgramatically = false;

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

   public Boolean isNotSetByUser() {
      return isCheckedProgramatically;
   }

}

3

C'è un'altra semplice soluzione che funziona abbastanza bene. L'esempio è per 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;
    }
}

2

Domanda interessante. Per quanto ne so, una volta che sei nell'ascoltatore, non puoi rilevare quale azione ha attivato l'ascoltatore, il contesto non è sufficiente. A meno che non utilizzi un valore booleano esterno come indicatore.

Quando si seleziona la casella "a livello di programmazione", impostare un valore booleano prima per indicare che è stato eseguito a livello di programmazione. Qualcosa di simile a:

private boolean boxWasCheckedProgrammatically = false;

....

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

E nel tuo ascoltatore, non dimenticare di reimpostare lo stato della casella di controllo:

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

2

Prova NinjaSwitch:

Basta chiamare setChecked(boolean, true)per modificare lo stato controllato dell'interruttore senza rilevarlo!

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

2

Se OnClickListenerè già impostato e non deve essere sovrascritto, utilizzare !buttonView.isPressed()come isNotSetByUser().

Altrimenti la variante migliore è usare al OnClickListenerposto di OnCheckedChangeListener.


buttonView.isPressed () è una bella soluzione. C'è un problema quando utilizziamo OnClickListener, quando l'utente fa scorrere l'interruttore, non riceveremo la richiamata.
Aju

2

La risposta accettata potrebbe essere semplificata un po 'per non mantenere un riferimento alla casella di controllo originale. In questo modo possiamo usare SilentSwitchCompat(o SilentCheckboxCompatse preferisci) direttamente nell'XML. L'ho anche fatto in modo che tu possa impostare OnCheckedChangeListenersu nullse lo desideri.

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

Puoi quindi usarlo direttamente nel tuo XML in questo modo (Nota: avrai bisogno dell'intero nome del pacchetto):

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

Quindi puoi selezionare la casella in silenzio chiamando:

SilentCheckBox mySilentCheckBox = (SilentCheckBox) findViewById(R.id.my_check_box)
mySilentCheckBox.silentlySetChecked(someBoolean)

1

Ecco la mia implementazione

Codice Java per switch personalizzato:

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

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

Per modificare lo stato dell'interruttore senza attivare il listener, utilizzare:

swSelection.setCheckedSilently(contact.isSelected)

Puoi monitorare il cambiamento di stato normalmente:

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

A Kotlin:

 swSelection.setOnCheckedChangeListener{buttonView, isChecked -> run {
            contact.isSelected = isChecked
        }}

1

La mia variante con le funzioni di estensione di Kotlin:

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

... sfortunatamente dobbiamo trasmettere ogni volta CheckedChangeListener perché la classe CheckBox non ha getter per il campo mOnCheckedChangeListener ((

Uso:

checkbox.setCheckedSilently(true, myCheckboxListener)

0

Crea una variabile

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;          

}

Nell'applicazione quando lo si modifica al posto dell'utente, chiama in notSetByUser(true)modo che non sia impostato dall'utente, altrimenti chiama notSetByUser(false)cioè è impostato dal programma.

Infine, nel tuo listener di eventi, dopo aver chiamato isNotSetByUser (), assicurati di riportarlo di nuovo alla normalità.

Chiama questo metodo ogni volta che gestisci quell'azione tramite l'utente o in modo programmatico. Chiama notSetByUser () con il valore appropriato.


0

Se il tag della vista non viene utilizzato, puoi utilizzarlo invece di estendere la casella di controllo:

        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
                    }

ogni volta che chiami qualcosa che seleziona / deseleziona la casella di controllo, chiama anche questo prima di selezionare / deselezionare:

checkbox.setTag(true);

0

Ho creato l'estensione con RxJava PublishSubject, semplice. Reagisce solo agli eventi "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
}
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.