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

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()?

Ответ 1

Ответ 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:

Я создал класс-оболочку (см. Decorator Pattern), который обрабатывает эту проблему инкапсулированным способом:

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.

Ответ 2

Возможно, вы можете проверить isShown()? Если TRUE - чем пользователь. Работает для меня.

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

Ответ 3

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

fooobar.com/questions/70635/...

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

Ответ 4

Внутри onCheckedChanged() просто проверьте, действительно ли пользователь выполнил проверку/снятие флажка с радиокнопки, а затем выполните следующее:

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

});

Ответ 5

Попробуйте расширить CheckBox. Что-то вроде этого (не полный пример):

public MyCheckBox extends CheckBox {

   private Boolean isCheckedProgramatically = false;

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

   public Boolean isNotSetByUser() {
      return isCheckedProgramatically;
   }

}

Ответ 6

Существует еще одно простое решение, которое работает очень хорошо. Пример для коммутатора.

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

Ответ 7

Интересный вопрос. Насколько я знаю, как только вы в слушателе, вы не можете определить, какое действие вызвало слушателя, контекста недостаточно. Если вы не используете внешнее логическое значение в качестве индикатора.

Когда вы устанавливаете флажок "программно", задайте логическое значение, прежде чем указывать, что это было сделано программно. Что-то вроде:

private boolean boxWasCheckedProgrammatically = false;

....

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

И в своем слушателе не забудьте reset состояние флажка:

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

Ответ 8

Попробуйте 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);
        }
    }
}

Ответ 9

Если OnClickListener уже установлен и не должен быть перезаписан, используйте !buttonView.isPressed() как isNotSetByUser().

В противном случае лучшим вариантом является использование OnClickListener вместо OnCheckedChangeListener.

Ответ 10

Принятый ответ может быть немного упрощен, чтобы не поддерживать ссылку на исходный флажок. Это делает так, что мы можем использовать 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)

Ответ 11

Создать переменную

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() с соответствующим значением.

Ответ 12

Если тег представления не используется, вы можете использовать его вместо расширения флажка:

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

Ответ 13

Вот моя реализация

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

Ответ 14

Мой вариант с функциями расширения 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 не имеет метода get для поля mOnCheckedChangeListener ((

Использование:

checkbox.setCheckedSilently(true, myCheckboxListener)