可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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();
}
});
How to implement method isNotSetByUser()
?
回答1:
Answer 2:
A very simple answer:
Use on OnClickListener instead of 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);
}
});
Now you only pick up click events and don't have to worry about programmatic changes.
Answer 1:
I have created a wrapper class (see Decorator Pattern) which handles this problem in an encapsulated way:
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 can still be declared the same in XML, but use this when initializing your GUI in code:
BetterCheckBox myCheckBox;
// later...
myCheckBox = new BetterCheckBox(context,
(CheckBox) view.findViewById(R.id.my_check_box));
If you want to set checked from code without triggering the listener, call myCheckBox.silentlySetChecked(someBoolean)
instead of setChecked
.
回答2:
Maybe You can check isShown()? If TRUE - than it's user. Works for me.
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:
You can remove the listener before changing it programatically and add it again, as answered in the following SO post:
https://stackoverflow.com/a/14147300/1666070
theCheck.setOnCheckedChangeListener(null);
theCheck.setChecked(false);
theCheck.setOnCheckedChangeListener(toggleButtonChangeListener);
回答4:
Inside onCheckedChanged() just check whether user has actually checked/unchecked the radio button and then do the stuff accordingly as follows:
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:
Try extending CheckBox. Something like that (not complete example):
public MyCheckBox extends CheckBox {
private Boolean isCheckedProgramatically = false;
public void setChecked(Boolean checked) {
isCheckedProgramatically = true;
super.setChecked(checked);
}
public Boolean isNotSetByUser() {
return isCheckedProgramatically;
}
}
回答6:
There is another simple solution that works pretty well. Example is for 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;
}
}
回答7:
Interesting question. To my knowledge, once you're in the listener, you can't detect what action has triggered the listener, the context is not enough. Unless you use an external boolean value as an indicator.
When you check the box "programmatically", set a boolean value before to indicate it was done programmatically. Something like:
private boolean boxWasCheckedProgrammatically = false;
....
// Programmatic change:
boxWasCheckedProgrammatically = true;
checkBoxe.setChecked(true)
And in your listener, don't forget to reset the state of the checkbox:
@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:
Try NinjaSwitch
:
Just call setChecked(boolean, true)
to change the switch's checked state without detected!
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:
If OnClickListener
is already set and shouldn't be overwritten, use !buttonView.isPressed()
as isNotSetByUser()
.
Otherwise the best variant is to use OnClickListener
instead of OnCheckedChangeListener
.
回答10:
The accepted answer could be simplified a bit to not maintain a reference to the original checkbox. This makes it so we can use the SilentSwitchCompat
(or SilentCheckboxCompat
if you prefer) directly in the XML. I also made it so you can set the OnCheckedChangeListener
to null
if you desire to do so.
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);
}
}
You can then use this directly in your XML like so (Note: you will need the whole package name):
<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"/>
Then you can check the box silently by calling:
SilentCheckBox mySilentCheckBox = (SilentCheckBox) findViewById(R.id.my_check_box)
mySilentCheckBox.silentlySetChecked(someBoolean)
回答11:
Create a variable
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;
}
In the application when you change it instead of the user, call notSetByUser(true)
so it is not set by the user, else call notSetByUser(false)
i.e. it is set by program.
Lastly, in your event listener, after calling isNotSetByUser(), make sure you again change it back to normal.
Call this method whenever you are handling that action either thru user or programmatically. Call the notSetByUser() with appropriate value.
回答12:
If the view's tag isn't used, you can use it instead of extending the checkbox:
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
}
each time you call something that checks/unchecks the checkbox, also call this before checking/unchecking :
checkbox.setTag(true);
回答13:
Here is my implementation
Java Code for Custom Switch :
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);
}}
Equivalent Kotlin Code :
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)
}}
To change switch state without triggering listener use :
swSelection.setCheckedSilently(contact.isSelected)
You can monitor state change as normally by :
swSelection.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
// Do something
}
});
In Kotlin :
swSelection.setOnCheckedChangeListener{buttonView, isChecked -> run {
contact.isSelected = isChecked
}}