ฉันจะแยกแยะได้อย่างไรว่า Switch, Checkbox Value ถูกเปลี่ยนโดยผู้ใช้หรือทางโปรแกรม (รวมถึงโดยการเก็บรักษา)


111
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

1
ที่เกี่ยวข้องกับค่าเปลี่ยน Checkbox ไม่เรียก onCheckChangedเสนอโดยKrishan
bummi

ฉันมีวิธีแก้ปัญหาที่ง่ายและชัดเจนมากขึ้น: ดูstackoverflow.com/a/41574200/3256989
ultraon

คำตอบ:


158

คำตอบ 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:

ฉันได้สร้างคลาส wrapper (ดูรูปแบบมัณฑนากร) ซึ่งจัดการปัญหานี้ด้วยวิธีการห่อหุ้ม:

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
ขอบคุณสำหรับ anthropomo นี้ฉันไม่รู้ว่าทำไมฉันถึงไม่คิดถึงเรื่องนี้ก่อนหน้านี้ แต่คุณช่วยฉันเวลาอันมีค่า;) ไชโย!
CyberDandy

ฉันไม่แน่ใจเกี่ยวกับเรื่องนี้ แต่ถ้าคุณขยาย SwitchCompat (โดยใช้ appcompat v7) เพื่อรับสวิตช์ดีไซน์ Material คุณอาจสิ้นสุดความสามารถในการออกแบบและการย้อมสีใหม่
DNax

4
โซลูชันทั้งสองมีข้อบกพร่องร้ายแรง วิธีแก้ปัญหาแรก: เมื่อผู้ใช้ลากสวิตช์ผู้ฟังจะไม่เริ่มทำงาน แนวทางที่สอง: เนื่องจาก setOnCheckedChangeListener ทำการตรวจสอบค่าว่างนั้นจึงตั้งค่าตัวฟังเก่าเมื่อมีชุดใหม่อยู่แล้ว
Paul Woitaschek

วิธีแก้ปัญหาแรก: ปัญหาที่ทราบและกึ่งยอมรับได้หากคุณต้องการวิธีแก้ปัญหาที่กระชับและไม่ต้องการใช้วิดเจ็ตนั้น วิธีที่สอง: เปลี่ยนการตรวจสอบโมฆะเพื่อระบุกรณีขอบที่ไม่น่าเป็นไปได้ประเภทนั้น ตอนนี้เรากำหนดlistenerให้myListenerเมื่อใดก็ตามที่listenerไม่เป็นโมฆะ ในเกือบทุกกรณีสิ่งนี้ไม่ได้ทำอะไรเลย แต่ถ้าเราสร้างผู้ฟังใหม่ด้วยเหตุผลบางประการมันก็ไม่เสีย
anthropomo

38

บางทีคุณสามารถตรวจสอบ isShown ()? หากเป็นจริง - มากกว่าผู้ใช้ ใช้ได้ผลสำหรับฉัน

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
มันใช้งานได้ (หมายถึงไม่มีการโทรกลับที่ไม่คาดคิด) แม้ว่าคุณจะเรียก "setChecked (isChecked)" ใน onStart () หรือ onResume () จึงถือได้ว่าเป็นโซลูชั่นที่สมบูรณ์แบบ
Alan Zhiliang Feng

1
ดูเหมือนไม่ใช่วิธีแก้ปัญหาทั่วไป จะเกิดอะไรขึ้นถ้าปุ่มนี้แสดงขึ้นในขณะนี้ แต่ค่าของมันเปลี่ยนจากรหัส
Pavel

1
ฉันไม่เข้าใจว่า 'isShown ()' แยกความแตกต่างระหว่างการกระทำของผู้ใช้และการเปลี่ยนแปลงทางโปรแกรมอย่างไร เช่นวิธีที่สามารถบอกได้ว่าเป็นการกระทำของผู้ใช้หาก 'isShown ()' เป็นจริง?
Muhammed Refaat

1
โซลูชันนี้ใช้ได้เฉพาะในกรณีที่เฉพาะเจาะจงมากและอาศัยกระบวนการสร้างและจัดวางกิจกรรมที่ไม่มีเอกสารและไม่รับประกัน ไม่มีการรับประกันว่าสิ่งนี้จะไม่พังในอนาคตและจะไม่ทำงานหากมีการแสดงผลมุมมองแล้ว
Manuel

26

ภายใน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เท็จอุปกรณ์ Nokia ที่เปิด TalkBack
Mikhail

SwitchMaterial ทำงานได้โดยไม่มีปัญหา ตัดสินใจได้ดีขอบคุณ!
R00We

25

คุณสามารถลบฟังก่อนที่จะเปลี่ยนโดยทางโปรแกรมและเพิ่มอีกครั้งดังที่ตอบในโพสต์ SO ต่อไปนี้:

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

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

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

มิฉะนั้นตัวแปรที่ดีที่สุดคือการใช้แทนOnClickListenerOnCheckedChangeListener


buttonView.isPressed () เป็นทางออกที่ดี มีปัญหาเมื่อเราใช้ OnClickListener เมื่อผู้ใช้เลื่อนสวิตช์เราจะไม่ได้รับการติดต่อกลับ
อาจู

2

คำตอบที่ยอมรับอาจทำให้ง่ายขึ้นเล็กน้อยเพื่อไม่ให้อ้างอิงกับช่องทำเครื่องหมายเดิม สิ่งนี้ทำให้เราสามารถใช้SilentSwitchCompat(หรือSilentCheckboxCompatถ้าคุณต้องการ) โดยตรงใน XML ฉันยังทำมันเพื่อให้คุณสามารถตั้งค่า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
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.