【问题标题】:How can I distinguish whether Switch,Checkbox Value is changed by user or programmatically (including by retention)?如何区分 Switch、Checkbox 值是由用户更改还是以编程方式(包括保留)更改?
【发布时间】:2012-02-26 03:24:44
【问题描述】:
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()

【问题讨论】:

标签: android checkbox listener onchange


【解决方案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:

我创建了一个包装类(参见装饰器模式),它以封装的方式处理这个问题:

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

【讨论】:

  • 对于Switch,答案 1 适用于水龙头和滑动盒,而答案 2 仅适用于水龙头盒。作为个人喜好,我让我的类扩展CheckBox/Switch,而不是引用一个。这样感觉更干净(请注意,如果这样做,您必须在 XML 中指定完整的包名称)。
  • 感谢这个 anthropomo,我不知道为什么我没有早点想到它,但是你为我节省了一些宝贵的时间;)。干杯!
  • 对此我不确定,但如果您扩展 SwitchCompat(使用 appcompat v7 )以获得材料设计切换,您可能会结束新的重新设计和着色功能。
  • 两种解决方案都有严重的缺陷。第一个解决方案:当用户拖动开关时,监听器不会被触发。第二种解决方案:因为 setOnCheckedChangeListener 会进行 null 检查,所以当已经设置了新的侦听器时,它实际上会设置旧的侦听器。
  • 第一个解决方案:如果您想要一个简洁的解决方案并且不打算使用该小部件,则已知和半可接受的问题。第二种解决方案:更改空检查以解决那种不太可能的边缘情况。现在,只要listener 不为空,我们就将listener 分配给myListener。在几乎所有情况下,这都不起作用,但如果我们出于某种原因创建了一个新的侦听器,那并没有坏掉。
【解决方案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();
        }
    }
});

【讨论】:

  • 即使您在 onStart() 或 onResume() 中调用“setChecked(isChecked)”,它也可以工作(意味着没有意外回调)。所以它可以被认为是一个完美的解决方案。
  • 似乎不是一个通用的解决方案。如果按钮在此刻显示,但它的值从代码中改变了怎么办?
  • 我不明白,'isShown()' 如何区分用户操作和程序更改?如果 'isShown()' 为真,它怎么能说这是一个用户操作?
  • 此解决方案仅适用于非常特定的情况,并且依赖于未记录的、无保证的活动创建和布局过程。不能保证这在将来不会中断,如果视图已经呈现,它就不起作用。
【解决方案3】:

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

【讨论】:

  • 很好的解决方案,不需要自定义视图。
  • 当用户通过滑动/滑动切换开关时,这不起作用
  • 在任何地方都使用这种方法,但发现一个案例它不起作用,因为isPressed 返回 false,诺基亚设备开启了 TalkBack。
  • SwitchMaterial 工作没有问题。很好的决定,谢谢!
  • 同意@Mikhail,当可访问性对讲开启时,此返回值会下降。在三星设备上测试
【解决方案4】:

您可以在以编程方式更改它之前删除侦听器并再次添加它,如以下 SO 帖子中所述:

https://stackoverflow.com/a/14147300/1666070

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

【讨论】:

    【解决方案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】:

      还有另一种效果很好的简单解决方案。以 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】:

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

        【讨论】:

          【解决方案8】:

          这应该足够了:

          SwitchCompact.setOnCheckedChangeListener((buttonView, isChecked) -> {
                   if (buttonView.isPressed()) {
                      if (!isChecked) {
                         //do something
                      } else {
                        // do something else
                      }
                   }
                });
          

          【讨论】:

            【解决方案9】:

            有趣的问题。据我所知,一旦你在监听器中,你就无法检测到触发监听器的动作,上下文是不够的。除非您使用外部布尔值作为指标。

            当您选中“以编程方式”复选框时,请在前面设置一个布尔值以指示它是以编程方式完成的。比如:

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

            【讨论】:

              【解决方案10】:

              如果OnClickListener 已设置且不应被覆盖,请将!buttonView.isPressed() 用作isNotSetByUser()

              否则最好的变体是使用OnClickListener 而不是OnCheckedChangeListener

              【讨论】:

              • buttonView.isPressed() 是一个不错的解决方案。我们在使用OnClickListener的时候有个问题,当用户在开关上滑动时,我们不会得到回调。
              【解决方案11】:

              可以稍微简化接受的答案,以不保留对原始复选框的引用。这使得我们可以直接在 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)
              

              【讨论】:

                【解决方案12】:

                这是我的实现

                自定义开关的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
                        }}
                

                【讨论】:

                  【解决方案13】:

                  我的带有 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)
                  

                  【讨论】:

                    【解决方案14】:

                    创建一个变量

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

                    【讨论】:

                      【解决方案15】:

                      如果视图的标签没有被使用,你可以使用它而不是扩展复选框:

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

                      【讨论】:

                        【解决方案16】:

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

                        【讨论】:

                          猜你喜欢
                          • 2013-10-30
                          • 2014-03-25
                          • 2023-02-22
                          • 2011-02-17
                          • 1970-01-01
                          • 2017-08-27
                          • 1970-01-01
                          • 2010-12-23
                          • 2010-11-20
                          相关资源
                          最近更新 更多