如何区分Switch,Checkbox值是由用户还是通过编程方式更改(包括保留)?


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 2012年


我有一个更简单明了的解决方案:请参阅stackoverflow.com/a/41574200/3256989
ultraon

Answers:


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:

我创建了一个包装器类(请参见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


15
对于a Switch,答案1在分接盒和滑套中都有效,而答案2仅在分接盒中起作用。作为个人喜好,我让我的课扩展CheckBox/ Switch而不是引用一个。感觉更干净(请注意,如果要这样做,则必须在XML中指定完整的程序包名称)。
howettl

1
感谢这个人类活动,我不知道为什么我没有早些考虑,但是您为我节省了一些宝贵的时间;)。干杯!
Cyber​​Dandy

我对此不确定,但是如果您扩展SwitchCompat(使用appcompat v7)以获取材质设计开关,则可能会终止新的重新设计和着色功能。
DNax 2014年

4
两种解决方案都有严重的缺陷。第一个解决方案:用户拖动开关时,不会触发监听器。第二种解决方案:因为setOnCheckedChangeListener会进行null检查,所以实际上在设置了新的侦听器时会设置旧的侦听器。
Paul Woitaschek '16

第一个解决方案:如果您想要一个简洁的解决方案并且不打算使用该小部件,则是已知且半可接受的问题。第二种解决方案:更改了null检查以解决这种不太可能的边缘情况。现在,我们分配listenermyListenerwhen的listener值不为null。在几乎所有情况下,这都不起作用,但是如果由于某种原因我们创建了新的侦听器,那么这并没有中断。
anthropomo

38

也许您可以检查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();
        }
    }
});

1
即使您在onStart()或onResume()中调用“ setChecked(isChecked)”,它也可以工作(意味着没有意外的回调)。因此,它可以被认为是一个完美的解决方案。
冯志良

1
似乎不是一般的解决方案。如果此时显示按钮,但其值已从代码中更改,该怎么办?
帕维尔

1
我不明白,“ isShown()”如何区分用户操作和程序更改?像“ isShown()”为true时如何说是用户操作?
Muhammed Refaat

1
该解决方案仅在非常特殊的情况下有效,并且依赖于未记录的,无保证的活动创建和布局过程。无法保证将来不会中断,如果已经渲染视图,则无法正常工作。
曼努埃尔

26

在Just内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
Vlad

12
当用户通过滑动/滑动来切换开关时,此功能不起作用
Mohitum

1
随处使用此方法,但发现情况不成立,因为isPressed返回false的诺基亚设备已启用TalkBack。
米哈伊尔

SwitchMaterial工作正常。好的决定,谢谢!
R00We


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.isPressed()是一个不错的解决方案。当我们使用OnClickListener时出现问题,当用户在开关上滑动时,我们将不会获得回调。
阿菊

2

可以简化接受的答案,以免保留对原始复选框的引用。这样,我们就可以直接在XML中使用SilentSwitchCompat(或,SilentCheckboxCompat如果愿意)。我也做到了,因此您可以根据需要将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);
}}

等效的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)
}}

要在不触发监听器的情况下更改开关状态,请使用:

swSelection.setCheckedSilently(contact.isSelected)

您可以通过以下方式照常监视状态更改:

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

在Kotlin:

 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
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.