Как я могу определить, изменяется ли переключатель, значение флажка пользователем или программно (в том числе путем сохранения)?

111
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 03
У меня есть более простое и ясное решение: см stackoverflow.com/a/41574200/3256989
ultraon

Ответы:

158

Ответ 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, но используйте это при инициализации вашего графического интерфейса в коде:

BetterCheckBox myCheckBox;

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

Если вы хотите установить проверку из кода без запуска слушателя, вызовите myCheckBox.silentlySetChecked(someBoolean)вместо setChecked.

антропомик
источник
15
Для a Switchответ 1 работает как в случае касания, так и в случае слайдов, тогда как ответ 2 будет работать только в случае касания. В качестве личного предпочтения я сделал свой класс расширенным CheckBox/ Switchвместо того, чтобы содержать ссылку на него. Так выглядит чище (обратите внимание, что вы должны указать полное имя пакета в XML, если вы это сделаете).
howettl
1
Спасибо за этого антропомика, не знаю, почему я не подумал об этом раньше, но вы сэкономили мне драгоценное время;). Ура!
CyberDandy
Я не уверен в этом, но если вы расширите SwitchCompat (используя appcompat v7), чтобы получить переключатель материального дизайна, вы можете прекратить новые возможности редизайна и тонирования.
DNax
4
Оба решения имеют серьезные недостатки. Первое решение: когда пользователь перетаскивает переключатель, слушатель не запускается. Второе решение: поскольку setOnCheckedChangeListener выполняет эту нулевую проверку, он фактически устанавливает старый прослушиватель, когда уже установлен новый.
Paul Woitaschek
Первое решение: известная и полу-приемлемая проблема, если вы хотите краткое решение и не собираетесь использовать этот виджет. Второе решение: изменил нулевую проверку, чтобы разрешить такой маловероятный крайний случай. Теперь мы относим listenerк myListenerвсякий раз , когда listenerне равно нулю. Почти во всех случаях это ничего не дает, но если мы по какой-то причине создадим нового слушателя, это не сломается.
anthropomo
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 () истинно?
Мухаммед Рефаат 05
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возвращает false, устройство Nokia с включенным TalkBack.
Михаил
SwitchMaterial работают без проблем. Хорошее решение, спасибо!
R00We
25

Вы можете удалить слушателя, прежде чем изменять его программно, и добавить его снова, как указано в следующем сообщении SO:

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;
    }
}
Invised
источник
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.isPressed () - хорошее решение. Когда мы используем OnClickListener, возникает проблема: когда пользователь нажимает на переключатель, мы не получаем обратного вызова.
Aju
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)
Smalls
источник
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);
разработчик Android
источник
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
}
Януш Хайн
источник