According to the documentation http://developer.android.com/reference/android/animation/AnimatorSet.html#end() end() method should assign the end values for all animations inside the set. However ending does nothing if the set was not started. This is different from ValueAnimator i.e. which does not care and artifically starts the animation, fires onAnimationStart() event and then ends it which results in setting the end values properly.
Let me tell you why this is a problem. If you want to have the ability to update your View with and without animation (depending on the situation) you don't want to have two separate methods for it. Instead you could create an animation and start it normally or immediately set the end values. With ValueAnimator this works nicely. However AnimatorSet does not play ball and if you try to end it before calling start() it simply exits.
One workaround for this is to call start() and end() immediately after but it does not cover the situation with nested AnimatorSets like this:
AnimatorSet mainSet = new AnimatorSet();
AnimatorSet scaleSet = new AnimatorSet();
scaleSet.playTogether(
ObjectAnimator.ofFloat(view, "scaleX", 0.5f),
ObjectAnimator.ofFloat(view, "scaleY", 0.5f));
mainSet.playSequentially(
ObjectAnimator.ofFloat(view, "alpha", 0.5f),
scaleSet);
mainSet.start();
mainSet.end();
This causes the mainSet to call end() on all its children. But since AnimatorSet.end() is broken the scaleSet does not set ending values. I attempted to extend AnimatorSet class and fix this but soon found out it is marked final.
In my app I mostly use animations but have some few cases where animations are not needed. Does anyone have an idea how to achieve the proper behaviour?
You can do that on all child animations with:
for (Animator animation : mainSet.getChildAnimations())
{
animation.start();
animation.end();
}
I know this is an old question but a recursive approach can solve this:
void endAnimationsRecursively(final Animator mainAnimator){
if (mainAnimator instanceof AnimatorSet) {
for (final Animator animator : ((AnimatorSet)
mainAnimator).getChildAnimations()) {
endAnimationsRecursively(animator);
}
mainAnimator.end();
mainAnimator.cancel();
}
}
Related
Building this app, I have managed to use some animations
using them with View view.setAnimation() etc..
This is the code i have:
// animation Properties
Animation fadeIn = new AlphaAnimation(0, 1);
fadeIn.setInterpolator(new DecelerateInterpolator()); // add this
fadeIn.setDuration(5000);
AnimationSet animation1 = new AnimationSet(false); // change to false
//animation.addAnimation(fadeIn);
animation1.addAnimation(fadeIn);
animation1.setRepeatCount(1);
Animation fadeOut = new AlphaAnimation(1, 0);
fadeOut.setInterpolator(new AccelerateInterpolator()); // and this
//fadeOut.setStartOffset(fadeInDuration + timeBetween);
fadeOut.setDuration(5000);
AnimationSet animation = new AnimationSet(false); // change to false
//animation.addAnimation(fadeIn);
animation.addAnimation(fadeOut);
animation.setRepeatCount(1);
textViewTopBannerBizName.setAnimation(animation1);
textViewTopBannerBizCategory.setAnimation(animation1);
So all, I want is that textViewTopBannerBizName and textViewTopBannerBizCategory will fade into screen as i used animation1 for both of them.
However first time when i launched the app it worked perfect but when I relaunched it again it stopped working.
It makes me wonder... why...?
Please help,
Thank you for your time.
try this.(Tested)
textViewTopBannerBizName.startAnimation(animation1);
You can clear animation if cached before by calling clearAnimation() and then startAnimation or setAnimation.
Edited
setAnimation
Sets the next animation to play for this view.But view animation does not start yet.
startAnimation
If you want the animation to play immediately, use startAnimation. This method provides allows fine-grained control over the start time and invalidation, but you must make sure that
1) the animation has a start time set,
2) the view will be invalidated when the animation is supposed to start
After setting animation, Please do invalidate the view if it's not an Activity
imb6.setAnimation(MainActivity.blinkAnimation(mContext, true))
invalidate();
I wish this'll helps.
Been doing some animation inside of a row in RecyclerView (not the row itself. Imagine expanding text) and there are times when the animation leaks to other recycled views which should not have that animation in them.
Since I use property animation the scale action resizes the inner view and the leak can be seen in 2 aspects:
1) The animation will go on (This I could overcome with some guards)
2) The view has been resized and stopped in its tracks and so it will be reflected in the recycled view.
How can I reset the views to their original state? I have tried many approaches from posts but none solved it. The closest definition I got was in this unanswered post:
How to reset view to original state after using animators to animates its some properties?
Here is a sample of how I set up my animation in onBind (this one has an attempt to use onAnimationEnd which I found in one post but did not work)
ObjectAnimator scaleXUp = ObjectAnimator.ofFloat(mView, View.SCALE_X, 10f);
scaleXUp.setRepeatCount(ValueAnimator.INFINITE);
scaleXUp.setRepeatMode(ValueAnimator.REVERSE);
scaleXUp.setDuration(700);
ObjectAnimator scaleYUp = ObjectAnimator.ofFloat(mView, View.SCALE_Y, 10f);
scaleYUp.setRepeatCount(ValueAnimator.INFINITE);
scaleYUp.setRepeatMode(ValueAnimator.REVERSE);
scaleYUp.setDuration(700);
mTotalAnimation = new AnimatorSet();
mTotalAnimation.play(scaleXUp).with(scaleYUp);
mTotalAnimation.setInterpolator(new AccelerateDecelerateInterpolator());
mTotalAnimation.addListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
animation.removeListener(this);
animation.setDuration(0);
for(Animator va : ((AnimatorSet)animation).getChildAnimations()) {
((ValueAnimator)va).reverse();
}
}
});
mTotalAnimation.start();
And here is what I do in the onUnbindData:
if (mTotalAnimation != null) {
mTotalAnimation.end();
mTotalAnimation = null;
}
And as I saw many people like the clearAnimation approach - tried and did not work either.
4 days and not one response but meanwhile I solved it myself.
So the approach was close, just wrongly placed.
I added this method:
private void stopAnimation() {
for (Animator anim : mTotalAnimation.getChildAnimations()) {
((ObjectAnimator) anim).reverse();
anim.end();
}
}
and I call that when I want to reset the view.
Here I am getting the animations from the AnimatorSet and reversing and ending each. I did not understand why I had to do it manually but it looks like this ability will be added in Android O:
https://developer.android.com/preview/api-overview.html#aset
Starting in Android O, the AnimatorSet API now supports seeking and playing in reverse. Seeking lets you set the position of the animation set to a specific point in time. Playing in reverse is useful if your app includes animations for actions that can be undone. Instead of defining two separate animation sets, you can play the same one in reverse.
Is there a way to run an AnimatorSet in reverse on Android? The ValueAnimator API does provide a reverse method on the individual animators but not on a set of animators.
If your AnimatorSet is being played sequentially then you could use the method mentioned by #blackbelt:
public static AnimatorSet reverseSequentialAnimatorSet(AnimatorSet animatorSet) {
ArrayList<Animator> animators = animatorSet.getChildAnimations();
Collections.reverse(animators);
AnimatorSet reversedAnimatorSet = new AnimatorSet();
reversedAnimatorSet.playSequentially(animators);
reversedAnimatorSet.setDuration(animatorSet.getDuration());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
// getInterpolator() requires API 18
reversedAnimatorSet.setInterpolator(animatorSet.getInterpolator());
}
return reversedAnimatorSet;
}
The caveat being that this only works for simple sequential animations as any dependencies setup in the original AnimatorSet will be lost. Also, if an interpolator was used on the AnimatorSet it will only carry over on API 18 or newer (per method mentioned above, you could alternatively manually add the interpolator back to the new reversed animator set).
The individual animations within the AnimatorSet will not play in reverse, if that is desirable then you'll also have to iterate over the animations of the AnimatorSet and set a ReverseInterpolator on each, see answer to Android: Reversing an Animation.
You can take the initial input values from i.e ValueAnimator.ofFloat(0, 1) and switch them around with yourAnimator.setFloatValues(1, 0) before calling yourAnimatorSet.start() when you want to reverse animation
The reverse() method has been added in API 26:
Plays the AnimatorSet in reverse. If the animation has been seeked to a specific play time using setCurrentPlayTime(long), it will play backwards from the point seeked when reverse was called. Otherwise, then it will start from the end and play backwards. This behavior is only set for the current animation; future playing of the animation will use the default behavior of playing forward.
Note: reverse is not supported for infinite AnimatorSet.
I have a property animation, but it's not smooth, i.e I'm getting jumps in the animation. I tried to play with the parameters for the animator, such as the interpolator, the duration, and the frame delay, but can't get a smooth effect. Does anyone have some tricks / examples? Here's my current code for setting the animator:
rotationAnimator=new ObjectAnimator();
rotationAnimator.setTarget(rotationController);
rotationAnimator.setPropertyName("mapRotation");
rotationAnimator.setInterpolator(new LinearInterpolator());
ValueAnimator.setFrameDelay(24);
rotationAnimator.setDuration(ROTATION_DURATION);
rotationAnimator.setRepeatCount(ValueAnimator.INFINITE);
rotationAnimator.setRepeatMode(ValueAnimator.RESTART);
(There's also a call to setFloatValues, but it comes later in the code, before I start the animation)
EDIT: I was asked to show what I'm animating, so here it is:
That's the setter function that gets the value from the animation:
public void setMapRotation(float rotationDegrees)
{
if ((rotationAnimator!=null)&&(rotationAnimator.isRunning()))
rotationAnimator.cancel();
rotationDegrees%=360;
if (rotationDegrees<0) rotationDegrees+=360;
rotationView.setRotationDegrees(rotationDegrees);
if (rotationDegrees!=oldRotationDegrees)
{
notifyRotationListeners();
oldRotationDegrees=rotationDegrees;
}
}
If you look at the code, you'll see there's another function that gets called (setRotationDegrees), so here it is:
public void setRotationDegrees(float rotationDegrees)
{
this.rotationDegrees=rotationDegrees%360;
if (this.rotationDegrees<0) this.rotationDegrees+=360;
Log.i("MapView","View degrees: " + this.rotationDegrees);
invalidate();
}
And that's what happens after the invalidation:
#Override protected void dispatchDraw(Canvas canvas)
{
Log.i("MapView","Redrawing");
int saveCount=canvas.save(Canvas.MATRIX_SAVE_FLAG);
canvas.rotate(rotationDegrees,getWidth()/2,getHeight()/2);
canvas.setDrawFilter(drawFilter);
canvas.getMatrix(rotationMatrix);
super.dispatchDraw(canvas);
canvas.restoreToCount(saveCount);
}
I don't know if there's something particularly heavy here, but I may be wrong...
Defining an AnimatorSet and set the Interpolator on it may solve your issue:
rotationAnimator=new ObjectAnimator();
rotationAnimator.setTarget(rotationController);
rotationAnimator.setPropertyName("mapRotation");
ValueAnimator.setFrameDelay(24);
rotationAnimator.setDuration(ROTATION_DURATION);
rotationAnimator.setRepeatCount(ValueAnimator.INFINITE);
rotationAnimator.setRepeatMode(ValueAnimator.RESTART);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.getChildAnimations().add(rotationAnimator);
animatorSet.setInterpolator(new LinearInterpolator());
...
animatorSet.start();
I've resolved by adding HW acceleration directly to my animated view and view container (myViewContainer - in my case a RelativeLayout):
if(!myViewContainer.isHardwareAccelerated())
{
myViewContainer.setLayerType(View.LAYER_TYPE_HARDWARE, null);
myAnimatedView1.setLayerType(View.LAYER_TYPE_HARDWARE, null);
myAnimatedView2.setLayerType(View.LAYER_TYPE_HARDWARE, null);
}
Jumps in the middle of the animation are more likely to be caused by either something in your rotationController class, that is receiving the animated value "mapRotation". Or it could be something to do with your layout.
I would imagine that there is something quite intense, memory or cpu wise, that is causing the juttering.
If your animating something that has lots of layouts then that could be the cause. If in your code you have lots of findViewByID that is getting called as a result of your animation then that to can cause a slow down.
If your just animating a simple image, then im not entirely sure what the issue would be.
Personally i think your code looks fine, perhaps show/describe what your animating.
[edit]
I have limited knowledge of animating with the canvas so i took a quick look at how you were using canvas.save() and canvas.restoreToCount() which i hadn't seen before.
It all looks good though i do find it odd that several tutorials rotate the canvas rather than the ball image to create the rotation. Still, the only reference i found to improving the animating was a comment in the following link that suggested using postInvalidateOnAnimation(). Not sure where though, perhaps instead of invalidate().
I use the ObjectAnimator API (android.animation.ObjectAnimator) to animate a button once it's clicked (v is the Button):
ObjectAnimator animator = ObjectAnimator.ofFloat(v, "rotationY", 360f);
animator.setDuration(5000);
animator.start();
When I test this on the emulator, it works for the first click (button rotates). But when I click the button again (the fragment is not destroyed etc. after the first click), I don't see any animation on the emulator (the emulator isn't the fastest, but with 5 seconds I should see something).
Do I need to destroy/close something after the first animation or what am I missing?
Does anyone have a hint or can reproduce this?
Thanks in advance,
Martin
The second time you will try to animate from 360.0f to 360.0f. Change your call to ofFloat() to:
ObjectAnimator.ofFloat(v, "rotationY", 0.0f, 360.0f)
To elaborate on Romain's answer, the result of the single-value factory constructor is animation that will run from whatever the current value is to the value specified in the parameters. In your case, the object had a value of 0 to begin with an animated (the first time) to a value of 360. The second time it ran, it animated from the current value (360) to the specified value (360). Not much of an animation.
The fix is as above: hard-code both the start and end values for the animator. Alternatively, you can reset the value back to 0 when the animation finishes by implementing the AnimatorListener.onAnimationEnd method and resetting it when the animation finishes:
animator.addListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
v.setRotationY(0);
}
});