Does an Android Animation stop on its own if made invisible? - android

ImageView something = (ImageView)findViewById(R.id.my_animated_image_view);
something.setVisibility(View.INVISIBLE);
((AnimationDrawable)something.getDrawable()).stop(); // <-- Is this line redundant?
In my case, i have a bunch of animations of which only one runs/is visible, and right now I'm calling setVisibility() and stop() on all of them. Might get faster if i don't need to call stop() on my own?

From AnimationDrawable source:
#Override
public boolean setVisible(boolean visible, boolean restart) {
boolean changed = super.setVisible(visible, restart);
if (visible) {
if (changed || restart) {
setFrame(0, true, true);
}
} else {
unscheduleSelf(this);
}
return changed;
}
So if you apply setVisible(false,false) to the AnimationDrawable then the animation will stop. But not when you apply it to the View. If you want the animation to go smooth then try AnimationDrawable.setVisible(false,false) and if you want your view Invisible don't make it invisible cause a lot of UI stuff happens then. Try setting an Alpha animation ,make it transparent and setFillAfter(true). When the animations end call view to setVisibility(View.Invisible) where is needed. This probably with give you some FPS. But generally consider overall what UI stuff happen when you animate. Avoid GC calls and view invalidations

Related

Android AnimatorSet repeat then cancel

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!

Android - TransitionDrawable starting in 'end' state flickers when initially drawn

I have a RecyclerView holding a list of Artists. An artist can be marked as a favorite or not a favorite.
There's a heart to represent this state, and I have a transition drawable, where I'm going from a outlined heart, to a filled in heart based on if the artist in question has been marked as a favorite. The animation itself is working correctly and smoothly transitions between the two when the user clicks on the heart, and looks beautiful.
TransitionDrawable td = new TransitionDrawable(new Drawable[]{
ContextCompat.getDrawable(getActivity(), R.drawable.favorite_border),
ContextCompat.getDrawable(getActivity(), R.drawable.favorite)});
artistViewHolder.image.setImageDrawable(td);
if (artist.isFavorite()) {
td.startTransition(0);
}
In the onClick for the item, I call either .reverseTransition(300) or .startTransition(300) depending on the next state.
I'm running into problems anytime the view is created (1st screen load) and I need to start in the 2nd position (favorite). The initial load you can see it flicker from the empty heart to the filled heart, even though my animation time is set to 0 when it's created. This also happens anytime the list gets invalidated such as if it gets filtered into a smaller set of artists.
I guess since it needs to do things and isn't actually just setting the drawable, 0 isn't actually "instant". I can't find any other way to start in the "end" position though, other than to reverse the starting position of the images in the transition drawable itself.
If I do that though, start/reverse are going to behave differently on a heart by heart basis. At best, I would need to extend the transition drawable, and override the start/reverse/reset methods to take into account which initial state it's in, and that seems kind of hacky?
Am I missing something obvious to start in the end position but not cause that flicker?
Thanks!
You don't migtht remove flicker on td.startTransition(0);
So, you need change order in your Drawables array.
For example
Drawable favorite_border = ContextCompat.getDrawable(getActivity(), R.drawable.favorite_border);
Drawable favorite = ContextCompat.getDrawable(getActivity(), R.drawable.favorite);
TransitionDrawable td = new TransitionDrawable(isFavorite
? new Drawable[]{favorite, favorite_border}
: new Drawable[]{favorite_border, favorite});
artistViewHolder.image.setImageDrawable(td);
This will work provided that you do this in onBindViewHolder
Unless there's a better way to do this, I've gone ahead and extended it, and flipped the start/reverse call based off the initial state.
public class MyTransitionDrawable extends TransitionDrawable{
private boolean initialFavorite = false;
public MyTransitionDrawable(Drawable[] layers) {
super(layers);
}
public MyTransitionDrawable(Drawable[] layers, boolean initialFavorite) {
super(layers);
this.initialFavorite = initialFavorite;
}
public boolean isInitialFavorite() {
return initialFavorite;
}
#Override
public void startTransition(int durationMillis) {
if(!initialFavorite){
super.startTransition(durationMillis);
}else{
super.reverseTransition(durationMillis);
}
}
#Override
public void reverseTransition(int durationMillis) {
if(!initialFavorite){
super.reverseTransition(durationMillis);
}else{
super.startTransition(durationMillis);
}
}
}
`

invalidate() on custom View does not cause onDraw(Canvas c) to be called

For my activity i use 3 custom views stacked.
the lower one is a SurfaceView fullscreen, the middle one is derived from GridView and overrides onDraw to add custom graphic.
The top one is derived directly from View, sits in a corner of the screen and act as a knob to control the others two views (this is the problematic view).
to add a custom animation to this view, i used this pattern:
public class StubKnobView extends View{
private Drawable knob;
private Point knobPos;
private float rotation;
private boolean needsToMove;
public void moveKnob(){
/* ...various calculations... */
this.needsToMove=true;
invalidate(); //to notify the intention to start animation
}
private void updateAnimation(){
/* ........... */
this.needsToMove= !animationComplete;
invalidate();
}
#Override
protected void onDraw(Canvas canvas) {
int saveCount=canvas.getSaveCount();
canvas.save();
canvas.rotate(this.rotation, this.knobPos.x, this.knobPos.y );
this.knob.draw(canvas);
canvas.restoreToCount(saveCount);
if(this.needsToMove){
updateAnimation();
}
}
}
ideally, if there is an animation pending, after each drawing cycle the view should auto invalidate.
right now this doesn't work, to force the animation i have to touch the screen to cause a onDraw cycle.
Using "show screen updates" of Dev tools I see that no screen invalidate/update cycle happen , apart when i click the screen.
specifying the dirty rect also ha no effect.
So, any idea where to look to know why this invalidate/draw cycle does not work the way is intended?
I encontered this situation, and also doubted that invalidate() doesn't make onDraw() called again.
After simplified the business codes, it's turn out that there is a dead loop - exactly too much iterations, which blocks the UI thread.
So, my advice is that make sure the UI thread going smoothly first.
Try something like this with a private class in your View. The idea is not to call invalidate() from within onDraw(). Instead, post it on the running queue. Instantiate the private class in your View constructor and then just call animateKnob.start() when you want the animation. The onDraw() can then just focus on drawing.
public void moveKnob(){
// update the state to draw... KnobPos, rotation
invalidate()
}
private class AnimateKnob{
public void start(){
// Do some calculations if necessary
// Start the animation
MyView.this.post( new Runnable() {
stepAnimation()
});
}
public void stepAnimation(){
// update the animation, maybe use an interpolator.
// Here you could also determine if the animation is complete or not
MyView.this.moveKnob();
if( !animationComplete ){
// Call it again
MyView.this.post( new Runnable() {
stepAnimation()
});
}
}
}

Autoadvancing gallery animation

I have a Gallery widget set up to auto advance every 10000ms. However the transition between views is instantaneous and I would prefer to have a transition.
My advancing method is like so:
private void tickerAdvance() {
int selectedItemPosition = mTickerGallery.getSelectedItemPosition();
if (selectedItemPosition + 1 == mTickerGallery.getCount()) {
mTickerGallery.setSelection(0, true);
} else {
mTickerGallery.setSelection(selectedItemPosition + 1, true);
}
}
I was under the impression that setting the animation to true would cause it to animate between states. In my XML I've also added animationDuration="500", however the transition still pops between states instantly.
The problem is because you have used the setSelection() which obviously won't give you the feel of an Gallery being scrolled. SO instead of using setSelection() you have to override the onScroll() of gallery.
Here is how you do it.
Assuming that you have made the necessary steps for auto advancing periodically, now do this code.
MotionEvent e1, e2;
gallery.onScroll(e1, e2, scroll_limit , 0); //scroll limit is your integer variable for scroll limit.
The answer I came up with is that the Gallery API is terribly non-extensible. There are multiple package private or private methods that would unlock this functionality without a kludge including moveNext, FlingRunnable.startwithdistance, and scrollToChild. Alas, they are all unavailable.
Instead the best answer I was able to come up with was to override setSelection to get all the behaviors I needed. In the setSelection method I reverse the deceleration algorithm to calculate a velocity for a calculated distance and then call onFling. This is a nasty kludge could be made a ton better by making any of the above methods protected or public (I can't fathom a reason why at least moveNext and scrollToChild shouldn't be).
i had a similar task to make item to item animation and i decided to choose gallery, so i tried a lot of things, but the best way is to subclass gallery and add this:
boolean avoidSound = false;
public void showNext() {
avoidSound = true;
this.onKeyDown(KeyEvent.KEYCODE_DPAD_RIGHT, null);
}
public void playSoundEffect(int soundConstant) {
if (avoidSound) {
avoidSound = false;
} else {
super.playSoundEffect(soundConstant);
}
}
just call showNext() and it will do an animation

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