Zmień wartość pola wyboru bez wyzwalania onCheckChanged


147

Mam setOnCheckedChangeListenerrealizowane dla mojegocheckbox

Czy istnieje sposób, w jaki mogę zadzwonić

checkbox.setChecked(false);

bez wyzwalania onCheckedChanged


Dlaczego po prostu nie użyć prostej flagi prawda / fałsz? To najprostszy sposób rozwiązania tego problemu i potrzeba tylko trzech wierszy dodatkowego kodu. Zobacz moją odpowiedź poniżej.
Ruchir Baronia

Odpowiedzi:


280

Nie, nie możesz tego zrobić. onCheckedChangedMetoda jest wywoływana bezpośrednio setChecked. Co możesz zrobić, to:

mCheck.setOnCheckedChangeListener (null);
mCheck.setChecked (false);
mCheck.setOnCheckedChangeListener (mListener);

Zobacz źródło CheckBox i implementację setChecked:

public void  setChecked(boolean checked) {
    if (mChecked != checked) {
        mChecked = checked;
        refreshDrawableState();

        // Avoid infinite recursions if setChecked() is called from a listener
        if (mBroadcasting) {
            return;
        }

        mBroadcasting = true;
        if (mOnCheckedChangeListener != null) {
            mOnCheckedChangeListener.onCheckedChanged(this, mChecked);
        }

        if (mOnCheckedChangeWidgetListener != null) {
            mOnCheckedChangeWidgetListener.onCheckedChanged(this, mChecked);
        }

        mBroadcasting = false;            
    }
}

6
Jak proponujesz dostać mListener? Checkboxnie ma gettera dla swojegoOnCheckChangeListener
tir38

20
Cóż, nie musisz głosować „w dół” tylko dlatego, że nie rozumiesz rozwiązania. mListenerjest implementacją OnCheckChangedListenerinterfejsu, który został stworzony przez programistę. Z mojej odpowiedzi wynika, że ​​programista zachował odniesienie do własnej implementacji - mListener.
Shade

Czy zmiana odbiornika byłaby nieefektywna, jeśli chcesz używać metody setChecked () w sposób powtarzalny?
Ren

4
@Ren, zmiana detektora obejmuje tylko ustawienie właściwości w CheckBoxobiekcie. Nie powiedziałbym, że jest to nieefektywne.
Shade

Dokument brzmi „Wywoływane, gdy zaznaczony przycisk opcji się zmienił. Po wyczyszczeniu zaznaczenia, CheckId wynosi -1”. Jest to naprawdę mylące, powinno też zostać przekazane isChecked.
AA_PV,

69

Dodaj ten kod do OnCheckedChangeListener:

if(!compoundButton.isPressed()) {
            return;
}

Pomoże nam to dowiedzieć się, czy stan pogody checkBox został zmieniony programowo lub w wyniku działania użytkownika.


11
To powinno być oznaczone jako rozwiązanie, działa jak urok!
Ashwin Balani

5
być świadomym! To przerywa tryb dostępności! isPressed () nie jest prawdą, jeśli jest wyzwalany przez podwójne dotknięcie z trybu asystenta głosowego.
A1m

21

Innym możliwym sposobem osiągnięcia tego jest użycie niestandardowego CheckBox, który pozwoli Ci wybrać, czy chcesz, aby słuchacz został wywołany, czy nie:

public class CheckBox extends AppCompatCheckBox {
    private OnCheckedChangeListener mListener;

    public CheckBox(final Context context) {
        super(context);
    }

    public CheckBox(final Context context, final AttributeSet attrs) {
        super(context, attrs);
    }

    public CheckBox(final Context context, final AttributeSet attrs, final int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public void setOnCheckedChangeListener(final OnCheckedChangeListener listener) {
        mListener = listener;
        super.setOnCheckedChangeListener(listener);
    }

    public void setChecked(final boolean checked, final boolean alsoNotify) {
        if (!alsoNotify) {
            super.setOnCheckedChangeListener(null);
            super.setChecked(checked);
            super.setOnCheckedChangeListener(mListener);
            return;
        }
        super.setChecked(checked);
    }

    public void toggle(boolean alsoNotify) {
        if (!alsoNotify) {
            super.setOnCheckedChangeListener(null);
            super.toggle();
            super.setOnCheckedChangeListener(mListener);
        }
        super.toggle();
    }
}

Wersja Kotlin, jeśli wolisz:

class CheckBox @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : AppCompatCheckBox(context, attrs, defStyleAttr) {
    private var listener: CompoundButton.OnCheckedChangeListener? = null

    override fun setOnCheckedChangeListener(listener: CompoundButton.OnCheckedChangeListener?) {
        this.listener = listener
        super.setOnCheckedChangeListener(listener)
    }

    fun setChecked(checked: Boolean, alsoNotify: Boolean) {
        if (!alsoNotify) {
            super.setOnCheckedChangeListener(null)
            super.setChecked(checked)
            super.setOnCheckedChangeListener(listener)
            return
        }
        super.setChecked(checked)
    }

    fun toggle(alsoNotify: Boolean) {
        if (!alsoNotify) {
            super.setOnCheckedChangeListener(null)
            super.toggle()
            super.setOnCheckedChangeListener(listener)
        }
        super.toggle()
    }
}

przykładowe użycie:

checkBox.setChecked(true,false);

11

używasz po prostu setonclickListener, będzie działać dobrze i jest to bardzo prosta metoda, dzięki :)


35
onClick () nie jest wywoływana, gdy użytkownik przesuwa przełącznik.
James,

2
Rzeczywiście, zrobienie tego pozostawia cię bez obsługi, gdy użytkownik przeciąga przełącznik.
Victor G,

w jaki sposób uzyskasz wartość isChecked, czyli prawda czy fałsz?
Abhi

6

Używanie rozszerzeń Kotlin z odpowiedzią @Shade:

fun CompoundButton.setCustomChecked(value: Boolean,listener: CompoundButton.OnCheckedChangeListener) {
     setOnCheckedChangeListener(null)
     isChecked = value
     setOnCheckedChangeListener(listener)
}

6

Dla każdego, kto się na to natknie, prostszym sposobem na zrobienie tego jest użycie tagu w polu wyboru, a następnie sprawdzenie tego tagu w jego odbiorniku (kod jest w Kotlinie):

checkBox.tag = false
checkBox.setOnCheckedChangeListener{ buttonView, isChecked -> 
    if(checkBox.tag != true) {
        //Do some stuff
    } else {
        checkBox.tag = false
    }

Następnie podczas uzyskiwania dostępu po prostu ustaw tag na true, zanim ustawisz isChecked na true, gdy chcesz zignorować zmianę wartości:

checkBox.tag = true
checkBox.isChecked = true

Możesz również zamapować tag na klucz, używając alternatywnej metody setTag, która wymaga klucza, jeśli martwisz się o zrozumiałość. Ale jeśli wszystko jest zawarte w jednej klasie, kilka ciągów komentarzy wystarczy, aby wyjaśnić, co się dzieje.


4

Możesz użyć tej klasy SafeCheckBox jako pola wyboru:

public class SafeCheckBox extends AppCompatCheckBox implements CompoundButton.OnCheckedChangeListener {

    private OnSafeCheckedListener onSafeCheckedListener;

    private int mIgnoreListener = CALL_LISTENER;

    public static final int IGNORE = 0;
    public static final int CALL_LISTENER = 1;

    @Retention(RetentionPolicy.SOURCE)
    @IntDef({IGNORE, CALL_LISTENER})
    public @interface ListenerMode {
    }

    public SafeCheckBox(Context context) {
        super(context);
        init(context);
    }

    public SafeCheckBox(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public SafeCheckBox(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    /**
     * @param checkState     change state of the checkbox to 
     * @param mIgnoreListener true to ignore the listener else listener will be  notified
     */
    public void setSafeCheck(boolean checkState, @ListenerMode int mIgnoreListener) {
        if (isChecked() == checkState) return; //already in the same state no need to fire listener. 

        if (onSafeCheckedListener != null) { // this to avoid a bug if the user listens for the event after using this method and in that case he will miss first check
            this.mIgnoreListener = mIgnoreListener;
        } else {
            this.mIgnoreListener = CALL_LISTENER;
        }
        setChecked(checkState);
    }

    private void init(Context context) {
        setOnCheckedChangeListener(this);
    }


    public OnSafeCheckedListener getOnSafeCheckedListener() {
        return onSafeCheckedListener;
    }

    public void setOnSafeCheckedListener(OnSafeCheckedListener onSafeCheckedListener) {
        this.onSafeCheckedListener = onSafeCheckedListener;
    }

    @Override
    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {

        if (onSafeCheckedListener != null)
            onSafeCheckedListener.onAlwaysCalledListener(buttonView, isChecked);// this has to be called before onCheckedChange
        if (onSafeCheckedListener != null && (mIgnoreListener == CALL_LISTENER)) {
            onSafeCheckedListener.onCheckedChanged(buttonView, isChecked);
        }
        mIgnoreListener = CALL_LISTENER;
    }

    /**
     * Listener that will be called when you want it to be called.
     * On checked change listeners are called even when the setElementChecked is called from code. :(
     */
    public interface OnSafeCheckedListener extends OnCheckedChangeListener {
        void onAlwaysCalledListener(CompoundButton buttonView, boolean isChecked);
    }
}
  • Wtedy możesz zadzwonić: -

    setSafeCheck(true,ListenerMode.IGNORE);// OnCheckedChange listener will not be notified


3

Ustaw null na changeListener przed zaznaczeniem przycisku radiowego. Po zaznaczeniu przycisku opcji możesz ponownie ustawić odbiornik.

radioGroup.setOnCheckedChangeListener(null);
radioGroup.check(R.id.radioButton);
radioGroup.setOnCheckedChangeListener(new 

RadioGroup.OnCheckedChangeListener() {
   @Override
   public void onCheckedChanged(RadioGroup radioGroup, @IdRes int i) {

   }
});

1
Wykonuje swoją pracę i jest znacznie szybszy i prostszy niż inne rozwiązania tutaj.
PayToPwn

3

Moja interpretacja, która moim zdaniem jest najłatwiejsza
Może być pomocna)

public class ProgrammableSwitchCompat extends SwitchCompat {

    public boolean isCheckedProgrammatically = false;

    public ProgrammableSwitchCompat(final Context context) {
        super(context);
    }

    public ProgrammableSwitchCompat(final Context context, final AttributeSet attrs) {
        super(context, attrs);
    }

    public ProgrammableSwitchCompat(final Context context, final AttributeSet attrs, final int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public void setChecked(boolean checked) {
        isCheckedProgrammatically = false;
        super.setChecked(checked);
    }

    public void setCheckedProgrammatically(boolean checked) {
        isCheckedProgrammatically = true;
        super.setChecked(checked);
    }
}

Użyj tego

@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean on) {
    if (((ProgrammableSwitchCompat) compoundButton).isCheckedProgrammatically) {
        return;
    }
    //...
    ((ProgrammableSwitchCompat) compoundButton).setCheckedProgrammatically(true);
    //...
    ((ProgrammableSwitchCompat) compoundButton).setCheckedProgrammatically(false);
    //...
}

użycie wywoła setChecked(boolean)funkcję,
która jest wszystkim

KOTLIN

class MyCheckBox @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = R.attr.switchStyle
) : AppCompatCheckBox(context, attrs, defStyleAttr) {

    var programmatically = false

    override fun setChecked(checked: Boolean) {
        programmatically = false
        super.setChecked(checked)
    }

    fun setCheckedProgrammatically(checked: Boolean) {
        programmatically = true
        super.setChecked(checked)
    }
}

2

Oto proste rozwiązanie, którego użyłem:
Zdefiniuj odbiornik niestandardowy:

class CompoundButtonListener implements CompoundButton.OnCheckedChangeListener {

    boolean enabled = false;

    @Override
    public void onCheckedChanged(CompoundButton compoundButton, boolean checked) {

    }

    void enable() {
        enabled = true;
    }

    void disable() {
        enabled = false;
    }

    boolean isEnabled() {
        return enabled;
    }
}

Inicjalizacja:

CompoundButtonListener checkBoxListener = new CompoundButtonListener() {
    @Override
    public void onCheckedChanged(CompoundButton compoundButton, boolean checked) {
        if (isEnabled()) {
            // Your code goes here
        }
    }
};
myCheckBox.setOnCheckedChangeListener(checkBoxListener);

Stosowanie:

checkBoxListener.disable();

// Some logic based on which you will modify CheckBox state
// Example: myCheckBox.setChecked(true)

checkBoxListener.enable();

2

Co powiesz na to. Spróbuj użyć tagu w widoku

mCheck.setTag("ignore");
mCheck.setChecked(true);
mCheck.setTag(null);

i

switch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
        @Override
        public void onCheckedChanged(CompoundButton compoundButton, boolean selected) {

            //If switch has a tag, ignore below
            if(compoundButton.getTag() != null)
                return; 

            if (selected) {
                // do something
            } else {
                // do something else
            }

        }
    });

2

Użyłem ReentrantLocki blokuję go za każdym razem, gdy ustawiam isChecked:

// lock when isChecked is being set programmatically
val isBeingProgrammaticallySet = ReentrantLock()

// set isChecked programmatically
isBeingProgrammaticallySet.withLock()
{
    checkbox.isChecked = true
}

// do something only when preference is modified by user
checkbox.setOnCheckedChangeListener()
{
    _,isChecked ->
    if (isBeingProgrammaticallySet.isHeldByCurrentThread.not())
    {
        // do it
    }
}

1

Wszystkie powyższe odpowiedzi były dla mnie zbyt skomplikowane. Dlaczego po prostu nie stworzyć własnej flagi z prostą wartością logiczną?

Po prostu użyj prostego systemu flag z wartością logiczną. Utwórz boolean noListener. Ilekroć chcesz włączyć / wyłączyć przełącznik bez uruchamiania żadnego kodu (w tym przykładzie reprezentowany jako runListenerCode(), po prostu ustaw noListener=trueprzed wywołaniemswitch.setChecked(false/true)

switch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
           @Override
           public void onCheckedChanged(CompoundButton compoundButton, boolean selected) {
               if (!noListener) { //If we want to run our code like usual
                   runListenerCode();
               } else { //If we simply want the switch to turn off
                   noListener = false;
               }
           });

Bardzo proste rozwiązanie przy użyciu prostych flag. Na koniec ustawiliśmy się noListener=falsejeszcze raz, aby nasz kod dalej działał. Mam nadzieję że to pomoże!


1

Wypróbuj ten powinien działać dla Ciebie! Możesz tego użyć również z Firebase!

Aby uzyskać dane z Firebase! Użyj tego!

databaseReference.child(user.getPhoneNumber()).child("Reqs").addValueEventListener(new ValueEventListener() {

        @Override
        public void onDataChange(DataSnapshot dataSnapshot) {
            SharedPreferences prefs = mContext.getSharedPreferences("uinfo", MODE_PRIVATE);
            String pno = prefs.getString("username", "No name defined");

            if(dataSnapshot.child(pno).getValue(String.class).equals("acc")){
                holder.acc.setChecked(true);
            }else{
                holder.acc.setChecked(false);
            }
        }

        @Override
        public void onCancelled(DatabaseError databaseError) {
            // Getting Post failed, log a message
            Log.w("dfs", "loadPost:onCancelled", databaseError.toException());
            // ...
        }
    });

Potem, gdy użytkownik coś zrobi!

holder.acc.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                if(isChecked) {
                    if(buttonView.isPressed()) {
                        //your code
                    }
                }
                else {
                    if(buttonView.isPressed()) {
                       //your code
                    }
                }
            }
        });

1

Tak naprawdę nie chciałem przekazywać słuchaczowi za każdym razem, gdy ustawiliśmy zaznaczoną zmianę, ani za pomocą enabled jako sposobu na określenie, czy powinniśmy ustawić wartość (co się dzieje w przypadku, gdy mamy wyłączony przełącznik już podczas ustawiania wartości ?)

Zamiast tego używam tagów z identyfikatorem i kilku metod rozszerzających, które możesz wywołać:

fun CompoundButton.setOnCheckedWithoutCallingChangeListener(
    listener: (view: CompoundButton, checked: Boolean) -> Unit
) {
    setOnCheckedChangeListener { view, checked ->
        if (view.getTag(R.id.compound_button_checked_changed_listener_disabled) != true) {
            listener(view, checked)
        }
    }
    this.setTag(R.id.compound_button_enabled_checked_change_supported, true)
}

fun CompoundButton.setCheckedWithoutCallingListener(checked: Boolean) {
    check(this.getTag(R.id.compound_button_enabled_checked_change_supported) == true) {
        "Must set listener using `setOnCheckedWithoutCallingChangeListener` to call this method" 
    }

    setTag(R.id.compound_button_checked_changed_listener_disabled, true)
    isChecked = checked
    setTag(R.id.compound_button_checked_changed_listener_disabled, false)
}

Teraz możesz dzwonić, setCheckedWithoutCallingListener(bool)a to wymusi prawidłowe użycie nasłuchiwania.

Możesz również zadzwonić, setChecked(bool)aby zwolnić słuchacza, jeśli nadal tego potrzebujesz


0

Myślę, że jedynym sposobem jest użycie refleksji. Coś takiego:

CheckBox cb = (CheckBox) findViewById(R.id.checkBox1);
try {
    Field field = CompoundButton.class.getDeclaredField("mChecked");
    field.setAccessible(true);
    field.set(cb, cb.isChecked());
    cb.refreshDrawableState();
    cb.invalidate();
} catch (Exception e) {
    e.printStackTrace();
}

Może działać do momentu, gdy deweloperzy zmienią nazwę pola lub np. Podciągną metodę "isChecked" w hierarchii ... lub dokonają kolejnej refaktoryzacji ... Przynajmniej dodaj coś takiegoif(Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN){ /* do reflection */}
aeracode.

Zachęcanie do łamania API i kopania we wnętrzach jest złym pomysłem. Każda zmiana implementacji spowoduje awarię aplikacji.
mspanc

0

Moje rozwiązanie napisane w java na podstawie odpowiedzi @Chris:

chkParent.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                if(buttonView.getTag() != null){
                    buttonView.setTag(null);
                    return;
                }
                if(isChecked){
                    chkChild.setTag(true);
                    chkChild.setChecked(false);
                }
                else{
                    chkParent.setChecked(true);
                }
            }
});

chkChild.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                if(buttonView.getTag() != null){
                    buttonView.setTag(null);
                    return;
                }
                if(isChecked){
                    chkParent.setTag(true);
                    chkParent.setChecked(false);
                }
                else{
                    chkChild.setChecked(true);
                }
            }
});

2 pola wyboru i zawsze jedno będzie zaznaczone (chociaż jedno musi być zaznaczone na początku). Ustawienie znacznika na prawdziwe bloki onCheckedChanged odbiornika.


0
public void setCheckedChangeListenerSwitch() {

    switch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
        @Override
        public void onCheckedChanged(CompoundButton compoundButton, boolean selected) {

            if (selected) {
                // do something
            } else {
                // do something else
            }

        }
    });

}

// Update state & reset listener (so onCheckedNot called)
public void updateSwitchState(boolean state){

    switch.setOnCheckedChangeListener(null);
    switch.setChecked(state);
    setCheckedChangeListenerSwitch();

}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.