Android AnimatorSet repeat then cancel - android

What I need is an infinitely repeatable AnimatorSet (composed of 2 sequential animations) but also a way to stop these animations in a safe way (i.e. the final state is same as starting state). The stop would happen for example by tapping on the screen.
Major problem I had was when I tried stopping by calling either animatorSet.end() or cancel(), the animation was stopping either midway or finishing the first of the animations (hence not returning to original state).
I found a solution, which really is a mix of a number of different solutions found on SO.
IMPORTANT: The two sequential animations together create a graceful loop, such that animation1 takes a button slowly to the right, while animation2 returns it to the left more rapidly. This is why I need the stop to end gracefully, such that it finishes animation1 and animation2, whatever the state is when I want to stop it.

To create a repeatable AnimatorSet:
animatorSet = new AnimatorSet();
animatorSet.play(animation1).after(animation2);
animatorSet.addListener(new AnimatorListenerAdapter() {
boolean isCancelled;
#Override
public void onAnimationEnd(Animator animation) {
if (!isCancelled) {
animatorSet.start();
}
}
#Override
public void onAnimationCancel(Animator animation) {
this.isCancelled = true;
}
});
animatorSet.start();
Now the below method stopAnimation() you would call from your onClickListener or whatever you need to listen for to stop the animation (bear in mind animatorSet has to be accessible, make it a field for example).
public void stopAnimation(Context context){
guideAnimationSet.removeAllListeners();
In the end I didn't need any cancel() or end().
This actually makes sense: once I remove the listener, the animatorSet will finish the current animations and then won't be able to start it again since the listener for repeating is gone.
Hope it helps someone!

Related

Android ViewPropertyAnimator and ValueAnimator: How can I do fast-forward?

I am using ViewPropertyAnimator like this:
view.animate().alpha(to).setDuration(duration);
And ValueAnimator:
ValueAnimator anim = ValueAnimator.ofInt(0, width);
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
viewGroup.setMinimumWidth((Integer) valueAnimator.getAnimatedValue());
}
});
anim.setDuration(duration).start();
Sometimes I want to cancel the animation before its finished, but still apply the remaining steps immediately (as if the animation had completed). Is there a simple way to accomplish this using the APIs, or do I need to wrap the Animators in another class and handle this myself?
There are two ways of stopping a running animation.
Animator.end() // end the animation
Animator.cancel() // cancel the animation
You can use either the end() method or the cancel() method. In both cases the animation will stop and can only be restarted by calling start().
The difference between end() and cancel() is the state that the animated objects will be in after the call to the method.
When calling cancel() the animation will stop in its tracks, leaving the animated objects in an intermediate state.
When calling the end() method the animation will effectively be fast forwarded into the final state of the animation.
All objects will appear the way they would at the end of the animation.
Source: http://cogitolearning.co.uk/?p=1482
You can use an AnimatorListener for this.
anim.addListener(new Animator.AnimatorListener() {
#Override
public void onAnimationStart(Animator animation) {}
#Override
public void onAnimationEnd(Animator animation) {}
#Override
public void onAnimationCancel(Animator animation) {
// Set view properties here as if the animation had completed
viewGroup.setMinimumWidth(...);
}
#Override
public void onAnimationRepeat(Animator animation) {}
});
If you don't mind using a third party library then android-transition can be used:
Import the library in Gradle:
compile 'com.github.kaichunlin.transition:core:0.9.2'
Your ViewPropertyAnimator scenario would become
TransitionAnimation animation =
ViewTransitionBuilder.transit(view).alpha(to).duration(duration).buildAnimation();
Your ValueAnimator scenario would become
final int width = <width>;
TransitionAnimation animation =
ViewTransitionBuilder.transit(viewGroup).duration(duration).addTransitionHandler(new TransitionHandler() {
#Override
public void onUpdateProgress(TransitionController controller, View target, float progress) {
target.setMinimumWidth((int) (progress*width));
}
}).buildAnimation();
Then call animation.startAnimation(); to start and animation.endAnimation(); to end the animation and applying the final state.
The advantage of this library is that that a single concept/class can be used where it would require a mix of different classes (i.e. ViewPropertyAnimator & ValueAnimator), and that the animation state/progress can be tied to UI state like how much the Drawer is being pulled out.

Any tips for reducing lag when running multiple animations at the same time?

Once my app reaches ~4+ animations running concurrently, the animations start to lag a little. Are there any ways I could fix/optimize that? I am using ObjectAnimator and ValueAnimator.
So, if the views are not having to redraw themselves during the animations, you can enable hardware layers during the duration of the animations. For instance:
final View myView = // your view
Animator animator = ObjectAnimator.ofFloat(myView, View.ALPHA, 0f, 1f);
animator.setDuration(1000);
animator.addAnimatorListener(new Animator.AnimatorListener() {
#Override
public void onAnimationStart(Animator animator) {
myView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
}
#Override
public void onAnimationCancel(Animator animator) {
myView.setLayerType(View.LAYER_TYPE_NONE, null);
}
#Override
public void onAnimationEnd(Animator animator) {
// See http://stackoverflow.com/a/19615205/321697
myView.post(new Runnable() {
#Override
public void run() {
myView.setLayerType(View.LAYER_TYPE_NONE, null);
}
}
}
}
animator.start();
I'd suggest breaking that listener implementation out into a separate class, but the general idea is that you want to put the view into a hardware backed layer as soon as the animation starts (animation properties such as translation, rotation, scale, and alpha are incredibly cheap when backed by a hardware layer) and then clear the layer once the animation is complete.
In the onAnimationEnd callback, you'll notice a link to another answer where there's a bug with 4.0.x if you set the layer type to none in the onAnimationEnd callback. If you're only supporting 4.1 and above, the runnable indirection shouldn't be necessary.
EDIT: If you're animating a color transition, you probably don't want to do the above, as changing the color of the view will require it to be redrawn, causing it to rebuild the hardware layer on each frame.
If you have not used ViewPropertyAnimator, you better do. I moved and flipped(changed picture) 52 imageViews (one deck of playing cards) smoothly.
Here's an example on how to use ViewPropertyAnimator.

How to check to see if animation is running before starting new animation?

I have a activity where I have multiple ImageViews and when you click on it the ImageView will fade out and fade back in. What I'm trying to figure out is how I can click one ImageView and start the animation and when I click a 2nd one and the animation is still running it will ignore the second one. I think I need to do something with the animationListener but I can't figure out how to use that to check if the animation is running or not before I initiate a new animation. I could have sworn I saw an example that did this but I've been looking for days and can't find it anymore, I'm hoping someone would be able to help out here..... below is the code for my animation:
// Create Animation
protected void fadeAnimation() {
tempImg.startAnimation(fadeout);
//Allow animation to finish
mHandler.postDelayed(new Runnable() {
public void run() {
tempImg.startAnimation(fadein);
}
}, 1000);
}
I'm assuming both fadeout and fadein are Animation objects.
Use fadeout.hasEnded() to check if the first has finished before starting your second one.
For more details about the Animation class, see here:
http://developer.android.com/reference/android/view/animation/Animation.html
Instead of having to loop possibly in another thread checking if an animation has ended, you could use an animation listener, doing something like this:
// Create Animation
protected void fadeAnimation() {
fadeout.setAnimationListener(new Animation.AnimationListener(){
#Override
public void onAnimationStart(Animation animation) {
}
#Override
public void onAnimationEnd(Animation animation) {
tempImg.startAnimation(fadein);
}
#Override
public void onAnimationRepeat(Animation animation) {
}
};
tempImg.startAnimation(fadeout);
}
With this kind of solution you wouldn't need to be actively checking if an animation has finished and time it with the duration of the previous animation.
The onAnimationEnd(Animation) is fired right after the animation has ended. This also solves the issue of users with developer options "on" and animation speed set to "off".

animation not starting!! (android)

can anyone tell me why this animation isn't starting? i've tried putting code in the onAnimationStart listener and it is never called! there are no errors, it just never happens.. i've been staring at this for hours!!!
Animation bRegisterAnimation = fadeView(1,0,0);
bRegisterAnimation.setAnimationListener(new AnimationListener() {
public void onAnimationStart(Animation arg0) {}
public void onAnimationRepeat(Animation arg0) {}
public void onAnimationEnd(Animation arg0) {
bRegister.clearAnimation();
bRegister.setVisibility(View.INVISIBLE);
}
});
bRegister.setAnimation(bRegisterAnimation);
and here is the fadeView function:
public Animation fadeView(float startA, float endA, int delay) {
Animation animation = new AlphaAnimation(startA,endA);
animation.setDuration(1000);
animation.setStartOffset(delay);
return animation;
}
thanks!
You need to call bRegister.startAnimation(bRegisterAnimation). Also, you don't need to call setAnimation(). If you're trying to make a simple fade animation, you might try using this instead:
bRegister.startAnimation(AnimationUtils.loadAnimation(
getBaseContext(), android.R.anim.fade_in));
I guess bRegister is your view over which you want your animation to work. IF this is so, you have correctly set the animation with this view using bRegister.setAnimation(bRegisterAnimation);
but you haven't started the animation using startAnimation(). Try it once
I had a similar experience when trying to implement animations on my Android device, which was one that I borrowed from my sister. I tried getting animation to work on it for a whole day until I finally gave up. Days later was when I realized...... all animations had been turned off in the settings -_- lol so to avoid wasting your time like me it might be wise to check your settings first.

onAnimationEnd is not getting called, onAnimationStart works fine

I've a ScrollView in the PopupWindow. I'm animating ScrollView contents using TranslateAnimation.
When animation starts, the onAnimationStart listener is called but the onAnimationEnd is not getting called. Any ideas ?
Layout :
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:background="#drawable/popup_window_bg"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<View
android:layout_width="#dimen/toolbar_padding_left"
android:layout_height="#dimen/toolbar_height"/>
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+web/toolbar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scrollbars="none"
android:visibility="invisible">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
...
</LinearLayout>
</ScrollView>
</LinearLayout>
Animation code :
mToolbar = mPopupContents.findViewById( R.web.toolbar );
TranslateAnimation anim =
new TranslateAnimation(0, 0, -60, 0);
anim.setDuration(1000);
anim.setAnimationListener(new Animation.AnimationListener() {
public void onAnimationStart(Animation a) {
Log.d(LOGTAG, "---- animation start listener called" );
}
public void onAnimationRepeat(Animation a) {}
public void onAnimationEnd(Animation a) {
Log.d(LOGTAG, "---- animation end listener called" );
}
});
mToolbar.startAnimation(anim);
Update : I verified that the onAnimationEnd is called but it is called after some delay (provided you don't start the new animation during that time).
AnimationEnd is not reliable. If you don't want to rewrite your code with custom views that override OnAnimationEnd, use postDelayed.
Here's some example code:
final FadeUpAnimation anim = new FadeUpAnimation(v);
anim.setInterpolator(new AccelerateInterpolator());
anim.setDuration(1000);
anim.setFillAfter(true);
new Handler().postDelayed(new Runnable() {
public void run() {
v.clearAnimation();
//Extra work goes here
}
}, anim.getDuration());
v.startAnimation(anim);
While it MAY seem ugly, I can guarantee it's very reliable. I use it for ListViews that are inserting new rows while removing with animation to other rows. Stress testing a listener with AnimationEnd proved unreliable. Sometimes AnimationEnd was never triggered. You might want to reapply any transformation in the postDelayed function in case the animation didn't fully finish, but that really depends on what type of animation you're using.
Make sure that you are USING view.startAnimation(Animation)
AND NOT view.setAnimation(Animation). This simple confusion may be a problem.
After I don't remember how may posts read and days spent finding out a solution for this issue I discovered that if the object to move is not on the Screen region (for example is positioned out of the screen coords) OnAnimationEnd callback is not getting called. Probably the animation fails just after started (start method is called, I coded a listener) but nothing is wrote into logcat. Maybe this is not exactly your case but this finally solved my problem and hope it can help you too.
Also, when using animations, don't forget the setFillAfter() to true.
http://developer.android.com/reference/android/view/animation/Animation.html#setFillAfter(boolean)
If fillAfter is true, the transformation that this animation performed will persist when it is finished. Defaults to false if not set. Note that this applies when using an AnimationSet to chain animations. The transformation is not applied before the AnimationSet itself starts.
anim.setFillAfter(true);
mToolbar.startAnimation(anim);
For anyone stumbling upon this question: Consider switching over to using the Property Animation system instead http://developer.android.com/guide/topics/graphics/prop-animation.html
I've had several problems with the old way of animating a fade-in/out on a view (through AlphaAnimation). OnAnimationEnd was not being called etc ... With the Property Animation all those problems were resolved.
If you want to support API<11 devices, Jake Wharton's https://github.com/JakeWharton/NineOldAndroids is the way to go
Happened to me in a scenario that isn't described in other answers. Tried this and it didn't work:
ValueAnimator.ofInt(currentHeight, desiredHeight).apply {
duration = 500
addListener {
doOnEnd {
Log.d(TAG, "animate: animation ended")
}
}
start()
}
Changing to this resolved the issue. Unfortunately can't tell why.
ValueAnimator.ofInt(currentHeight, desiredHeight).apply {
duration = 500
doOnEnd {
Log.d(TAG, "animate: animation ended")
}
start()
}
The delay is probably due to the anim.setDuration(1000) or if you are doing this on a thread, then probably due to context switching. Try manipulating the delay time and see if you notice any difference.
Try to use overridePendingAnimation(int,int).
i.e.
overridePendingAnimation(0,0)
It will override your default animation and then you can define you own animation By calling the method startAnimation using the object of View.
Here is my example code. Dont know whether it will be helpful to you or not.
overridePendingTransition(0,0);
//finish();
v.startAnimation(AnimationUtils.loadAnimation(getApplicationContext(), R.anim.fadeout));
startActivity(new Intent(ContentManagerActivity.this,Mainmenu_activity.class));
overridePendingTransition(R.anim.fadein,0);
There might be someone still having this issue and not found a solution -even though reads a lot of stackoverflow answers- like me!
So my case was: I used animatorSet and
there was not a single view that i could call clearAnimation on,
I wasn't calling my animation from backgroundThread -which you should never do that, btw-
As a solution, i did call animatorSet.end() right before animatorSet.start()
When you start an animation command in a view that is partially offscreen, the animation start and the onStartListener is called, but the animation doesn't run completely (somehow it is interrupted in the middle). My guess is that, as the view is offscreen, it is canceled and therefore it's onFinish is not called. As a workaround for that i created my own animation listener that start's an handler and uses postDelayed to notify the user of the animation end event. In Kotlin:
abstract class PartiallyOffScreenAnimationListener : Animation.AnimationListener, AnimationListener {
override fun onAnimationRepeat(animation: Animation?) {
onAnimationRepeat_(animation)
}
override fun onAnimationEnd(animation: Animation) {}
override fun onAnimationStart(animation: Animation) {
onAnimationStart_(animation)
Handler().postDelayed({
onAnimationEnd_(animation)
animation.setAnimationListener(null)
}, animation.duration + 50)
}
}
Please notice that, in the case the animation doesn't run completely, the view may be left at an inconsistent state (for example, animating layout parameters may lead to a weird middle interpolator factor being dropped. For that reason, you should verify at the end callback that the view is in the wanted state. If not, just set it manually.
Do it like belows
make animation final
invoke it inside a handler
listener should be instantiated once.
clear animation.
for example
view.clearAnimation();
new Hander().post(
run() {
final TranslateAnimation ani = new TranslateAnimation(0, 0, 0, 0);
ani.setAnimationListener(mListener);
}
);
private Animation.AnimationListener mListener = new Animation.AnimationListener() {
}
Are you not setting another animation before that one you are waiting ends?
Without context is hard to understand if it's something like that... but it is probably one of the only things that would cause it.
I guess Its gets called after sometime becasue you are using setDuration for animation and passing 1000 ms in it. Just pass 0 and it won't take a while to get called.
I tried your code it's working fine at OnAnimation start and inAmimationEnd also , after duration time means after finish animation onAnimationEnd is called , so your code working fine
TranslateAnimation anim =new TranslateAnimation(0, 0, -60, 0);
anim.setDuration(1000);
anim.setAnimationListener(new Animation.AnimationListener() {
public void onAnimationStart(Animation a) {
Log.w("Start", "---- animation start listener called" );
}
public void onAnimationRepeat(Animation a) {}
public void onAnimationEnd(Animation a) {
Log.d(" end ","---- animation end listener called" );
}
});
mIv.setAnimation(anim);
mIv.startAnimation(anim);
Where do you start your animation ? If in onCreate, it is wrong. Try to do it in onResume.

Categories

Resources