Como posso distinguir se Switch, Checkbox Value é alterado pelo usuário ou programaticamente (incluindo por retenção)?

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

Como implementar o método isNotSetByUser()?

Solvek
fonte
Não tenho certeza, mas acho que se o usuário alternasse, você também obteria um retorno de chamada onClick se definir esse listener. Então, talvez você possa definir apenas um sinalizador booleano no onClick, dessa forma, você pode verificar em onCheckChanged para ver se o usuário iniciou a mudança.
FoamyGuy
Tenho uma solução mais simples e clara: consulte stackoverflow.com/a/41574200/3256989
ultraon

Respostas:

157

Resposta 2:

Uma resposta muito simples:

Use OnClickListener em vez de 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);
        }

    });

Agora você só seleciona eventos de clique e não precisa se preocupar com alterações programáticas.


Resposta 1:

Eu criei uma classe de wrapper (consulte Decorator Pattern) que lida com esse problema de forma encapsulada:

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 ainda pode ser declarado o mesmo em XML, mas use isso ao inicializar sua GUI no código:

BetterCheckBox myCheckBox;

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

Se você deseja definir verificado a partir do código sem acionar o ouvinte, chame em myCheckBox.silentlySetChecked(someBoolean)vez de setChecked.

antropomo
fonte
15
Para a Switch, a resposta 1 funciona em casos de toque e slide, enquanto a resposta 2 só funcionará no caso de toque. Como preferência pessoal, fiz minha classe estender CheckBox/ Switchao invés de manter uma referência a um. Parece mais limpo dessa forma (observe que você deve especificar o nome completo do pacote no XML se fizer isso).
Howettl
1
Obrigado por este antropomo, não sei porque não pensei nisso antes, mas você me poupou um tempo precioso;). Felicidades !
CyberDandy
Não tenho certeza sobre isso, mas se você estender SwitchCompat (usando appcompat v7) para obter a mudança de design de material, você pode encerrar os novos recursos de reprojeto e tingimento.
DNax
4
Ambas as soluções apresentam falhas graves. Primeira solução: quando o usuário arrasta o interruptor, o ouvinte não é acionado. Segunda solução: como setOnCheckedChangeListener faz essa verificação de nulos, na verdade define o ouvinte antigo quando há um novo já definido.
Paul Woitaschek
Primeira solução: problema conhecido e semi-aceitável se você quiser uma solução concisa e não tiver a intenção de usar esse widget. Segunda solução: alterou a verificação de nulos para resolver esse tipo de caso extremo improvável. Agora atribuímos listenera myListenerquando listenernão for nulo. Em quase todos os casos, isso não faz nada, mas se criarmos um novo ouvinte por algum motivo, isso não será quebrado.
anthropomo
38

Talvez você possa verificar isShown ()? Se TRUE - do que o usuário. Funciona para mim.

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();
        }
    }
});
Denis Babak
fonte
1
Ele funciona (significando nenhum retorno de chamada inesperado) mesmo que você chame "setChecked (isChecked)" em onStart () ou onResume (). Portanto, pode ser considerada uma solução perfeita.
Alan Zhiliang Feng
1
Parece não ser uma solução geral. E se o botão for mostrado no momento, mas seu valor for alterado do código?
Pavel
1
Não entendi, como 'isShown ()' distingue entre ações do usuário e alterações programáticas? como como ele pode dizer que é uma ação do usuário se 'isShown ()' for verdadeiro?
Muhammed Refaat
1
Esta solução funciona apenas em casos muito específicos e depende de um processo de criação e layout de atividade não documentado e não garantido. Não há garantia de que isso não quebrará no futuro e não funcionará se uma visualização já estiver renderizada.
Manuel
26

Dentro do, onCheckedChanged()basta verificar se o usuário tem realmente checked/uncheckedo botão de opção e fazer as coisas de acordo da seguinte maneira:

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.   
    }
  }
});
adi
fonte
Boa solução, sem necessidade de customizar a visualização.
Adju
Eu te amo. : DDD
Vlad
12
isso não está funcionando quando o usuário está alternando o botão deslizando / deslizando
mohitum
1
Usando essa abordagem em todos os lugares, mas encontrou um caso que não funciona como isPressedretorna falso, dispositivo Nokia com TalkBack ativado.
Mikhail
SwitchMaterial funciona sem problemas. Boa decisão, obrigado!
R00We
25

Você pode remover o ouvinte antes de alterá-lo programaticamente e adicioná-lo novamente, conforme respondido na seguinte postagem do SO:

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

theCheck.setOnCheckedChangeListener(null);
theCheck.setChecked(false);
theCheck.setOnCheckedChangeListener(toggleButtonChangeListener);
Eli Kohen
fonte
4

Tente estender o CheckBox. Algo assim (exemplo não completo):

public MyCheckBox extends CheckBox {

   private Boolean isCheckedProgramatically = false;

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

   public Boolean isNotSetByUser() {
      return isCheckedProgramatically;
   }

}
Romain Piel
fonte
3

Existe outra solução simples que funciona muito bem. O exemplo é para 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;
    }
}
Planejado
fonte
2

Pergunta interessante. Pelo que sei, uma vez que você está no ouvinte, você não pode detectar qual ação acionou o ouvinte, o contexto não é suficiente. A menos que você use um valor booleano externo como indicador.

Quando você marca a caixa "programaticamente", defina um valor booleano antes para indicar que foi feito programaticamente. Algo como:

private boolean boxWasCheckedProgrammatically = false;

....

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

E em seu ouvinte, não se esqueça de redefinir o estado da caixa de seleção:

@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;
}
Guillaume
fonte
2

Experimente NinjaSwitch:

Basta chamar setChecked(boolean, true)para alterar o estado verificado do switch sem ser detectado!

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);
        }
    }
}
Taeho Kim
fonte
2

Se OnClickListenerjá estiver definido e não deve ser substituído, use !buttonView.isPressed()como isNotSetByUser().

Caso contrário, a melhor variante é usar em OnClickListenervez de OnCheckedChangeListener.

Pavel
fonte
buttonView.isPressed () é uma boa solução. Há um problema quando usamos OnClickListener, quando o usuário desliza na chave, não obtemos retorno de chamada.
Adju
2

A resposta aceita pode ser um pouco simplificada para não manter uma referência à caixa de seleção original. Isso permite que possamos usar o SilentSwitchCompat(ou SilentCheckboxCompatse preferir) diretamente no XML. Eu também fiz isso para que você possa definir o OnCheckedChangeListenercomo nullse desejar.

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

Você pode então usar isso diretamente em seu XML da seguinte maneira (Observação: você precisará de todo o nome do pacote):

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

Em seguida, você pode marcar a caixa silenciosamente chamando:

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

Aqui está minha implementação

Código Java para troca personalizada:

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

Código Kotlin equivalente:

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

Para alterar o estado do switch sem acionar o ouvinte, use:

swSelection.setCheckedSilently(contact.isSelected)

Você pode monitorar a mudança de estado normalmente:

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

Em Kotlin:

 swSelection.setOnCheckedChangeListener{buttonView, isChecked -> run {
            contact.isSelected = isChecked
        }}
Thilina Kj
fonte
1

Minha variante com funções de extensão Kotlin:

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

... infelizmente, precisamos passar onCheckedChangeListener todas as vezes porque a classe CheckBox não tem um getter para o campo mOnCheckedChangeListener ((

Uso:

checkbox.setCheckedSilently(true, myCheckboxListener)
Fedir Tsapana
fonte
0

Crie uma variável

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;          

}

No aplicativo, quando você alterar em vez do usuário, chame de notSetByUser(true)forma que não seja definido pelo usuário, senão chame, notSetByUser(false)ou seja, ele é definido pelo programa.

Por último, em seu ouvinte de evento, depois de chamar isNotSetByUser (), certifique-se de alterá-lo novamente para o normal.

Chame esse método sempre que estiver manipulando essa ação por meio do usuário ou programaticamente. Chame notSetByUser () com o valor apropriado.

Tvd
fonte
0

Se a tag da visualização não for usada, você pode usá-la em vez de estender a caixa de seleção:

        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
                    }

cada vez que você chamar algo que marque / desmarque a caixa de seleção, chame também antes de marcar / desmarcar:

checkbox.setTag(true);
desenvolvedor android
fonte
0

Criei uma extensão com RxJava PublishSubject, simples. Reage apenas a eventos "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
}
Janusz Hain
fonte