As Writing Custom View for Android said:
Remove any posted Runnable in onDetachedFromWindow
I have a custom view, CircleCheckBox. When it's clicked animation will be executed.
The animation is implemented by View#postInvalidate().
So is there a way to remove any posted runnable in onDetachedFromWindow()?
EDIT
Let me tell you how CircleCheckBox works.
Step 1
In CircleCheckBox constructor I set View#OnClickListener for it:
this.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
toggle();
if (mListener != null) {
mListener.onCheckedChanged(isChecked());
}
}
});
Step 2
In method toggle() it will invoke:
#Override
public void setChecked(boolean checked) {
....
if (checked) {
startCheckedAnimation();
} else {
startUnCheckedAnimation();
}
}
Step 3
Let's say startCheckedAnimation().
private void startCheckedAnimation() {
// circle animation
ValueAnimator circleAnimator = ValueAnimator.ofFloat(0f, 1f);
circleAnimator.setInterpolator(new DecelerateInterpolator());
circleAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
float value = (float) animation.getAnimatedValue();
mArcPath.reset();
mArcPath.addArc(mRectF, -159, 360 * (1 - value));
postInvalidate();
}
});
circleAnimator.setDuration(mDuration / 4);
circleAnimator.start();
}
I use postInvalidate() method to let view invoke onDraw().
View#clearAnimation() will cancel all animations that have been applied to the View.
In your case you are using a ValueAnimator, which means that you have to keep a reference to it and when you need to cancel the animation you should perform ValueAnimator#cancel():
private ValueAnimator circleAnimator;
...
circleAnimator = ValueAnimator.ofFloat(0f, 1f);
// setup and start `ValueAnimator`
Then, when needed to cancel the animation:
circleAnimator.cancel();
I think you can just use removeCallbacks(Runnable r)method.
void removeCallbacks (Runnable r)
Remove any pending posts of Runnable r that are in the message queue.
Related
I have two instances of ValueAnimator that are started inside onClickListener - one moves the view up, another moves the same view down. Each animation has a startListener, so that when one starts, the second one is called cancel(). Ie if the animation, say up, interrupt in the middle, - cause the animation to go down, then cancel() should work, and it works, but if i set startOffset on both animations, cancel() in startListener works many times (as in a loop), and then i have a stackOverFlowError.
I noticed that the same error will be without startOffset, but if i call both animation starts at the same view without calling cancel() at the first animation. But cancel() is called inside startListener(!). And startListener does not works when the animation starts native with anim.start(), only after the end of the startOffset's delay.
Code:
Float firstLevelUp = 200f;
Float firstLevelDown = 0f;
long duration = 1500l;
ValueAnimator valueAnimatorView1Down = ValueAnimator.ofFloat(firstLevelUp,firstLevelDown);
ValueAnimator valueAnimatorView1Up = ValueAnimator.ofFloat(firstLevelUp,firstLevelDown);
public void initAnim(final View view){
valueAnimatorView1Down.setDuration(duration);
valueAnimatorView1Up.setDuration(duration);
valueAnimatorView1Down.addListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationStart(Animator animation) {
if (valueAnimatorView1Up.isStarted()) {
valueAnimatorView1Up.cancel();
valueAnimatorView1Down.setCurrentPlayTime(duration - valueAnimatorView1Up.getCurrentPlayTime());
Log.i("Down View1 animation " , "STARTED WITH CANCEL");
}else {
Log.i("Down View1 animation " , "STARTED WITHOUT CANCEL");
}
}
#Override
public void onAnimationCancel(Animator animation) {
Log.i("DownView1 animation " , "CANCELED");
}
});
valueAnimatorView1Up.addListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationStart(Animator animation) {
if (valueAnimatorView1Down.isStarted()) {
valueAnimatorView1Down.cancel();
valueAnimatorView1Up.setCurrentPlayTime(duration - valueAnimatorView1Down.getCurrentPlayTime());
Log.i("Up View1 animation " , "STARTED WITH CANCEL");
}else {
Log.i("Up View1 animation " , "STARTED WITHOUT CANCEL");
}
}
#Override
public void onAnimationCancel(Animator animation) {
Log.i("Up View1 animation " , "CANCELED");
}
});
valueAnimatorView1Down.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
view.setTranslationY((Float) valueAnimatorView1Down.getAnimatedValue());
}
});
valueAnimatorView1Up.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
view.setTranslationY((Float) valueAnimatorView1Up.getAnimatedValue());
}
});
}
public void startViewAnimation(boolean side) {
if (side) {
Log.i("Down animations ", "start()");
valueAnimatorView1Down.setStartDelay(800);
valueAnimatorView1Down.start();
} else {
Log.i("Up animations ", "start()");
valueAnimatorView1Up.setStartDelay(800);
valueAnimatorView1Up.start();
}
}
}
Questions: why is the cancel() on the onStartListener invoked in loop + stackOverFlowError, if i change the animation to the second one, before the startOffset is over in the first (although if i call a new animation after the start delay - everything works correctly) and how to fix it?
i use this code for animate x and y position for view
the code
v.animate().x(margin).y(0).setDuration(200).start();
how can i call method when this animation finished
i have this code also i can by it call method when finished the animate
but i cant set x and y for View
ValueAnimator varl = ValueAnimator.ofInt(from,to);
varl.setDuration(200);
varl.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
}
});
varl.addListener(new AnimatorListenerAdapter()
{
#Override
public void onAnimationEnd(Animator animation)
{
// animate finish call method
}
});
varl.start();
i just thinking about make this code
v.animate().x(margin).y(0).setDuration(200).start();
inside the method onAnimationUpdate
i think its bad idea any another solution
Looks like the base class has a listener.
ObjectAnimator anim = ObjectAnimator.ofFloat(view, "alpha", 0.0f);
anim.addListener(new AnimatorListener() {
...
#Override
public void onAnimationEnd(Animator animation) {
// Do something.
}
...
});
anim.setDuration(300).start();
I use this method to show any view that need a fade in or a fade out Animation.
It works perfectly when I pass false to execute the fade out animation but when I pass true, the fade in doesn't execute, instead, It just appears after 500ms without animation;
private void setViewTransition(final boolean isVisible, final View view) {
Animation fadeAnim;
if (isVisible) {
fadeAnim = new AlphaAnimation(0.0f, 1.0f);
if (view.getAlpha() == 1.0f) {
return;
}
} else {
fadeAnim = new AlphaAnimation(1.0f, 0.0f);
if (view.getAlpha() == 0.0f) {
return;
}
}
fadeAnim.setDuration(500);
fadeAnim.setAnimationListener(new Animation.AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {
}
#Override
public void onAnimationEnd(Animation animation) {
if (isVisible) {
view.setAlpha(1.0f);
} else {
view.setAlpha(0.0f);
}
}
#Override
public void onAnimationRepeat(Animation animation) {
}
});
view.startAnimation(fadeAnim);
}
It might be related to that issue: Android fade in not working which never got any answers...
What am I doing wrong ?
Although I'm not entirely sure why your way doesn't work. It is a bit roundabout and is probably more complicated than it needs to be to get a fade animation. Here's a really concise way of doing it instead (for API 12+).
private void setViewTransition(boolean isVisible, View view) {
view.animate().alpha(isVisible ? 1.0f : 0.0f).setDuration(500);
}
So what i am trying to achieve is user would open to first page of the view pager, and the view pager would bounce to half of the second page and bounce back to the fist page indicating that there are more pages to scroll to. I was wondering on how i could implement this?
You can use fakeDragBy method to achieve this effect:
viewPager.beginFakeDrag();
viewPager.fakeDragBy(offset); //offset in pixels.
viewPager.endFakeDrag();
EDIT:
I have made method for this:
private int animFactor;
private ValueAnimator animator = new ValueAnimator();
private void animateViewPager(final ViewPager pager, final int offset, final int delay) {
if (!animator.isRunning()) {
animator.removeAllUpdateListeners();
animator.removeAllListeners();
//Set animation
animator.setIntValues(0, -offset);
animator.setDuration(delay);
animator.setRepeatCount(1);
animator.setRepeatMode(ValueAnimator.RESTART);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
public void onAnimationUpdate(ValueAnimator animation) {
Integer value = animFactor * (Integer) animation.getAnimatedValue();
if (!pager.isFakeDragging()) {
pager.beginFakeDrag();
}
pager.fakeDragBy(value);
}
});
animator.addListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationStart(Animator animation) {
animFactor = 1;
}
#Override
public void onAnimationEnd(Animator animation) {
pager.endFakeDrag();
}
#Override
public void onAnimationRepeat(Animator animation) {
animFactor = -1;
}
});
animator.start();
}
}
Example of usage:
animateViewPager(pager, 10, 1000);
Edit2: ValueAnimator is class for Api level 11. Also set pager adapter before calling this method.
Adding a note to #Yuraj's answer. Call the method in onWindowFocusChanged when hasFocus==true as follows to avoid indexOutOfBoundsException:
#Override
public void onWindowFocusChanged(boolean hasFocus)
{
super.onWindowFocusChanged(hasFocus);
if(hasFocus)
{
Handler handler = new Handler();
final Runnable r = new Runnable()
{
public void run()
{
if(mViewPager.getCurrentItem() == 0)
{
Context context = Activity_main.this;
String filename="Init";
SharedPreferences stats;
stats = context.getSharedPreferences(filename, 0);
int appOpen = stats.getInt("appOpen", 0);
if(appOpen <= 5)
{
animateViewPager(mViewPager, 10, 300);
appOpen += 1;
SharedPreferences.Editor editor = stats.edit();
editor.putInt("appOpen", appOpen);
editor.commit();
}
}
}
};
handler.postDelayed(r, WAIT_VIEWPAGER_NUDGE);
}
}
Thank you Yuvraj! It worked with a simple modification. If anybody is getting "Invalid index 0, size is 0" error, here's a simple fix for it. If you call animateViewPager() method in onCreate() you might get this error, "Invalid index 0, size is 0". I believe viewpager.beginFakeDrag(); is being called before viewPager items / childs are initialized. So, call animateViewPager() with a delay like so:
new Handler().postDelayed(() -> animateViewPager(viewPager, 10, 1000), 500);
500 is the delay in milisecond
Hi I am applying multiple animations to a view using AnimatorSet and ValueAnimator.
When the user touches the view, successive animations are applied to the view. I do this to have a "zooming effect" when the user is moving his fingers on the view.
The view is a custom gallery.
Here is the code for animation:
private void createAnim(final View v) {
animating = true;
if (D)
Log.i(TAG, "sarting animation");
try {
v.clearAnimation();
v.getAnimation().reset();
} catch (Exception e) {
}
set = new AnimatorSet();
set.addListener(new AnimatorListener() {
#Override
public void onAnimationStart(Animator animation) {
}
#Override
public void onAnimationRepeat(Animator animation) {
}
#Override
public void onAnimationEnd(Animator animation) {
if (D)
Log.i(TAG, "Animation ended");
animating = false;
}
#Override
public void onAnimationCancel(Animator animation) {
animating = false;
if (D)
Log.i(TAG, "Animation cancelled");
}
});
set.setDuration(100);
ValueAnimator v1 = ObjectAnimator.ofFloat(v, "scaleX", mScaleFactor);
ValueAnimator v2 = ObjectAnimator.ofFloat(v, "scaleY", mScaleFactor);
animationList.add(v1);
animationList.add(v2);
set.playTogether(v1, v2);
set.start()
}
I have an onTouchListener and on action move I successively call this method increasing or decreasing the mScaleFactor.
When the user releases the finger I would like the view to go back to previous state, I mean to erase all animations applied as if they were never applied to view. The problem is you can easily do that for one animation but for many like that I just can't find the right way. Here is what I have tried:
-Adding the animations to an ArrayList and doing animation.reverse() on each one of them with a delay of +100 added to each of them so the execute one after another but the end result just doesn't seem exactly the same as before animations.
v.clearAnimation(); only cancels the last animation
v.getAnimation().reset(); same only works for last/active animation.
Is there a method to just get the view back as it was before any animation has been started?
Thanks a lot
Reversing the effects of an AnimatorSet can be tricky simply because there is no "reverse" method. The easiest method in this case, would probably simply be another AnimatorSet that does the opposite of the original. The issue here would be is your AnimatorListener could be out of sync. If you do this way and you want to keep the boolean, then you'd have to implement it as somewhat of a semaphore.
public static class AnimatorTracker implements AnimatorListener{
int counter;
public AnimatorTracker() {
counter = 0;
}
public boolean isAnimating() {
return counter == 0;
}
#Override
public void onAnimationStart(Animator animation) {
counter++;
}
#Override
public void onAnimationRepeat(Animator animation) {
}
#Override
public void onAnimationEnd(Animator animation) {
counter--;
}
#Override
public void onAnimationCancel(Animator animation) {
// Canceling an animation invokes onAnimationEnd, so nothing needs to be done.
}
}
Then just make the sets the same way you just did:
AnimatorTracker tracker = new AnimatorTracker(); // or make it a global reference
AnimatorSet set = new AnimatorSet();
AnimatorSet reverseSet = new AnimatorSet();
ValueAnimator v1 = ObjectAnimator.ofFloat(v, "scaleX", mScaleFactor);
ValueAnimator v2 = ObjectAnimator.ofFloat(v, "scaleY", mScaleFactor);
ValueAnimator reverseV1 = ObjectAnimator.ofFloat(v, "scaleX", 1.0f);
ValueAnimator reverseV2 = ObjectAnimator.ofFloat(v, "scaleY", 1.0f);
set.addListener(tracker);
set.setDuration(100);
set.playTogether(v1, v2);
reverseSet.addListener(tracker);
reverseSet.setDuration(100);
reverseSet.playTogether(reverseV1, reverseV2);
Call set.start() when you want to animate forward. Call reverseSet.start() when you want to animate back.