Як я можу розрізнити, чи змінюється значення перемикача, прапорця користувачем або програмно (у тому числі шляхом утримання)?


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

Як реалізувати метод isNotSetByUser()?


Я не впевнений, але я думаю, якщо користувач переключив його, тоді ви отримаєте і зворотний дзвінок onClick, якщо встановите цього слухача. Тож можливо ви можете встановити булевий прапор onClick таким чином, ви можете перевірити його в onCheckChanged, щоб побачити, чи користувач ініціював зміни.
FoamyGuy


У мене є більш просте і ясне рішення: см stackoverflow.com/a/41574200/3256989
ultraon

Відповіді:


157

Відповідь 2:

Дуже проста відповідь:

Використовуйте OnClickListener замість 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);
        }

    });

Тепер ви лише підбираєте події кліків і не потрібно турбуватися про програмні зміни.


Відповідь 1:

Я створив клас обгортки (див. Шаблон декоратора), який вирішує цю проблему інкапсульованим способом:

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 все ще можна оголосити тим же самим у XML, але використовуйте це при ініціалізації вашого GUI у коді:

BetterCheckBox myCheckBox;

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

Якщо ви хочете встановити прапорець із коду, не викликаючи слухача, дзвоніть myCheckBox.silentlySetChecked(someBoolean)замість setChecked.


15
Для Switchвідповіді "відповідь 1" працює як у випадках торкання, так і в слайді, тоді як відповідь 2 працюватиме лише у випадку з краном. В якості особистого уподобання я зробив свій клас розширенням CheckBox/ Switchзамість того, щоб посилатися на один. Таким чином, ви відчуваєте себе більш чистим (зауважте, що ви повинні вказати повне ім'я пакета в XML, якщо це зробити).
howettl

1
Дякую за цей антропомо, я не знаю, чому я не думав про це раніше, але ви заощадили мені дорогоцінний час;). Ура!
CyberDandy

Я не впевнений у цьому, але якщо ви продовжите SwitchCompat (використовуючи appcompat v7), щоб отримати комутатор дизайну матеріалів, ви можете закінчити нову можливість переробки та тонування.
DNax

4
Обидва рішення мають серйозні недоліки. Перше рішення: Коли користувач перетягує комутатор, слухач не звільняється. Друге рішення: Оскільки setOnCheckedChangeListener робить цю нульову перевірку, він актуально встановлює старого слухача, коли вже є новий набір.
Пол Войташек

Перше рішення: відома та напівприйнятна проблема, якщо ви хочете стисло вирішити та не маєте наміру використовувати цей віджет. Друге рішення: змінив нульовий чек, щоб вирішити такий вид малоймовірного кращого випадку. Тепер ми відносимо listenerдо myListenerвсякий раз , коли listenerне дорівнює нулю. Майже у всіх випадках це нічого не робить, але якщо ми чомусь зробимо нового слухача, це не порушено.
антропомо

38

Можливо Ви можете перевірити isShown ()? Якщо ІСТИНА - то це користувач. Працює для мене.

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
Він працює (мається на увазі відсутність несподіваного зворотного виклику), навіть якщо ви викликаєте "setChecked (isChecked)" в onStart () або onResume (). Тож це можна вважати ідеальним рішенням.
Алан Чжилянг Фен

1
Здається, це не загальне рішення. Що робити, якщо кнопка відображається в момент, але її значення змінюється з коду?
Павло

1
Я не розумію, як "isShown ()" відрізняє дії користувача від програмних змін? наприклад, як можна сказати, що це дія користувача, якщо "isShown ()" є правдою?
Мухаммед Рефаат

1
Це рішення працює лише у дуже конкретних випадках і покладається на незадокументований, не гарантований процес створення та компонування діяльності. Немає гарантій, що це не зламається в майбутньому, і це не спрацює, якщо подання вже надано.
Мануель

26

Всередині onCheckedChanged()просто перевірте, чи є у користувача насправді checked/uncheckedперемикач, а потім виконайте інформацію відповідно до наступного:

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

Гарне рішення, не потрібно налаштовувати вид.
Аю

Я тебе люблю. : DDD
Влад

12
це не працює, коли користувач перемикає комутатор, перетягуючи / ковзаючи
mohitum

1
Використовуючи цей підхід скрізь, але знайшли випадок, він не працює, оскільки isPressedповертає помилковий пристрій Nokia з TalkBack.
Михайло

SwitchMaterial працює без проблем. Гарне рішення, дякую!
R00Ми

25

Ви можете видалити слухача перед тим, як змінити його програмно і додати його знову, як відповіли в наступному повідомленні ТА:

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

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

4

Спробуйте розширити CheckBox. Щось подібне (не повний приклад):

public MyCheckBox extends CheckBox {

   private Boolean isCheckedProgramatically = false;

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

   public Boolean isNotSetByUser() {
      return isCheckedProgramatically;
   }

}

3

Є ще одне просте рішення, яке працює досить добре. Приклад - для 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

Цікаве запитання. Наскільки мені відомо, щойно ви перебуваєте у слухачі, ви не можете виявити, яка дія викликала слухача, контексту недостатньо. Якщо ви не використовуєте зовнішнє булеве значення в якості індикатора.

Якщо ви встановите прапорець "програмно", встановіть булеве значення перед тим, щоб вказати, що це було зроблено програмно. Щось на зразок:

private boolean boxWasCheckedProgrammatically = false;

....

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

І у своєму слухачі не забудьте скинути стан прапорця:

@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

Спробуйте NinjaSwitch:

Просто зателефонуйте, setChecked(boolean, true)щоб змінити перевірений стан комутатора без виявлення!

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

Якщо OnClickListenerце вже встановлено і його не слід перезаписувати, використовуйте !buttonView.isPressed()як isNotSetByUser().

В іншому випадку найкращим варіантом є використання OnClickListenerзамість OnCheckedChangeListener.


buttonView.isPress () - приємне рішення. Виникає проблема, коли ми використовуємо OnClickListener, коли користувач ковзає на комутаторі, ми не отримуватимемо зворотного дзвінка.
Аю

2

Прийняту відповідь можна трохи спростити, щоб не підтримувати посилання на початковий прапорець. Це робить це так, що ми можемо використовувати SilentSwitchCompat(або, SilentCheckboxCompatякщо вам зручніше) безпосередньо в XML. Я також зробив це так , ви можете встановити , OnCheckedChangeListenerщоб , nullякщо ви хочете , щоб зробити це.

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

Потім ви можете використовувати це безпосередньо у своєму XML так (Примітка: вам знадобиться ціла назва пакету):

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

Потім ви можете встановити прапорець безшумно, зателефонувавши:

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

1

Ось моя реалізація

Код Java для користувацького перемикача:

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

Еквівалентний код Котліна:

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

Щоб змінити стан комутатора, не запускаючи слухача:

swSelection.setCheckedSilently(contact.isSelected)

Ви можете стежити за зміною стану, як правило:

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

У Котліні:

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

1

Мій варіант з функціями розширення Kotlin:

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

... на жаль, нам потрібно щоразу переходити onCheckedChangeListener, оскільки клас CheckBox не отримує для поля mOnCheckedChangeListener ((

Використання:

checkbox.setCheckedSilently(true, myCheckboxListener)

0

Створіть змінну

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;          

}

У програмі, коли ви змінюєте його замість користувача, дзвоніть, notSetByUser(true)щоб він не встановлювався користувачем, інакше дзвоніть, notSetByUser(false)тобто встановлюється програмою.

Нарешті, після того, як виклик isNotSetByUser (), слухач вашої події переконайтеся, що ви знову повернете його до нормального.

Викликайте цей метод щоразу, коли ви обробляєте цю дію або через користувача, або програмно. Викличте notSetByUser () з відповідним значенням.


0

Якщо тег перегляду не використовується, ви можете використовувати його замість розширення прапорця:

        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
                    }

кожен раз, коли ви викликаєте щось, що перевіряє / знімає прапорець, також дзвоніть це перед тим, як перевірити / зняти прапорець:

checkbox.setTag(true);

0

Я створив розширення за допомогою простого RxJava PublishSubject. Реагує лише на події "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
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.