Làm cách nào để tôi có thể phân biệt liệu Chuyển đổi, Giá trị hộp kiểm được thay đổi bởi người dùng hay theo chương trình (bao gồm cả bằng cách giữ lại)?


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

Phương pháp thực hiện như thế nào isNotSetByUser()?


Tôi không chắc chắn, nhưng tôi nghĩ nếu người dùng bật tắt nó thì bạn cũng sẽ nhận được một cuộc gọi lại onClick nếu bạn đặt trình nghe đó. Vì vậy, có thể bạn có thể đặt nhưng cờ boolean trong onClick theo cách đó bạn có thể kiểm tra nó trong onCheckChanged để xem liệu người dùng có bắt đầu thay đổi hay không.
FoamyGuy


Tôi có giải pháp đơn giản hơn và rõ ràng: xem stackoverflow.com/a/41574200/3256989
ultraon

Câu trả lời:


157

Câu trả lời 2:

Một câu trả lời rất đơn giản:

Sử dụng trên OnClickListener thay vì 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);
        }

    });

Giờ đây, bạn chỉ chọn các sự kiện nhấp chuột và không phải lo lắng về các thay đổi có lập trình.


Trả lời 1:

Tôi đã tạo một lớp trình bao bọc (xem Mẫu trang trí) để xử lý sự cố này theo cách đóng gói:

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 vẫn có thể được khai báo giống nhau trong XML, nhưng hãy sử dụng điều này khi khởi tạo GUI của bạn trong mã:

BetterCheckBox myCheckBox;

// later...
myCheckBox = new BetterCheckBox(context,
    (CheckBox) view.findViewById(R.id.my_check_box));

Nếu bạn muốn đặt đã chọn từ mã mà không kích hoạt người nghe, hãy gọi myCheckBox.silentlySetChecked(someBoolean)thay vì setChecked.


15
Đối với a Switch, câu trả lời 1 hoạt động trong cả trường hợp chạm và trượt, trong khi câu trả lời 2 sẽ chỉ hoạt động trong trường hợp chạm. Theo sở thích cá nhân, tôi đã mở rộng lớp học của mình CheckBox/ Switchthay vì giữ một tham chiếu đến một lớp. Cảm thấy rõ ràng hơn theo cách đó (lưu ý rằng bạn phải chỉ định tên gói đầy đủ trong XML nếu bạn làm điều đó).
howettl

1
Cảm ơn vì anthropomo này, không hiểu sao tôi không nghĩ đến nó sớm hơn, nhưng bạn đã tiết kiệm cho tôi một khoảng thời gian quý báu;). Chúc mừng!
CyberDandy

Tôi không chắc về điều này, nhưng nếu bạn mở rộng SwitchCompat (sử dụng appcompat v7) để nhận chuyển đổi material design, bạn có thể kết thúc khả năng thiết kế lại và pha màu mới.
DNax

4
Cả hai giải pháp đều có sai sót nghiêm trọng. Giải pháp đầu tiên: Khi người dùng kéo công tắc, người nghe sẽ không bị kích hoạt. Giải pháp thứ hai: Bởi vì setOnCheckedChangeListener kiểm tra null nên nó thực sự đặt bộ lắng nghe cũ khi có một bộ mới đã được thiết lập.
Paul Woitaschek

Giải pháp đầu tiên: vấn đề đã biết và bán chấp nhận được nếu bạn muốn một giải pháp ngắn gọn và không có ý định sử dụng tiện ích đó. Giải pháp thứ hai: đã thay đổi kiểm tra null để giải quyết loại trường hợp cạnh khó xảy ra đó. Bây giờ chúng tôi chỉ địnhlistener cho myListenerbất cứ khi nào listenerkhông phải là null. Trong hầu hết các trường hợp, điều này không có tác dụng gì, nhưng nếu chúng ta tạo ra một người nghe mới vì một lý do nào đó, điều đó không bị hỏng.
anthropomo

38

Có lẽ Bạn có thể kiểm tra isShown ()? Nếu ĐÚNG - hơn là người dùng. Làm việc cho tôi.

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
Nó hoạt động (có nghĩa là không có cuộc gọi lại bất ngờ) ngay cả khi bạn gọi "setChecked (isChecked)" trong onStart () hoặc onResume (). Vì vậy, nó có thể được coi là một giải pháp hoàn hảo.
Alan Zhiliang Feng,

1
Có vẻ như không phải là một giải pháp chung. Điều gì sẽ xảy ra nếu nút được hiển thị trong thời điểm này nhưng giá trị của nó bị thay đổi từ mã?
Pavel

1
Tôi không hiểu, làm thế nào 'isShown ()' được phân biệt giữa hành động của người dùng và các thay đổi có lập trình? như cách nó có thể nói đó là một hành động của người dùng nếu 'isShown ()' là true?
Muhammed Refaat

1
Giải pháp này chỉ hoạt động trong những trường hợp rất cụ thể và dựa vào quá trình tạo và bố trí hoạt động không có tài liệu, không đảm bảo. Không có gì đảm bảo rằng điều này sẽ không bị hỏng trong tương lai và nó không hoạt động nếu một chế độ xem đã được hiển thị.
Manuel

26

Bên trong, onCheckedChanged()chỉ cần kiểm tra xem người dùng có thực sự có checked/uncheckednút radio hay không và sau đó thực hiện những thứ tương ứng như sau:

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

Giải pháp tốt, không cần tùy chỉnh chế độ xem.
Aju

Tôi mến bạn. : DDD
Vlad

12
này không hoạt động khi người dùng được chuyển đổi qua lại các công tắc bằng cách swiping / trượt
mohitum

1
Sử dụng phương pháp này ở khắp mọi nơi nhưng đã tìm thấy một trường hợp nó không hoạt động vì isPressedtrả về sai, thiết bị Nokia đã bật TalkBack.
Mikhail

SwitchMaterial hoạt động mà không có vấn đề gì. Quyết định tốt, cảm ơn!
R00We

25

Bạn có thể xóa trình nghe trước khi thay đổi theo chương trình và thêm lại, như đã trả lời trong bài đăng SO sau:

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

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

4

Hãy thử mở rộng CheckBox. Một cái gì đó tương tự (không phải là ví dụ hoàn chỉnh):

public MyCheckBox extends CheckBox {

   private Boolean isCheckedProgramatically = false;

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

   public Boolean isNotSetByUser() {
      return isCheckedProgramatically;
   }

}

3

Có một giải pháp đơn giản khác hoạt động khá tốt. Ví dụ là cho 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

Câu hỏi thú vị. Theo hiểu biết của tôi, khi bạn đã ở trong người nghe, bạn không thể phát hiện ra hành động nào đã kích hoạt người nghe, ngữ cảnh là chưa đủ. Trừ khi bạn sử dụng giá trị boolean bên ngoài làm chỉ báo.

Khi bạn chọn hộp "theo chương trình", hãy đặt giá trị boolean trước đó để cho biết giá trị đó đã được thực hiện theo chương trình. Cái gì đó như:

private boolean boxWasCheckedProgrammatically = false;

....

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

Và trong trình nghe của bạn, đừng quên đặt lại trạng thái của hộp kiểm:

@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

Hãy thử NinjaSwitch:

Chỉ cần gọi setChecked(boolean, true)để thay đổi trạng thái đã kiểm tra của công tắc mà không bị phát hiện!

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

Nếu OnClickListenerđã được đặt và không được ghi đè, hãy sử dụng !buttonView.isPressed()như isNotSetByUser().

Nếu không, biến thể tốt nhất là sử dụng OnClickListenerthay vì OnCheckedChangeListener.


buttonView.isPressed () là một giải pháp tốt. Có một vấn đề khi chúng tôi sử dụng OnClickListener, khi người dùng trượt trên nút chuyển, chúng tôi sẽ không nhận được lệnh gọi lại.
Aju

2

Câu trả lời được chấp nhận có thể được đơn giản hóa một chút để không duy trì tham chiếu đến hộp kiểm ban đầu. Điều này làm cho nó để chúng tôi có thể sử dụng SilentSwitchCompat(hoặc SilentCheckboxCompatnếu bạn thích) trực tiếp trong XML. Tôi cũng đã làm cho nó, do đó bạn có thể thiết lập OnCheckedChangeListenerđể nullnếu bạn muốn làm như vậy.

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

Sau đó, bạn có thể sử dụng nó trực tiếp trong XML của mình như vậy (Lưu ý: bạn sẽ cần cả tên gói):

<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"/>

Sau đó, bạn có thể đánh dấu chọn hộp bằng cách gọi:

SilentCheckBox mySilentCheckBox = (SilentCheckBox) findViewById(R.id.my_check_box)
mySilentCheckBox.silentlySetChecked(someBoolean)

1

Đây là cách thực hiện của tôi

Mã Java cho Công tắc tùy chỉnh:

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

Mã Kotlin tương đương:

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

Để thay đổi trạng thái chuyển đổi mà không kích hoạt trình nghe, hãy sử dụng:

swSelection.setCheckedSilently(contact.isSelected)

Bạn có thể theo dõi sự thay đổi trạng thái như bình thường bằng cách:

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

Trong Kotlin:

 swSelection.setOnCheckedChangeListener{buttonView, isChecked -> run {
            contact.isSelected = isChecked
        }}

1

Biến thể của tôi với các chức năng mở rộng Kotlin:

fun CheckBox.setCheckedSilently(isChecked: Boolean, onCheckedChangeListener: CompoundButton.OnCheckedChangeListener) {
    if (isChecked == this.isChecked) return
    this.setOnCheckedChangeListener(null)
    this.isChecked = isChecked
    this.setOnCheckedChangeListener(onCheckedChangeListener)
}

... rất tiếc, chúng ta cần phải chuyển onCheckedChangeListener vì lớp CheckBox không có getter cho trường mOnCheckedChangeListener ((

Sử dụng:

checkbox.setCheckedSilently(true, myCheckboxListener)

0

Tạo một biến

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;          

}

Trong ứng dụng khi bạn thay đổi nó thay vì người dùng, hãy gọi notSetByUser(true)như vậy nó không phải do người dùng thiết lập, người khác gọi notSetByUser(false)tức là nó được đặt bởi chương trình.

Cuối cùng, trong trình nghe sự kiện của bạn, sau khi gọi isNotSetByUser (), hãy đảm bảo rằng bạn lại thay đổi nó trở lại bình thường.

Gọi phương thức này bất cứ khi nào bạn xử lý hành động đó thông qua người dùng hoặc theo chương trình. Gọi notSetByUser () với giá trị thích hợp.


0

Nếu thẻ của chế độ xem không được sử dụng, bạn có thể sử dụng nó thay vì mở rộng hộp kiểm:

        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
                    }

mỗi khi bạn gọi một cái gì đó sẽ chọn / bỏ chọn hộp kiểm, hãy gọi cái này trước khi chọn / bỏ chọn:

checkbox.setTag(true);

0

Tôi đã tạo tiện ích mở rộng với RxJava's PublishSubject, đơn giản. Chỉ phản ứng trên các sự kiện "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
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.