In Stackview, it seems that OnItemSelectedListener (from superclass
"AdapterView") is never called...
How can I trigger some event when the view on top of the stack is
changed by the user ?
I want to display some text to show the position of the current item
inside the stack, so I need to find a way to update the textview when the user browses through the stack.
Thanks,
A little late for the party but for folks coming here from google. Fortunately I found an easier solution. It still involves extending StackView though.
import android.content.Context;
import android.util.AttributeSet;
import android.widget.StackView;
public class StackViewAdv extends StackView
{
public StackViewAdv(Context context, AttributeSet attrs)
{
super(context, attrs);
}
public StackViewAdv(Context context, AttributeSet attrs, int defStyleAttr)
{
super(context, attrs, defStyleAttr);
}
#Override
public void setDisplayedChild(int whichChild)
{
this.getOnItemSelectedListener().onItemSelected(this, null, whichChild, -1);
super.setDisplayedChild(whichChild);
}
}
Please note that this solution only gives the index of the selected view to the listener and view (second parameter on onItemSelected) is null!
Using this.getCurrentView() instead of null unfortunately doesn't work because it returns a sub class of StackView. Maybe someone finds a solution to that.
What i have done is writing a new class extending StackView and writing some code to get the OnItemSelected logics works. When the onTouchEvent gives me a MotionEvent.getAction() == ACTION_UP, i start a Thread that calls himself 'till the StackView.getDisplayedChild() changes. When it changes, i start the OnItemSelected logic, so i can always get the first displayed child.
public boolean onTouchEvent(MotionEvent motionEvent) {
if (motionEvent.getAction() == MotionEvent.ACTION_UP && this.getAdapter() != null) {
mPreviousSelection = this.getDisplayedChild();
post(mSelectingThread);
}
return super.onTouchEvent(motionEvent);
}
This thread cycles himself untill he gets the new displayedChild:
private class SelectingThread implements Runnable {
CustomStackView mStackView;
public SelectingThread(CustomStackView stackView) {
this.mStackView = stackView;
}
#Override
public void run() {
if(mStackView.getAdapter() != null) {
if (mPreviousSelection == CustomStackView.this.getDisplayedChild()) {
mThisOnItemSelectedListener.onItemSelected(mStackView, mStackView.getAdapter().getView(mPreviousSelection, null, mStackView),
mStackView.mPreviousSelection, mStackView.getAdapter().getItemId(mPreviousSelection));
return;
} else {
mPreviousSelection = mStackView.getDisplayedChild();
mStackView.post(this);
}
}
}
}
This Listener instead sets the Selected flag to true after deselecting them all.
private class StackViewOnItemSelectedListener implements OnItemSelectedListener {
CustomStackView mStackView;
public StackViewOnItemSelectedListener(CustomStackView stackView) {
this.mStackView = stackView;
}
#Override
public void onItemSelected(AdapterView<?> parent, View selectedView, int position, long id) {
deselectAll();
if (mStackView.getAdapter() != null) {
if (mOnItemSelectedListener != null) {
mStackView.mOnItemSelectedListener.onItemSelected(parent, selectedView, position, id);
}
mStackView.getAdapter().getView(position, null, mStackView).setSelected(true);
}
}
private void deselectAll() {
if (mStackView.getAdapter() != null) {
int adapterSize = mStackView.getAdapter().getCount();
for (int i = 0; i < adapterSize; i++) {
mStackView.getAdapter().getView(i, null, mStackView).setSelected(false);
}
}
}
#Override
public void onNothingSelected(AdapterView<?> parent) {
if (mStackView.getAdapter() != null) {
if (mOnItemSelectedListener != null) {
mStackView.mOnItemSelectedListener.onNothingSelected(parent);
}
deselectAll();
}
}
}
I've tested it a little and it works..
Related
I want to detect when users are pulling down for refresh
(refresh line in under ActionBar is starting expand to its UI width).
I want to replace ActionBar with message ("Swipe down to Refresh") but,
I don't know which event should I use to call my function.
You can extend SwipeRefreshLayout, override onTouchEvent, and make your change on the ACTION_DOWN event, ie:
public class MySwipeRefreshLayout extends SwipeRefreshLayout {
private Runnable onActionDown, onActionUp;
public MySwipeRefreshLayout(Context context, AttributeSet attributeSet) {
super(context,attributeSet);
}
public void setOnActionDown(Runnable onActionDown) {
this.onActionDown = onActionDown;
}
public void setOnActionUp(Runnable onActionUp) {
this.onActionUp = onActionUp;
}
#Override
public boolean onTouchEvent (MotionEvent ev) {
if (ev.getAction() == ev.ACTION_DOWN) {
if (onActionDown != null) {
onActionDown.run();
}
} else if (ev.getAction() == ev.ACTION_UP) {
if (onActionUp != null) {
onActionUp.run();
}
}
return super.onTouchEvent(ev);
}
:
:
}
Make sure you use the extended class in your layout. Then, in your view, you can pass a Runnable to setOnActionDown to update the actionbar or whatever else you want....
I tried to add Scroll listener in StaggeredGridView
there i can't figure out a way to add that there is no implementations for that i can see
Github url
https://github.com/maurycyw/StaggeredGridView
Thanks
Here's how I tried to tackle the problem
First ,add a private instance :
private AbsListView.OnScrollListener mOnScrollListener;
Add public setOnClickListener method :
public void setOnScrollListener (AbsListView.OnScrollListener l) {
mOnScrollListener = l;
}
Then, in trackMotionScroll , add lines to invoke the listener:
private boolean trackMotionScroll(int deltaY, boolean allowOverScroll) {
.
.
.
/* HERE we call onScroll */
if (mOnScrollListener != null) {
mOnScrollListener.onScroll(null, getFirstPosition(), getChildCount(), this.mItemCount);
}
return deltaY == 0 || movedBy != 0;
}
You can also implement your own onScrollStateChanged (AbsListView view, int scrollState)method, but I am too lazy to do so :P
Finally you can call gridView.setOnScrollListener(listener) to pass in a listener to StaggeredGridView
Hope it helps.
Create a class that inherits SwipeRefreshLayout and override canChildScrollUp() method to check if StaggeredGridView reached the top or not, if it reached the top return true else return false.
public class SwipeDownToRefrsh extends SwipeRefreshLayout{
PullToRefreshStaggeredGridView pullToRefreshStaggeredGridView;
public SwipeDownToRefrsh(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
}
public void canChildScrollUp (PullToRefreshStaggeredGridView pullToRefreshStaggeredGridView) {
this.pullToRefreshStaggeredGridView = pullToRefreshStaggeredGridView;
}
#Override
public boolean canChildScrollUp() {
// TODO Auto-generated method stub
if (pullToRefreshStaggeredGridView == null) {
return true;
} else {
return !pullToRefreshStaggeredGridView.getRefreshableView().mGetToTop;
}
}
}
In your Activity or Fragment just send the instant of your StaggeredGridView like this :
((SwipeDownToRefrsh)holder.swipeRefreshLayout).canChildScrollUp(holder.staggeredGridView);
I encountered the same problem, and that's why I added the support for scroll listener myself you can find the project on github here: https://github.com/GoMino/StaggeredGridView
I have a standard Android Gallery control:
<Gallery
android:id="#+id/galArt"
android:spacing="10dp"
android:fadingEdgeLength="0dp"
android:unselectedAlpha="1"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
From which I listen to events with this code:
galArt.setOnItemSelectedListener(new OnItemSelectedListener() {
public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
showMessageToast("Selected: " + pos);
}
public void onNothingSelected(AdapterView<?> arg0) {}
});
This works as I guess it is intended to: when I swipe the image, I get a toast telling me which image is now selected.
However, this toast appears before the image has stopped sliding, while the animation is still running. I want to take action after it has stopped sliding, to avoid interrupting the animation.
What can I listen to in order to get the notification after the animation is done?
Alright, I have found a solution. Note that this is merely a solution, and not necessarily the best solution.
My solution is to just ignore all the logical events (like onScroll or onAnimationEnd, since I couldn't get any of them to work anyway), and listen to changes in a child view's location. When the child view is stationary, the animation has ended.
An actual benefit of doing it this way is that this works for both dragging and flinging.
A problem is that the onItemSelected function will be called from another thread than your UI thread. Solve that by using your activity's runOnUIThread function, as shown in the example.
The way to listen for changes (note that this is not the regular onItemSelected function, but rather my own onItemReallySelected):
galArt.setOnItemReallySelectedListener(new OnItemSelectedListener() {
public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
_activity.runOnUiThread(new Runnable() {
public void run() {
//Do your stuff here ...
}
});
}
public void onNothingSelected(AdapterView<?> arg0) {
//... or here.
}
});
My implementation of the Android Gallery:
import java.util.Timer;
import java.util.TimerTask;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.Gallery;
public class ArtGallery extends Gallery {
OnItemSelectedListener _listener;
Timer _timer = new Timer();
public ArtGallery(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public ArtGallery(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ArtGallery(Context context) {
super(context);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if(event.getAction()==MotionEvent.ACTION_UP){
setTimer();
}
return super.onTouchEvent(event);
}
private int _lastScrollX = Integer.MIN_VALUE;
private void setTimer() {
//Cancel existing tasks (if any), and create a new timer.
_timer.cancel();
_timer = new Timer();
//Schedule our animation check.
_timer.schedule(new TimerTask() {
#Override
public void run() {
//Just some value that will change while the animation is running.
int x = getSelectedView().getLeft();
if(_lastScrollX != x){
//Value has changed; save current value, and reset the timer.
_lastScrollX = x;
setTimer();
}else{
//The value hasn't changed during the last 50ms. That probably means that the animation has stopped.
fireOnSelected();
}
}
}, 50);
}
public void setOnItemReallySelectedListener(OnItemSelectedListener listener){
_listener = listener;
}
//This function is copied from the Android Gallery source code, and works exactly like the original one.
private void fireOnSelected() {
if (_listener == null)
return;
int selection = this.getSelectedItemPosition();
if (selection >= 0) {
_listener.onItemSelected(this, getSelectedView(), selection, getAdapter().getItemId(selection));
} else {
_listener.onNothingSelected(this);
}
}
}
I am trying to find a form validation library in android. Is there such a thing ?
I have a registration form that I want to validate its fields. If the user enters an invalid data, I want to put a red warning mark at the right of the field and pop up a tooltip that he entered an invalid data.
I know about the android:inputType but this is not what I want
I don't know about any such libraries. But if you are working with EditTexts, then your best option is to use a custom TextWatcher:
class TextCheck implements TextWatcher
{
private EditText editor;
public TextCheck(EditText editor)
{
this.editor = editor;
}
#Override
public void onTextChanged(CharSequence arg0, int arg1, int arg2, int arg3)
{
// check the text, and if the user entered
// something wrong, change your edittext
if(something wrong)
{
editor.setBackgroundColor(Color.RED); //for example
}
}
#Override
public void afterTextChanged(Editable arg0){}
#Override
public void beforeTextChanged(CharSequence arg0, int arg1, int arg2, int arg3){}
}
And then you can use it on all your EditTexts like
EditText editor = (EditText) findViewById(...your id...);
editor.addTextChangedListener(new TextCheck(editor));
Is there such a thing?
Ah.. yes there is one and you can find it here.
It does form validation for you, using but not limited to Annotations. To find out what the library does please visit the following answer on SO where I have described the usage of the library.
If you want to write new rules you can always extend the Rule class.
PS: I am the author of the library.
I know this is old, but you can try this excellent Android Validation library and visit this Stackoverflow reference and this Stackoverflow reference for usage examples because I found the main librarys how-tos kinda of hard to understand.
I did something similar. You can improve this code and adapt for your necessity.
EditTextWithValidation.java
public class EditTextWithValidation extends EditText implements OnTouchListener {
private EditTextValidator mValidator;
public EditTextWithValidation(Context context) {
super(context);
initialize();
}
public EditTextWithValidation(Context context, AttributeSet attrs) {
super(context, attrs);
initialize();
}
public EditTextWithValidation(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initialize();
}
public EditTextValidator getCustomValidator() {
return mValidator;
}
#Override
public boolean onTouch(View v, MotionEvent event) {
setError(null);
return false;
}
private void initialize() {
mValidator = new EditTextValidator(this);
setOnTouchListener(this);
}
}
EditTextValidator.java
public class EditTextValidator {
private static final String TAG = EditTextValidator.class.getName();
private enum ValidationResult {
Ok, Rules, Mismatch
}
private EditText mParent;
private Pattern mValidationPattern;
private int mValidationErrorMsgId;
private boolean mAllowEmpty;
private EditText mMatchView;
private int mMismatchMsgId;
private int mMinLength;
private int mMaxLength;
private ValidationResult mValidationResult;
public EditTextValidator(EditText parent) {
this.mParent = parent;
}
public void setAllowEmpty(boolean allowEmpty) {
this.mAllowEmpty = allowEmpty;
}
public void setValidationErrorMsgId(int validationErrorMsgId) {
this.mValidationErrorMsgId = validationErrorMsgId;
}
public void setValidationRules(String strPattern, int validationErrorMsgId, boolean allowEmpty) {
try {
if (!TextUtils.isEmpty(strPattern)) {
mValidationPattern = Pattern.compile(strPattern);
}
} catch (PatternSyntaxException e) {
Log.e(TAG, e.getMessage(), e);
ToastUtil.toastShort("Invalid validation pattern!");
}
this.mValidationErrorMsgId = validationErrorMsgId;
this.mAllowEmpty = allowEmpty;
}
public void setValidLength(int min, int max) {
mMinLength = min;
mMaxLength = max;
}
public void shouldMatch(EditText matchView, int mismatchMsgId) {
this.mMatchView = matchView;
this.mMismatchMsgId = mismatchMsgId;
}
public boolean validate() {
mValidationResult = ValidationResult.Ok;
InputMethodManager imm = (InputMethodManager) mParent.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(mParent.getWindowToken(), 0);
final String text = mParent.getText().toString();
final int length = text.length();
if (mValidationResult == ValidationResult.Ok && !mAllowEmpty && 0 == text.length()) {
mValidationResult = ValidationResult.Rules;
}
if (mValidationResult == ValidationResult.Ok) {
if (mMinLength != 0 && length < mMinLength) {
mValidationResult = ValidationResult.Rules;
} else if (mMaxLength != 0 && length > mMaxLength) {
mValidationResult = ValidationResult.Rules;
}
}
if (mValidationResult == ValidationResult.Ok && mValidationPattern != null) {
Matcher m = mValidationPattern.matcher(text);
if (!m.matches())
mValidationResult = ValidationResult.Rules;
}
if (mValidationResult == ValidationResult.Ok && mMatchView != null) {
if (mMatchView.getText().toString().compareTo(text) != 0)
mValidationResult = ValidationResult.Mismatch;
}
if (ValidationResult.Ok == mValidationResult) {
mParent.setError(null);
} else {
CharSequence error = null;
if (ValidationResult.Rules == mValidationResult)
error = MyApplication.getContext().getText(mValidationErrorMsgId);
else if (ValidationResult.Mismatch == mValidationResult)
error = MyApplication.getContext().getText(mMismatchMsgId);
mParent.setError(error);
mParent.requestFocus();
}
return mValidationResult == ValidationResult.Ok;
}
}
Usage:
mSignupEmail = (EditTextWithValidation) root.findViewById(R.id.signup_email);
mSignupEmail.getCustomValidator().setValidationRules(
"[a-zA-Z0-9_-]+(?:\\.[a-zA-Z0-9_-]+)*#[a-zA-Z0-9-]+(?:\\.[a-zA-Z0-9_-]+)*\\.(?:[a-zA-Z]{2,})",
R.string.email_answer_validation_msg,
false);
mSignupEmail.getCustomValidator().setValidLength(0, 50);
…
mSignupPassword = (EditTextWithValidation) root.findViewById(R.id.signup_password);
mSignupPassword.getCustomValidator().setValidationRules(
"[a-zA-Z0-9!##$%^&*()]{6,20}",
R.string.password_validation_msg,
false);
…
mSignupConfirmPassword = (EditTextWithValidation) root.findViewById(R.id.signup_confirm_password);
mSignupConfirmPassword.getCustomValidator().setAllowEmpty(true);
mSignupConfirmPassword.getCustomValidator().shouldMatch(mSignupPassword, R.string.password_mismatch);
mSignupConfirmPassword.getCustomValidator().setValidationErrorMsgId(R.string.password_validation_msg);
…
if (mSignupEmail.getCustomValidator().validate() && mSignupPassword.getCustomValidator().validate() && mSignupConfirmPassword.getCustomValidator().validate()) {
// DO SOMETHING
}
Install this app: https://play.google.com/store/apps/details?id=com.desarrollodroide.repos
Go to: Utils -> Android-Validator -> View Demo
There are a bunch of other cool libraries in this app as well. The good thing is that you can view demo of each library and get the link to github repo of that particular library if you like it. It's very useful.
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();
}
});
How to implement method isNotSetByUser()?
Answer 2:
A very simple answer:
Use on OnClickListener instead of 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);
}
});
Now you only pick up click events and don't have to worry about programmatic changes.
Answer 1:
I have created a wrapper class (see Decorator Pattern) which handles this problem in an encapsulated way:
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 can still be declared the same in XML, but use this when initializing your GUI in code:
BetterCheckBox myCheckBox;
// later...
myCheckBox = new BetterCheckBox(context,
(CheckBox) view.findViewById(R.id.my_check_box));
If you want to set checked from code without triggering the listener, call myCheckBox.silentlySetChecked(someBoolean) instead of setChecked.
Maybe You can check isShown()? If TRUE - than it's user. Works for me.
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();
}
}
});
Inside the onCheckedChanged() just check whether the user has actually checked/unchecked the radio button and then do the stuff accordingly as follows:
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.
}
}
});
You can remove the listener before changing it programatically and add it again, as answered in the following SO post:
https://stackoverflow.com/a/14147300/1666070
theCheck.setOnCheckedChangeListener(null);
theCheck.setChecked(false);
theCheck.setOnCheckedChangeListener(toggleButtonChangeListener);
Try extending CheckBox. Something like that (not complete example):
public MyCheckBox extends CheckBox {
private Boolean isCheckedProgramatically = false;
public void setChecked(Boolean checked) {
isCheckedProgramatically = true;
super.setChecked(checked);
}
public Boolean isNotSetByUser() {
return isCheckedProgramatically;
}
}
Try NinjaSwitch:
Just call setChecked(boolean, true) to change the switch's checked state without detected!
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);
}
}
}
There is another simple solution that works pretty well. Example is for 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;
}
}
This should be enough :
SwitchCompact.setOnCheckedChangeListener((buttonView, isChecked) -> {
if (buttonView.isPressed()) {
if (!isChecked) {
//do something
} else {
// do something else
}
}
});
Interesting question. To my knowledge, once you're in the listener, you can't detect what action has triggered the listener, the context is not enough. Unless you use an external boolean value as an indicator.
When you check the box "programmatically", set a boolean value before to indicate it was done programmatically. Something like:
private boolean boxWasCheckedProgrammatically = false;
....
// Programmatic change:
boxWasCheckedProgrammatically = true;
checkBoxe.setChecked(true)
And in your listener, don't forget to reset the state of the checkbox:
#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;
}
If OnClickListener is already set and shouldn't be overwritten, use !buttonView.isPressed() as isNotSetByUser().
Otherwise the best variant is to use OnClickListener instead of OnCheckedChangeListener.
The accepted answer could be simplified a bit to not maintain a reference to the original checkbox. This makes it so we can use the SilentSwitchCompat (or SilentCheckboxCompat if you prefer) directly in the XML. I also made it so you can set the OnCheckedChangeListener to null if you desire to do so.
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);
}
}
You can then use this directly in your XML like so (Note: you will need the whole package name):
<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"/>
Then you can check the box silently by calling:
SilentCheckBox mySilentCheckBox = (SilentCheckBox) findViewById(R.id.my_check_box)
mySilentCheckBox.silentlySetChecked(someBoolean)
Here is my implementation
Java Code for Custom Switch :
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);
}}
Equivalent Kotlin Code :
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)
}}
To change switch state without triggering listener use :
swSelection.setCheckedSilently(contact.isSelected)
You can monitor state change as normally by :
swSelection.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
// Do something
}
});
In Kotlin :
swSelection.setOnCheckedChangeListener{buttonView, isChecked -> run {
contact.isSelected = isChecked
}}
My variant with Kotlin extension functions:
fun CheckBox.setCheckedSilently(isChecked: Boolean, onCheckedChangeListener: CompoundButton.OnCheckedChangeListener) {
if (isChecked == this.isChecked) return
this.setOnCheckedChangeListener(null)
this.isChecked = isChecked
this.setOnCheckedChangeListener(onCheckedChangeListener)
}
...unfortunately we need to pass onCheckedChangeListener every time because CheckBox class has not getter for mOnCheckedChangeListener field((
Usage:
checkbox.setCheckedSilently(true, myCheckboxListener)
Create a variable
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;
}
In the application when you change it instead of the user, call notSetByUser(true) so it is not set by the user, else call notSetByUser(false) i.e. it is set by program.
Lastly, in your event listener, after calling isNotSetByUser(), make sure you again change it back to normal.
Call this method whenever you are handling that action either thru user or programmatically. Call the notSetByUser() with appropriate value.
If the view's tag isn't used, you can use it instead of extending the checkbox:
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
}
each time you call something that checks/unchecks the checkbox, also call this before checking/unchecking :
checkbox.setTag(true);
I have created extension with RxJava's PublishSubject, simple one. Reacts only on "OnClick" events.
/**
* 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
}