RecycleView, wasAnimatingWhenDetached isn't set to false when view is attached
See original GitHub issueWe have a related issue created few hours ago. But the main issue isn’t highlighted/described enough inside of the ticket.
Let’s start:
I’m using LottieAnimationView.java
inside RecyclerView.java
. In my code every third item has an animation running, I’m operating with a dataset containing 60 items.
When LottieAnimationView.java
is playing an animation and user starts scrolling we receive a onDetachedFromWindow()
(in case if view scroll offset is big enough).
@Override
protected void onDetachedFromWindow() {
if (isAnimating()) {
cancelAnimation();
wasAnimatingWhenDetached = true;
}
super.onDetachedFromWindow();
}
Here we are setting wasAnimatingWhenDetached
to true
. Afterwards, when user scrolls again and returns to the same item - onAttachedToWindow() callback is called:
@Override protected void onAttachedToWindow() {
super.onAttachedToWindow();
if (autoPlay || wasAnimatingWhenDetached) {
playAnimation();
// Autoplay from xml should only apply once.
autoPlay = false;
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
// This is needed to mimic newer platform behavior.
// https://stackoverflow.com/a/53625860/715633
onVisibilityChanged(this, getVisibility());
}
}
In this callback we are relying on wasAnimatingWhenDetached
and in our case we will play animation which wasn’t finished. Until now everything was pretty ok, we are restarting animation which wasn’t finished - pretty logical. But we aren’t setting wasAnimatingWhenDetached
flag to false
- and that has some side effects.
In example we scroll back and forth, basically just returning to the same item - and it will again go through onDetachedFromWindow()
&& onAttachedToWindow()
callbacks. And when the onAttachedToWindow()
callback will be triggered we will re-run the animation again, evenif it was finished, which isn’t the desired behavior(at least as I see it). That will happen because wasAnimatingWhenDetached
isn’t set to false in onAttachedToWindow()
callback. Here is an example how it works in real-life(almost) app. Video is attached as link on google drive as Github doesn’t supports mp4.
So, as you can see I have the bells animation which should be played only once. At first I want to show normal/desired flow:
- User scrolls to an item with animation
- Animation plays till it is finished
- User scrolls back and forth but animation doesn’t restarts
Afterwards I wanna show wrong flow:
- User scrolls to an item with animation
- Animation starts
- User starts scrolling and animation is cancelled
- User scrolls back to that animation item and animation re-runs and is finished. We end up in finished animation state.
- Every next time user scrolls back and forth - animation re-runs. That is not desired behavior(IMHO).
I’ve cloned lottie project and imported it as module to mine project. I have an idea how that could be fixed(if thats a bug of course). We can add following line to onAttachedToWindow()
callback:
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
if (autoPlay || wasAnimatingWhenDetached) {
playAnimation();
// Autoplay from xml should only apply once.
autoPlay = false;
// this one line could be used to reset our flag
wasAnimatingWhenDetached = false;
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
// This is needed to mimic newer platform behavior.
// https://stackoverflow.com/a/53625860/715633
onVisibilityChanged(this, getVisibility());
}
}
I haven’t analyzed other cases, where this fix could create issues. But I’m almost sure other cases do not exist. Anyway it would be great to hear feedback or explanation on that. Animation JSON file attached: ringing_bell.json.zip
Issue Analytics
- State:
- Created 4 years ago
- Comments:7 (2 by maintainers)
Top GitHub Comments
@gpeal Sorry for late reply! I was kind of really far away from civilization and stable internet connection. Sure, I have few ideas how that espresso test could be written. Actually I’ve spent some time analyzing existing
Tests
insideFragmentVisibilityTests
. I think my case is similar totestPausesWhenScrolledOffScreenAndResumesWhenComesBack()
test, so I’ve taken it as an example and base for building mine. While playing around I’ve found an interesting thing inside that test.In this code snippet we are scrolling back and forth and verifying that
animationView
restarts animating when it is visible. But that is kinda tricky, as it would be playing animation in any case, because onBindViewHolder()->bindLottieHolder() is called :So as result
playAnimation()
is called and in any caseanimationView!!.isAnimating
will returntrue
. Per my mind - this test is testing wrong flow, it should be adjusted. As usually - I could be wrong. Going back to mine case - the best way here is to create a PR, it would be easier to review changes. Anyway I’ll explain mine approach and solution here:IdlingResource
, which is waiting until lottie animation is finished:LottieAnimationView.java 286 line
insideonAttachedToWindow()
callback:wasAnimatingWhenDetached = false;
BTW
testPausesWhenScrolledOffScreenAndResumesWhenComesBack()
test could be adjusted to useanimationWasPlayed
boolean(as in mine test) and that will fix it. Probably my comment is not a small one, because of that I prefer creating a PR. Thanks a lot!I’m still seeing this in
3.1.0
. Usingpost { lottieView.playAnimation() }
works for now (https://github.com/airbnb/lottie-android/issues/1284#issuecomment-507229013).