Jak mogę rozróżnić, czy przełącznik, wartość pola wyboru jest zmieniana przez użytkownika, czy programowo (w tym przez przechowywanie)?

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

Jak wdrożyć metodę isNotSetByUser()?

Solvek
źródło
Nie jestem pewien, ale myślę, że jeśli użytkownik go przełączył, wtedy również otrzymasz wywołanie zwrotne onClick, jeśli ustawisz ten odbiornik. Więc może możesz ustawić flagę boolowską w onClick w ten sposób, że możesz to sprawdzić w onCheckChanged, aby sprawdzić, czy użytkownik zainicjował zmianę.
FoamyGuy
1
powiązana wartość pola wyboru Zmień bez wyzwalania onCheckChanged , zaproponowana przez krishan
bummi
Mam prostsze i bardziej przejrzyste rozwiązanie: patrz stackoverflow.com/a/41574200/3256989
ultraon

Odpowiedzi:

157

Odpowiedź 2:

Bardzo prosta odpowiedź:

Użyj na OnClickListener zamiast 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);
        }

    });

Teraz odbierasz tylko zdarzenia związane z kliknięciami i nie musisz się martwić o zmiany w programie.


Odpowiedź 1:

Utworzyłem klasę opakowującą (patrz Wzorzec dekoratora), która rozwiązuje ten problem w sposób hermetyzowany:

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 można nadal zadeklarować tak samo w XML, ale użyj tego podczas inicjowania GUI w kodzie:

BetterCheckBox myCheckBox;

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

Jeśli chcesz ustawić sprawdzony z kodu bez wyzwalania nasłuchiwania, wywołaj myCheckBox.silentlySetChecked(someBoolean)zamiast setChecked.

anthropomo
źródło
15
W przypadku a Switchodpowiedź 1 działa zarówno w przypadku dotknięcia, jak i przesuwania, podczas gdy odpowiedź 2 działa tylko w przypadku dotknięcia. Zgodnie z osobistą preferencją, rozszerzyłem moją klasę CheckBox/ Switchzamiast utrzymywać odniesienie do jednej. Dzięki temu wydaje się czystszy (pamiętaj, że musisz podać pełną nazwę pakietu w kodzie XML, jeśli to zrobisz).
howettl
1
Dzięki za tego antropomo, nie wiem, dlaczego wcześniej o tym nie pomyślałem, ale zaoszczędziłeś mi cennego czasu;). Twoje zdrowie !
CyberDandy,
Nie jestem tego pewien, ale jeśli rozszerzysz SwitchCompat (używając appcompat v7), aby uzyskać przełącznik Material Design, możesz zakończyć nowe możliwości przeprojektowania i przyciemniania.
DNax
4
Oba rozwiązania mają poważne wady. Pierwsze rozwiązanie: kiedy użytkownik przeciąga przełącznik, słuchacz nie zostaje zwolniony. Drugie rozwiązanie: Ponieważ setOnCheckedChangeListener robi to sprawdzenie wartości null, faktycznie ustawia stary detektor, gdy istnieje już nowy zestaw.
Paul Woitaschek
Pierwsze rozwiązanie: znany i częściowo akceptowalny problem, jeśli chcesz uzyskać zwięzłe rozwiązanie i nie zamierzasz używać tego widżetu. Drugie rozwiązanie: zmieniono sprawdzanie wartości zerowej, aby rozwiązać ten rodzaj mało prawdopodobnego przypadku granicznego. Teraz przypisujemy listenerdo myListenerwhen i listenernie jest null. W prawie wszystkich przypadkach to nic nie daje, ale jeśli z jakiegoś powodu stworzymy nowego słuchacza, to nie jest zepsute.
anthropomo
38

Może możesz sprawdzić isShown ()? Jeśli PRAWDA - niż jest to użytkownik. Pracuje dla mnie.

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
źródło
1
Działa (co oznacza brak nieoczekiwanego wywołania zwrotnego), nawet jeśli wywołasz "setChecked (isChecked)" w onStart () lub onResume (). Można więc uznać to za idealne rozwiązanie.
Alan Zhiliang Feng
1
Wydaje się, że nie jest to ogólne rozwiązanie. Co się stanie, jeśli przycisk zostanie wyświetlony w danym momencie, ale jego wartość zostanie zmieniona z kodu?
Pavel
1
Nie rozumiem, jak „isShown ()” odróżnia akcje użytkownika od zmian programistycznych? na przykład jak może powiedzieć, że jest to akcja użytkownika, jeśli „isShown ()” jest prawdą?
Muhammed Refaat
1
To rozwiązanie działa tylko w bardzo specyficznych przypadkach i polega na nieudokumentowanym, niegwarantowanym procesie tworzenia i planowania działań. Nie ma gwarancji, że to się nie zepsuje w przyszłości i nie zadziała, jeśli widok jest już renderowany.
Manuel
26

Wewnątrz po onCheckedChanged()prostu sprawdź, czy użytkownik faktycznie ma checked/uncheckedprzycisk opcji, a następnie wykonaj czynności w następujący sposób:

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
źródło
Dobre rozwiązanie, nie ma potrzeby dostosowywania widoku.
Aju,
Kocham Cię. : DDD
Vlad
12
to nie działa, gdy użytkownik przełącza przełącznik, przesuwając / przesuwając
mohitum
1
Korzystając z tego podejścia wszędzie, ale okazało się, że nie działa, ponieważ isPressedzwraca fałsz, urządzenie Nokia z włączoną funkcją TalkBack.
Michaił
SwitchMaterial działa bez problemu. Dobra decyzja, dzięki!
R00Śr
25

Możesz usunąć odbiornik przed zmianą programistyczną i dodać go ponownie, zgodnie z odpowiedzią w następującym poście SO:

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

theCheck.setOnCheckedChangeListener(null);
theCheck.setChecked(false);
theCheck.setOnCheckedChangeListener(toggleButtonChangeListener);
Eli Kohen
źródło
4

Spróbuj rozszerzyć CheckBox. Coś takiego (niepełny przykład):

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
źródło
3

Jest jeszcze jedno proste rozwiązanie, które działa całkiem nieźle. Przykład dotyczy Switcha.

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;
    }
}
Niewidzialny
źródło
2

Interesujące pytanie. O ile mi wiadomo, kiedy już jesteś w słuchaczu, nie możesz wykryć, jaka akcja wywołała słuchacza, kontekst nie wystarczy. Chyba że używasz zewnętrznej wartości logicznej jako wskaźnika.

Gdy zaznaczysz pole „programowo”, ustaw wcześniej wartość logiczną, aby wskazać, że zostało to zrobione programowo. Coś jak:

private boolean boxWasCheckedProgrammatically = false;

....

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

Nie zapomnij zresetować stanu pola wyboru w swoim odbiorniku:

@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
źródło
2

Spróbuj NinjaSwitch:

Wystarczy zadzwonić, setChecked(boolean, true)aby zmienić stan zaznaczenia przełącznika bez wykrycia!

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
źródło
2

Jeśli OnClickListenerjest już ustawiona i nie należy jej nadpisywać, użyj !buttonView.isPressed()jako isNotSetByUser().

W przeciwnym razie najlepszym wariantem jest użycie OnClickListenerzamiast OnCheckedChangeListener.

Pavel
źródło
buttonView.isPressed () to fajne rozwiązanie. Jest problem, gdy używamy OnClickListener, gdy użytkownik przesunie przełącznik, nie otrzymamy callback.
Aju,
2

Zaakceptowaną odpowiedź można nieco uprościć, aby nie zachowywać odniesienia do oryginalnego pola wyboru. To sprawia, że ​​możemy używać SilentSwitchCompat(lub SilentCheckboxCompatjeśli wolisz) bezpośrednio w XML. Ja również go, dzięki czemu można ustawić OnCheckedChangeListener, aby nulljeśli chcecie, aby to zrobić.

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

Możesz następnie użyć tego bezpośrednio w swoim XML, w ten sposób (Uwaga: będziesz potrzebować całej nazwy pakietu):

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

Następnie możesz po cichu zaznaczyć to pole, dzwoniąc pod numer:

SilentCheckBox mySilentCheckBox = (SilentCheckBox) findViewById(R.id.my_check_box)
mySilentCheckBox.silentlySetChecked(someBoolean)
Drobnica
źródło
1

Oto moja realizacja

Kod Java dla przełącznika niestandardowego:

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

Równoważny kod Kotlin:

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

Aby zmienić stan przełącznika bez wyzwalania nasłuchiwania, użyj:

swSelection.setCheckedSilently(contact.isSelected)

Możesz normalnie monitorować zmianę stanu poprzez:

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

W Kotlinie:

 swSelection.setOnCheckedChangeListener{buttonView, isChecked -> run {
            contact.isSelected = isChecked
        }}
thilina Kj
źródło
1

Mój wariant z funkcjami rozszerzającymi Kotlin:

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

... niestety za każdym razem musimy przekazywać onCheckedChangeListener, ponieważ klasa CheckBox nie ma funkcji pobierającej dla pola mOnCheckedChangeListener ((

Stosowanie:

checkbox.setCheckedSilently(true, myCheckboxListener)
Fedir Tsapana
źródło
0

Utwórz zmienną

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;          

}

W aplikacji, gdy zmienisz to zamiast użytkownika, call, notSetByUser(true)aby nie było ustawione przez użytkownika, w przeciwnym razie callnotSetByUser(false) tj. Jest ustawiony przez program.

Na koniec, po wywołaniu metody isNotSetByUser () w detektorze zdarzeń upewnij się, że ponownie zmienisz ją z powrotem na normalną.

Wywołaj tę metodę za każdym razem, gdy wykonujesz tę akcję przez użytkownika lub programowo. Wywołaj notSetByUser () z odpowiednią wartością.

Tvd
źródło
0

Jeśli tag widoku nie jest używany, możesz go użyć zamiast rozszerzać pole wyboru:

        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
                    }

za każdym razem, gdy wywołujesz coś, co zaznacza / odznacza pole wyboru, wywołaj to również przed zaznaczeniem / odznaczeniem:

checkbox.setTag(true);
programista Androida
źródło
0

Stworzyłem rozszerzenie z prostym RxJava PublishSubject. Reaguje tylko na zdarzenia „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
źródło