When clicking on a Checkbox, a default Android material design animation is triggered (from blank to a "V" mark, and from "V" mark to a blank).
I want to identify when the animation ends.
According to documentation, this should be possible in one of two ways -
When the checkbox is checked or unchecked (setOnCheckedChangeListener()), obtain the current Animation object (getAnimation()) and register a listener on it (setAnimationListener()). Unfortunately, this doesn't work - the Animation object, at this point in time, is null.
Subclass the Checkbox object and implement its onAnimationEnd() method. Unfortunately, this doesn't work as well - the method is never called.
What am I missing? What is a good way to identify when such a default animation ends? I assume the relevant events can be registered on some other view in the activity, but I can't figure out which.
Here is a relevant code snippet for the first approach (animation is always null) -
CheckBox checkBox = (CheckBox)findViewById(R.id.checkbox1);
checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
Animation animation = compoundButton.getAnimation();
Log.d("Checkbox", "Animation is " + animation);
}
});
I was somewhat successful setting the following OnCheckedChangeListener on the Checkbox.
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
Drawable drawable = buttonView.getButtonDrawable().getCurrent();
if (drawable instanceof AnimatedVectorDrawable) {
AnimatedVectorDrawable animatedDrawable = (AnimatedVectorDrawable) drawable;
animatedDrawable.registerAnimationCallback(new Animatable2.AnimationCallback() {
#Override
public void onAnimationEnd(Drawable drawable) {
super.onAnimationEnd(drawable);
// your action goes here
}
});
} else {
// and maybe here as well as a fallback
}
}
Quite often (maybe 50% of the time) the onAnimationEnd callback is triggered twice. I don’t understand why, though.
Related
I am working on a data collection application that while in an active state locks the screen to prevent errant user interaction. I would like to "lock" the checkbox while in this state to prevent the user from checking or unchecking the box. While other buttons on the screen still "click" when in this state, (listener is active) their events are not executed when the boolean bLockedScreen == true. I'd like for my boolean flag (bLockedScreen) when true to disable the check box listener.
What is the best way to go about doing this? TIA
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
...
ckbxVerbose = (CheckBox) v.findViewById(R.id.ckbxVerbose);
return v;
}
public void verbose(){
if(ckbxVerbose.isChecked()){
//do something
}
Update: Resolved this by putting,
ckbxVerbose.setEnabled(false);
in the method that set the boolean flag to disable screen widgets. Will likely do this as well with the other buttons that are inactive while data is being collected. Thank you Prince Ali for your answer.
You can disable or enable the CheckBox by calling setEnabled and setting its value to false or true. Here is a minimal working example involving 2 CheckBoxes, if the first CheckBox is checked, then disable the second one, otherwise enable it:
ckbxVerbose.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton buttonView,boolean isChecked) {
if ( isChecked ) {
ckbxVerbose2.setEnabled(false);
} else {
ckbxVerbose2.setEnabled(true);
}
}
}
);
Another example can be:
CharSequence str = txt.getText();
if ( str.equals("Checked") ) {
ckbxVerbose2.setEnabled(false);
} else {
ckbxVerbose2.setEnabled(true);
}
So I'm working on a list of check boxes. I'm using the OnClickListener to catch the touch event. I've also tried the OnTouch listener and the OnCheckChanged listener. The issue I've come across is the fact that the check box IsChecked value is set to true before it reaches any of these event listeners. So if I were to do something like this:
checkBox.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (checkBox.isChecked())
checkBox.setChecked(false);
}
});
It will always hit this if statement and immediately set the checkbox to unchecked because it's setting it as ischecked true before it reaches OnClickListener. The easiest way around this I've found is:
checkBox.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
checkBox.setChecked(!checkBox.isChecked());
if (checkBox.isChecked()) {
checkBox.setChecked(false);
}
}
});
But this is what I would call hackish. The other option I see being to create a new checkbox class and override the preformClick method.
#Override
public boolean performClick() {
toggle();
final boolean handled = super.performClick();
if (!handled) {
// View only makes a sound effect if the onClickListener was
// called, so we'll need to make one here instead.
playSoundEffect(SoundEffectConstants.CLICK);
}
return handled;
}
But that seems like a lot more work than should need to go in to accomplishing this task.
So, essentially my question is: Is there a method where I can override and intercept the setting of the checkbox before it is actually changed?
The OnClickListener of the CheckBox always gets called after it changed its checked state (as you can see in the performClick() method you copied, the toggle() method changes the checked state, before calling super's performClick(), which will call the OnClickListener).
If you don't want to always change the checked state of the CheckBox, then you can override performClick() and leave out the toggle() method (or only call it if some condition is true).
But if you want to change the state every time a click happens, then the easiest way is to use the OnClickListener, and just negate your conditions (e.g. if you want to do something when the checkbox was empty, then you check if the checkbox's new state is not empty).
I want my fragment to not receive any clicks on the views while the fragment transition animation is not yet finished. It is just a simple fade. But things get wonky when I immediately press any view while the next fragment is fading in.
Any thoughts how to achieve this?
This is actually used in my own app. The idea is very simple, it just works, but needs quite a lot of additional coding.
The idea is very simple, use a boolean variable to maintain whether the screen should be locked, let's call it screenLocked. I do not actually block the click, but let the click do nothing.
For those actions which takes time, set screenLocked to true before start working, and set it back to false when the task is finished. Also you have to add checking on screenLocked before any action is done.
Another difficulty of the this method is that you need to have clear end point of your tasks. Using Fragment transition as an example, if the backstack is poped, there has no actual callback notifying you, for this case. To handle this, I would set another flag releaseOnResume before starting Fragment transition, and in onResume, I would use this flag to check if I should set screenLocked back to false.
Other solutions I have tried but not used:
Before I settled with the method I just mentioned, I have tried setEnabled, setClickable, or any UI based blocking, e.g. add a FrameLayout on top and capture all touch events.
These methods are not bad, especially given that they are easy to implement.
The only problem is that, onClick events can be queued due to double tapping, when you are handling the first onClick event, actually there could be another one queued up, even if you do any UI changes immediately to block any further clicks, you can't stop the next onClick event to come because it is queued already.
Hope this helps.
I use a countdown timer.
I manage this through the ontouch listener.
I create a method that manages the creation of the timer. I call it in the ontouch event. I use two methods (this is optional, but good for extensibility) to handle button enabling and disabling. I then use these methods with the timer to enable and disable the button.
See my code snippet.
In oncreate:
#Override protected void onCreate(Bundle savedInstanceState) {
/.../
button.setOnTouchListener(new View.OnTouchListener() {
#Override public boolean onTouch(View v, MotionEvent event) {
disableButton(button);
countDwn1();
/... time to do whatever you need..
// custom methods...
fragment = new MyFragAddFragment();
replaceFragment(fragment);
return false;
}
});
Methods:
public void countDwn1() {
CountDownTimer countDownTimer = new CountDownTimer(2000, 1000) {
public void onTick(long millisUntilFinished) {
}
public void onFinish() {
enableButton(button);
}
}.start();
}
public void disableButton(Button button) {
button.setEnabled(false);
}
public void enableButton(Button button) {
button.setEnabled(true);
}
You can extend this method to include passing the button as a parameter into the timer, for extensibility.
In the end I used something like this. I created a parent class for all my fragments and overriden the OnCreateAnimation method which is called on every animation.
#Override
public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) {
//Check if the superclass already created the animation
Animation anim = super.onCreateAnimation(transit, enter, nextAnim);
//If not, and an animation is defined, load it now
if (anim == null && nextAnim != 0) {
anim = AnimationUtils.loadAnimation(getActivity(), nextAnim);
}
//If there is an animation for this fragment, add a listener.
if (anim != null) {
anim.setAnimationListener(new Animation.AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {
isAnimationFinished = false;
}
#Override
public void onAnimationEnd(Animation animation) {
isAnimationFinished = true;
}
#Override
public void onAnimationRepeat(Animation animation) {
}
});
}
return anim;
}
The isAnimationFinished variable is a public variable that can be used by the calling activity and the child classes
I used a very similar piece of code as below to setOnClickListener on a button and the view was passed through.
((Switch) convertView.findViewById(R.id.push_switch)).setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
if(rowItem.getPush().equals("N")){
((Switch) convertView.findViewById(R.id.push_switch)).toggle();
rowItem.setPush("Y");
}
else if(rowItem.getPush().equals("Y")){
((Switch) convertView.findViewById(R.id.push_switch)).toggle();
rowItem.setPush("N");
}
}
});
Within the code above, there view isn't passed through so how can I access it to change where I have put convertView.
A side query that isn't crucial to the question is why the first line of this was cast to (Switch) when that didn't happen on a button. Just intrigued on that one!
Thanks :)
As #Mike M commented, compoundButton is the Switch. The callback method onCheckedChanged is passing you a reference to the CompoundButton which triggered the callback...in this case, your Switch.
You can do this:
compoundButton.toggle();
I have this code here
ToggleButton toggleAlarm = (ToggleButton) d.findViewById(R.id.toggle_alarm);
toggleAlarm.setOnCheckedChangeListener(new OnCheckedChangeListener() {
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if(isChecked)
{
Log.d("alarmCheck","ALARM SET TO TRUE");
sched.setAlarm(true);
}
else
{
Log.d("alarmCheck","ALARM SET TO FALSE");
sched.setAlarm(false);
}
}
});
I have to keep track if its ON or OFF. But when I logged something to logcat where it is on or off, it won't do a thing. I don't know, what is wrong, because on my other code same, syntax but it works I just copy paste it and change only the ToggleButton variable.
EDIT
I have observed, with the help of cdr. Powell of course, that when you put this code block, the one that I have posted, inside another anonymous listener, say listener for a save button, the checkOnChangedListener is broken, it doesn't function well inside another anonymous listener, but the one thing that I don't understand is that, there is also a outer listener in my code, it is like a button to display a dialog box and inside that dialog box, there is an add button that opens another dialog box which has that toggle button and another button for save or add which closes that dialog and returns to the previous dialog which will then display the newly added record, so anyone of you have an idea why is it broken when i put it inside a listener for a save button but works fine in a outer listener.
try this, May be the problem is with import
toggleAlarm.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if(isChecked)
{
Log.d("alarmCheck","ALARM SET TO TRUE");
sched.setAlarm(true);
}
else
{
Log.d("alarmCheck","ALARM SET TO FALSE");
sched.setAlarm(false);
}
}
});
Try toggleAlarm.isChecked() too see if the button is checked or not.
In case toggleAlarm.isChecked() does not work for you you could always.
boolean _isChecked = false;
((ToggleButton) d.findViewById(R.id.toggle_alarm)).setOnClickListener(new OnOnClickListener() {
public void onClick(View arg0) {
_isChecked = !isChecked;
if(_isChecked()) {
Log.d("alarmCheck","ALARM SET TO TRUE");
sched.setAlarm(true);
}
else {
Log.d("alarmCheck","ALARM SET TO FALSE");
sched.setAlarm(false);
}
}
});
So i have observed, with the help of cdr. powell of course, that when u put this code block, the one that i have posted, inside another anonymous listener, say listener for a save button, the checkOnChangedListener is broken, it doesn't function well inside another anonymous listener, but the one thing that i don't understand is that, there is also a outer listener in my code, its like a button to display a dialog box and inside that dialog box, there is an add button that opens another dialog box which has that toggle button and another button for save or add which closes that dialog and returns to the previous dialog which will then display the newly added record, so anyone of you have an idea why is it broken when i put it inside a listener for a save button but works fine in a outer listener.