Wie kann ich unterscheiden, ob Switch, Checkbox Value vom Benutzer oder programmgesteuert (einschließlich durch Aufbewahrung) geändert wird?

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

Wie implementiere ich eine Methode isNotSetByUser()?

Solvek
quelle
Ich bin nicht sicher, aber ich denke, wenn der Benutzer es umschaltet, erhalten Sie auch einen onClick-Rückruf, wenn Sie diesen Listener einstellen. Vielleicht können Sie in onClick nur ein boolesches Flag setzen, damit Sie es in onCheckChanged einchecken können, um festzustellen, ob der Benutzer die Änderung initiiert hat.
FoamyGuy
Ich habe eine einfachere und klarere
Ultraon

Antworten:

157

Antwort 2:

Eine sehr einfache Antwort:

Verwenden Sie OnClickListener anstelle von 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);
        }

    });

Jetzt erfassen Sie nur noch Klickereignisse und müssen sich nicht mehr um programmatische Änderungen kümmern.


Antwort 1:

Ich habe eine Wrapper-Klasse erstellt (siehe Decorator-Muster), die dieses Problem gekapselt behandelt:

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 kann in XML weiterhin als gleich deklariert werden. Verwenden Sie dies jedoch, wenn Sie Ihre GUI im Code initialisieren:

BetterCheckBox myCheckBox;

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

Wenn Sie Checked from Code festlegen möchten, ohne den Listener auszulösen, rufen Sie myCheckBox.silentlySetChecked(someBoolean)stattdessen statt setChecked.

Anthropomo
quelle
15
Bei a Switchfunktioniert Antwort 1 sowohl in Tap- als auch in Slide-Fällen, während Antwort 2 nur in Tap-Fällen funktioniert. Als persönliche Präferenz habe ich meine Klasse erweitert CheckBox/ Switchanstatt einen Verweis auf eine zu halten. Fühlt sich auf diese Weise sauberer an (beachten Sie, dass Sie den vollständigen Paketnamen im XML angeben müssen, wenn Sie dies tun).
Howettl
1
Danke für diesen Anthropomo, ich weiß nicht, warum ich nicht früher darüber nachgedacht habe, aber du hast mir wertvolle Zeit gespart;). Prost !
CyberDandy
Ich bin mir nicht sicher, aber wenn Sie SwitchCompat (mit Appcompat v7) erweitern, um den Materialdesign-Schalter zu erhalten, können Sie die neuen Redesign- und Tönungsfunktionen beenden.
DNax
4
Beide Lösungen weisen schwerwiegende Mängel auf. Erste Lösung: Wenn der Benutzer den Schalter zieht, wird der Listener nicht ausgelöst. Zweite Lösung: Da setOnCheckedChangeListener diese Nullprüfung durchführt, wird der alte Listener tatsächlich gesetzt, wenn bereits ein neuer Listener vorhanden ist.
Paul Woitaschek
Erste Lösung: Bekanntes und halbwegs akzeptables Problem, wenn Sie eine präzise Lösung wünschen und nicht beabsichtigen, dieses Widget zu verwenden. Zweite Lösung: Die Nullprüfung wurde geändert, um diese Art von unwahrscheinlichem Randfall zu beheben. Jetzt weisen wir listenerzu, myListenerwann listenerimmer nicht null ist. In fast allen Fällen tut dies nichts, aber wenn wir aus irgendeinem Grund einen neuen Hörer erstellen, ist das nicht kaputt.
Anthropomo
38

Vielleicht können Sie isShown () überprüfen? Wenn WAHR - dann ist es Benutzer. Funktioniert bei mir.

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
quelle
1
Es funktioniert (dh kein unerwarteter Rückruf), selbst wenn Sie "setChecked (isChecked)" in onStart () oder onResume () aufrufen. Es könnte also als perfekte Lösung angesehen werden.
Alan Zhiliang Feng
1
Scheint keine allgemeine Lösung zu sein. Was ist, wenn die Schaltfläche im Moment angezeigt wird, ihr Wert jedoch gegenüber dem Code geändert wird?
Pavel
1
Ich verstehe nicht, wie 'isShown ()' zwischen Benutzeraktionen und programmatischen Änderungen unterscheidet? Wie kann man sagen, dass es sich um eine Benutzeraktion handelt, wenn 'isShown ()' wahr ist?
Muhammed Refaat
1
Diese Lösung funktioniert nur in ganz bestimmten Fällen und basiert auf undokumentierten, nicht garantierten Aktivitäten zum Erstellen und Layouten von Aktivitäten. Es gibt keine Garantie dafür, dass dies in Zukunft nicht mehr funktioniert, und es funktioniert nicht, wenn eine Ansicht bereits gerendert wurde.
Manuel
26

onCheckedChanged()Überprüfen Sie in der Datei, ob der Benutzer tatsächlich checked/uncheckeddas Optionsfeld hat, und führen Sie die entsprechenden Schritte aus:

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
quelle
Gute Lösung, die Ansicht muss nicht angepasst werden.
Aju
Ich liebe dich. : DDD
Vlad
12
Dies funktioniert nicht, wenn der Benutzer den Schalter durch Wischen / Schieben
umschaltet
1
Wenn Sie diesen Ansatz überall verwenden, aber einen Fall gefunden haben, der nicht funktioniert, wird ein isPressedfalsches Nokia-Gerät mit aktiviertem TalkBack zurückgegeben.
Mikhail
SwitchMaterial funktioniert problemlos. Gute Entscheidung, danke!
R00Wir
25

Sie können den Listener entfernen, bevor Sie ihn programmgesteuert ändern, und ihn erneut hinzufügen, wie im folgenden SO-Beitrag beantwortet:

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

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

Versuchen Sie, CheckBox zu erweitern. So etwas (nicht vollständiges Beispiel):

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

Es gibt eine andere einfache Lösung, die ziemlich gut funktioniert. Beispiel ist für 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;
    }
}
Eingeladen
quelle
2

Interessante Frage. Meines Wissens können Sie im Listener nicht erkennen, welche Aktion den Listener ausgelöst hat. Der Kontext reicht nicht aus. Es sei denn, Sie verwenden einen externen booleschen Wert als Indikator.

Wenn Sie das Kontrollkästchen "programmgesteuert" aktivieren, legen Sie zuvor einen booleschen Wert fest, um anzuzeigen, dass er programmgesteuert ausgeführt wurde. Etwas wie:

private boolean boxWasCheckedProgrammatically = false;

....

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

Vergessen Sie in Ihrem Listener nicht, den Status des Kontrollkästchens zurückzusetzen:

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

Versuchen Sie NinjaSwitch:

Rufen Sie einfach setChecked(boolean, true)an, um den geprüften Status des Schalters zu ändern, ohne ihn zu erkennen!

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

Wenn OnClickListenerbereits festgelegt und nicht überschrieben werden soll, verwenden Sie !buttonView.isPressed()als isNotSetByUser().

Ansonsten ist die beste Variante verwenden OnClickListenerstatt OnCheckedChangeListener.

Pavel
quelle
buttonView.isPressed () ist eine schöne Lösung. Bei Verwendung von OnClickListener tritt ein Problem auf. Wenn der Benutzer auf den Switch schiebt, wird kein Rückruf angezeigt.
Aju
2

Die akzeptierte Antwort könnte etwas vereinfacht werden, um keinen Verweis auf das ursprüngliche Kontrollkästchen beizubehalten. Dies macht es so, dass wir das SilentSwitchCompat(oder SilentCheckboxCompatwenn Sie es vorziehen) direkt im XML verwenden können. Ich habe es auch so gemacht, dass Sie das einstellen können OnCheckedChangeListener, nullwenn Sie dies wünschen.

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

Sie können dies dann direkt in Ihrem XML verwenden (Hinweis: Sie benötigen den gesamten Paketnamen):

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

Dann können Sie das Kontrollkästchen stillschweigend aktivieren, indem Sie Folgendes anrufen:

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

Hier ist meine Implementierung

Java-Code für benutzerdefinierten Switch:

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

Äquivalenter Kotlin-Code:

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

Um den Schaltzustand zu ändern, ohne den Listener auszulösen, verwenden Sie:

swSelection.setCheckedSilently(contact.isSelected)

Sie können die Statusänderung wie gewohnt überwachen, indem Sie:

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

In Kotlin:

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

Meine Variante mit Kotlin-Erweiterungsfunktionen:

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

... leider müssen wirCheckedChangeListener jedes Mal weitergeben, da die CheckBox-Klasse keinen Getter für das Feld mOnCheckedChangeListener hat (()

Verwendung:

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

Erstellen Sie eine 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;          

}

Wenn Sie es in der Anwendung anstelle des Benutzers ändern, rufen Sie es auf, notSetByUser(true)damit es nicht vom Benutzer festgelegt wird, andernfalls rufen notSetByUser(false)Sie es auf, dh es wird vom Programm festgelegt.

Stellen Sie in Ihrem Ereignis-Listener nach dem Aufruf von isNotSetByUser () sicher, dass Sie ihn wieder auf normal zurücksetzen.

Rufen Sie diese Methode auf, wenn Sie diese Aktion entweder über den Benutzer oder programmgesteuert ausführen. Rufen Sie notSetByUser () mit dem entsprechenden Wert auf.

Fernseher
quelle
0

Wenn das Tag der Ansicht nicht verwendet wird, können Sie es verwenden, anstatt das Kontrollkästchen zu erweitern:

        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
                    }

Rufen Sie dies jedes Mal auf, wenn Sie etwas aufrufen, das das Kontrollkästchen aktiviert / deaktiviert, bevor Sie das Kontrollkästchen aktivieren / deaktivieren:

checkbox.setTag(true);
Android-Entwickler
quelle
0

Ich habe eine PublishSubjecteinfache Erweiterung mit RxJava erstellt . Reagiert nur auf "OnClick" -Ereignisse.

/**
 * 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
quelle