Android - 如何程序化地更改开关状态而不触发OnCheckChanged监听器

65
我正在寻找一种方法,使用 switch.setChecked(true); 来编程更改Android开关小部件的状态,而不会触发 OnCheckedChangedlistener。我的第一想法是用 OnClickListener 替换它,但由于这只注册点击事件,而您不仅可以点击,还可以滑动Switch,所以它不适合这个目的,因为如果用户将Switch从关闭状态滑到打开状态,则Switch实际上不会执行任何操作,因为用户没有点击... 如果有人有解决方案或聪明的解决方法,那就太棒了。

2
请使用以下代码行:"buttonView.isPressed()" - Mandeep Yadav
12个回答

65

抱歉,但这并没有奏效,onCheckedChanged 仍然被重复调用。 - iCantC

47

每个 CompoundButton(两状态按钮 - 开/关)都有一个“按下”状态,仅当用户按住视图时状态为真。

在开始实际逻辑之前,在您的侦听器中添加检查:

if(compoundButton.isPressed()) {
    // continue with your listener
}

这样,通过编程方式更改选中的值不会触发不需要的代码。

引用自@krisDrOid 的回答。


2
这种方法不可靠,因为对于滑动交互而言,isPressed 经常会是 false,而不是 tap。 - Roman Kotenko

42

在使用 switch 进行代码操作之前,您可以先取消注册监听器,然后进行所需的操作,最后再次注册该监听器。


你该如何注销监听器?抱歉,我对此还很陌生。 - Paul Alexander
2
我认为有两个选项:
  1. switch.setOnCheckedChangeListener(null); //不确定是否会导致问题...
  2. 创建一个虚拟类,实现OnCheckedChangeListender接口,并使用一个什么也不做的虚拟方法onCheckedChanged。然后注册一个新对象,稍后再次注册默认监听器。
- smnpl
如果我使用 switch.setOnCheckedChangeListener(null);,我该如何重新注册监听器呢?是 switch.setOnCheckedChangeListener(listener); 吗? - Paul Alexander
2
只要“listener”是指向您的监听器对象的引用,是的。那将是我解决这个问题的方法(尽管可能有更好的解决方案)。 - smnpl
4
你是怎么重新注册监听器的? - vamsidhar muthireddy
显示剩余3条评论

20

我有一个解决方案,在我的端上运行良好。 我在我的开关控件上添加了setOnTouchListener和setOnCheckedChangeListener,并添加了以下代码来解决我的问题。

    // set tag by default.
    mMySwitch.setTag("TAG");

    // Add OnCheckedChangeListener.
    mMySwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
         @Override
         public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
            if (mMySwitch.getTag() != null) {
                 mMySwitch.setTag(null);
                 return;
                }

          // Do your stuff here.
        }
    });

    // Add Touch listener.
    mMySwitch.setOnTouchListener(new View.OnTouchListener() {
         @Override
         public boolean onTouch(View v, MotionEvent event) {
            mMySwitch.setTag(null);
            return false;
         }
    });

这样,只有通过人类干预(拖动、点击、触摸)才会调用setOnCheckedChangeListener。

同时,在尝试更改开关控件的状态时,请不要忘记添加有效的字符串标记(不为空)。例如:

 mMySwitch.setTag("TAG");
 mMySwitch.setChecked(true);

谢谢这个解决方案,帮了我很多!不需要mListener,不幸的是没有switch.getOnCheckedChangeListener()方法。顺便说一句,在你的onCheckedChanged()中可以用buttonView替换mMySwitch。 - Lucker10

10

编写自定义的Switch或SwitchCompat并覆盖setOnCheckedListener方法。

public class SwitchCompat extends android.support.v7.widget.SwitchCompat {
private boolean mIgnoreCheckedChange = false;

public SwitchCompat(Context context) {
    super(context);
}

public SwitchCompat(Context context, AttributeSet attrs) {
    super(context, attrs);
}

public SwitchCompat(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
}


@Override
public void setOnCheckedChangeListener(final OnCheckedChangeListener listener) {
    super.setOnCheckedChangeListener(new OnCheckedChangeListener() {
        @Override
        public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
            if (mIgnoreCheckedChange) {
                return;
            }
            listener.onCheckedChanged(buttonView, isChecked);
        }
    });
}

public void setChecked(boolean checked, boolean notify) {
    mIgnoreCheckedChange = !notify;
    setChecked(checked);
    mIgnoreCheckedChange = false;
}

}

3

我能找到的最佳解决方案是:

const val SWITCH_COMPAT_IGNORE_TAG = "SWITCH_COMPAT_IGNORE_TAG"

fun SwitchCompat.isCheckedWithIgnoreTag(isChecked: Boolean) {
    tag = SWITCH_COMPAT_IGNORE_TAG
    this.isChecked = isChecked
    tag = null
}

switchCompat.isCheckedWithIgnoreTag(true)

switchCompat.setOnCheckedChangeListener { buttonView, isChecked ->
    if (buttonView.tag != SWITCH_COMPAT_IGNORE_TAG) {
        //TODO
    }
}

完美的解决方案! - Krupa Kakkad

0

基于 @ronginat 和 @hyb1996 的答案,我创建了 SensibleSwitch,这样你就可以将其直接放入 XML 布局中,像平常一样使用监听器,而不必担心其他问题。

 import android.content.Context
 import android.util.AttributeSet
 import androidx.appcompat.widget.SwitchCompat

 class SensibleSwitch : SwitchCompat {
     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(listener: OnCheckedChangeListener?) {
         super.setOnCheckedChangeListener { compoundButton, isChecked ->
             if (isPressed) {
                 listener?.onCheckedChanged(compoundButton, isChecked)
             }
         }
     }
 }

0
switch.setOnCheckedChangeListener
{
    compoundButton, check
    if (switch.isPressed || switch.isSelected)
        if (check) {
            TODO("")
        } else { 
            TODO("")
        }
}

2
请不要发布仅包含代码的答案。未来的读者会感激您解释为什么这个答案回答了问题,而不是从代码中推断出来。此外,由于这是一个旧问题,请解释它如何补充所有其他答案。 - Gert Arnold
他字面上是在问“不触发OnCheckedChangedListener”。 - Daniel Cettour

-1
如果什么都没用,这里有一个巧妙的解决方案。
在开关上方创建一个TextView,并为其添加clickListener。
 <TextView
        android:elevation="1dp"
        android:id="@+id/switchLabel"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:text=""
        app:layout_constraintBottom_toBottomOf="@id/switch1"
        app:layout_constraintStart_toStartOf="@+id/switch1"
        app:layout_constraintTop_toTopOf="@id/switch1"
        app:layout_constraintEnd_toEndOf="@id/switch1" />

在TextView中添加高程。


-1
在 Kotlin 中以编程方式设置默认开关,对应于 XML 中的 <Switch/>,请使用以下代码:
mySwitch.isChecked = true

然后,如果你想在用户点击时执行某些操作:

 mySwitch.setOnCheckedChangeListener { compoundButton, isChecked ->
            when (isChecked) {
                true -> { /* do something here*/ }
                false -> { /* do something here*/ }
            }
        }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接