Switch、Checkbox Valueがユーザーによって変更されたか、プログラムによって(保持によるものを含む)変更されたかをどのように区別できますか?


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

メソッドの実装方法はisNotSetByUser()


確かではありませんが、ユーザーが切り替えた場合、そのリスナーを設定すると、onClickコールバックも発生すると思います。そのため、おそらくonClickにブールフラグを設定できますが、onCheckChangedでチェックして、ユーザーが変更を開始したかどうかを確認できます。
FoamyGuy


私はもっと単純明快な解決策を持っている:参照stackoverflow.com/a/41574200/3256989
ultraon

回答:


157

回答2:

非常に簡単な答え:

OnCheckedChangeListenerの代わりにOnClickListenerを使用します

    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:

この問題をカプセル化して処理するラッパークラス(Decoratorパターンを参照)を作成しました。

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
の場合Switch、回答1はタップケースとスライドケースの両方で機能しますが、回答2はタップケースでのみ機能します。個人的な好みとして、クラスへの参照を保持するのではなく、クラスを拡張CheckBox/しましたSwitch。そのようにすっきりした感じになります(そうする場合は、XMLで完全なパッケージ名を指定する必要があることに注意してください)。
howettl 2013

1
この人類のおかげで、なぜ以前にそれを考えなかったのかわかりませんが、貴重な時間を節約してくれました;)乾杯!
Cyber​​Dandy 2014

これについてはよくわかりませんが、SwitchCompatを(appcompat v7を使用して)拡張してマテリアルデザインのスイッチを取得すると、新しい再設計と色付け機能が終了する可能性があります。
DNax 2014年

4
どちらのソリューションにも重大な欠陥があります。最初の解決策:ユーザーがスイッチをドラッグしても、リスナーは起動されません。2番目のソリューション:setOnCheckedChangeListenerはnullチェックを行うため、新しいリスナーがすでに設定されている場合、実際には古いリスナーが設定されます。
Paul Woitaschek

最初の解決策:簡潔な解決策が必要で、そのウィジェットを使用するつもりがない場合の既知の半許容可能な問題。2番目の解決策:ヌルチェックを変更して、このようなあり得ないエッジケースに対処しました。今、私たちは、割り当てlistenermyListenerいつでもlistenernullではありません。ほとんどの場合、これは何もしませんが、何らかの理由で新しいリスナーを作成しても、問題はありません。
anthropomo

38

多分あなたは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();
        }
    }
});

1
onStart()またはonResume()で「setChecked(isChecked)」を呼び出しても機能します(予期しないコールバックはありません)。したがって、これは完璧なソリューションと考えることができます。
Alan Zhiliang Feng 2016

1
一般的な解決策ではないようです。ボタンがすぐに表示されているが、その値がコードから変更されている場合はどうなりますか?
Pavel

1
わかりません。「isShown()」は、ユーザーアクションとプログラムの変更をどのように区別するのですか。「isShown()」がtrueの場合、ユーザーアクションであるとどのように言うことができますか?
Muhammed Refaat 2017

1
このソリューションは非常に特殊なケースでのみ機能し、文書化されていない、保証されていないアクティビティの作成とレイアウトプロセスに依存しています。これが将来中断しないという保証はなく、ビューが既にレンダリングされている場合は機能しません。
マヌエル

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
2018年

12
ユーザーがスワイプ/スライドしてスイッチを切り替えると、これは機能しません
mohitum

1
どこでもこのアプローチを使用しましたがisPressed、TalkBackがオンになっているNokiaデバイスがfalse を返したため機能しないケースを見つけました。
ミハイル

SwitchMaterialは問題なく動作します。良い決断、ありがとう!
R00We


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

かなりうまくいく別の簡単な解決策があります。例はスイッチです。

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()asとして使用しisNotSetByUser()ます。

それ以外の場合、最良のバリアントはのOnClickListener代わりに使用することですOnCheckedChangeListener


buttonView.isPressed()は素晴らしいソリューションです。OnClickListenerを使用すると問題が発生します。ユーザーがスイッチをスライドすると、コールバックが取得されません。
アジュ

2

受け入れられた回答は、元のチェックボックスへの参照を維持しないように少し簡略化できます。これにより、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)

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

コトリンでは:

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

...残念ながら、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.