Fixed endless recycler view animation. #1340

Additionally - adjusted "testPausesWhenScrolledOffScreenAndResumesWhenComesBack" test.

See more details: #1324
diff --git a/LottieSample/src/androidTest/java/com/airbnb/lottie/samples/FragmentVisibilityTests.kt b/LottieSample/src/androidTest/java/com/airbnb/lottie/samples/FragmentVisibilityTests.kt
index 7f507d6..f929d38 100644
--- a/LottieSample/src/androidTest/java/com/airbnb/lottie/samples/FragmentVisibilityTests.kt
+++ b/LottieSample/src/androidTest/java/com/airbnb/lottie/samples/FragmentVisibilityTests.kt
@@ -15,24 +15,20 @@
 import androidx.lifecycle.Lifecycle
 import androidx.recyclerview.widget.LinearLayoutManager
 import androidx.recyclerview.widget.RecyclerView
-import androidx.test.espresso.Espresso
 import androidx.test.espresso.Espresso.onView
 import androidx.test.espresso.IdlingRegistry
 import androidx.test.espresso.action.ViewActions
-import androidx.test.espresso.assertion.ViewAssertions
 import androidx.test.espresso.assertion.ViewAssertions.matches
-import androidx.test.espresso.matcher.ViewMatchers
 import androidx.test.espresso.matcher.ViewMatchers.*
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
 import com.airbnb.lottie.LottieAnimationView
 import com.airbnb.lottie.LottieDrawable
-import com.airbnb.lottie.samples.R
 import com.airbnb.lottie.model.LottieCompositionCache
 import com.nhaarman.mockitokotlin2.mock
-import org.junit.Assert
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertTrue
+import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import kotlin.random.Random
@@ -41,7 +37,7 @@
 @LargeTest
 class FragmentVisibilityTests {
 
-    @Test
+    @Before
     fun setup() {
         LottieCompositionCache.getInstance().clear()
     }
@@ -306,6 +302,8 @@
                 return RecyclerView(requireContext()).apply {
                     layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false)
                     adapter = object : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
+                        var animationWasPlayed = false
+
                         override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
                             return when (viewType) {
                                 0 -> object : RecyclerView.ViewHolder(LottieAnimationView(parent.context).apply { id = R.id.animation_view }) {}
@@ -323,12 +321,17 @@
                         }
 
                         private fun bindLottieHolder(holder: RecyclerView.ViewHolder) {
-                            animationView = holder.itemView as LottieAnimationView
-                            (holder.itemView as LottieAnimationView).apply {
-                                repeatCount = LottieDrawable.INFINITE
-                                setAnimation(R.raw.heart)
-                                playAnimation()
-                                IdlingRegistry.getInstance().register(LottieIdlingResource(this, name = "Lottie ${Random.nextFloat()}"))
+                            if (!animationWasPlayed) {
+                                animationView = holder.itemView as LottieAnimationView
+                                (holder.itemView as LottieAnimationView).apply {
+                                    //                                    repeatCount = LottieDrawable.INFINITE
+                                    setAnimation(R.raw.heart)
+                                    playAnimation()
+                                    animationWasPlayed = true
+                                    IdlingRegistry.getInstance().register(LottieIdlingResource(this, name = "Lottie ${Random.nextFloat()}"))
+                                }
+                            } else {
+                                IdlingRegistry.getInstance().register(LottieIdlingAnimationResource(animationView, name = "Lottie finished animation ${Random.nextFloat()}"))
                             }
                         }
 
@@ -347,6 +350,75 @@
         scenario.onFragment { assertFalse(it.animationView!!.isAnimating) }
         scenario.onFragment { it.requireView().scrollBy(0, -10_000) }
         scenario.onFragment { assertTrue(it.animationView!!.isAnimating) }
+        onView(withId(R.id.animation_view)).check(matches(isDisplayed()))
+        scenario.onFragment { assertFalse(it.animationView!!.isAnimating) }
+    }
+
+    @Test
+    fun testPausesWhenScrolledOffScreenAndResumesWhenComesBackWithoutRepeatingWhenFinished() {
+
+        class TestFragment : Fragment() {
+            var animationView: LottieAnimationView? = null
+            override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
+                return RecyclerView(requireContext()).apply {
+                    layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false)
+                    adapter = object : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
+
+                        var animationWasPlayed = false
+
+                        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
+                            return when (viewType) {
+                                0 -> object : RecyclerView.ViewHolder(
+                                        LottieAnimationView(parent.context)
+                                                .apply { id = R.id.animation_view }
+                                ) {}
+                                else -> object : RecyclerView.ViewHolder(TextView(parent.context)) {}
+                            }
+                        }
+
+                        override fun getItemCount(): Int = 1000
+
+                        override fun getItemViewType(position: Int) = position
+
+                        override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
+                            if (holder.itemViewType == 0) bindLottieHolder(holder)
+                            else bindOtherViewHolder(holder, position)
+                        }
+
+                        private fun bindLottieHolder(holder: RecyclerView.ViewHolder) {
+                            if (!animationWasPlayed) {
+                                animationView = holder.itemView as LottieAnimationView
+                                (holder.itemView as LottieAnimationView).apply {
+                                    setAnimation(R.raw.heart)
+                                    playAnimation()
+                                    animationWasPlayed = true
+                                    IdlingRegistry.getInstance().register(LottieIdlingResource(this, name = "Lottie ${Random.nextFloat()}"))
+                                }
+                            } else {
+                                IdlingRegistry.getInstance().register(LottieIdlingAnimationResource(animationView, name = "Lottie finished animation ${Random.nextFloat()}"))
+                            }
+                        }
+
+                        private fun bindOtherViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
+                            (holder.itemView as TextView).text = "Item $position"
+                        }
+                    }
+                }
+            }
+        }
+
+        val scenario = launchFragmentInContainer<TestFragment>()
+        onView(isAssignableFrom(RecyclerView::class.java)).check(matches(isDisplayed()))
+        scenario.onFragment { assertTrue(it.animationView!!.isAnimating) }
+        scenario.onFragment { it.requireView().scrollBy(0, 10_000) }
+        scenario.onFragment { assertFalse(it.animationView!!.isAnimating) }
+        scenario.onFragment { it.requireView().scrollBy(0, -10_000) }
+        scenario.onFragment { assertTrue(it.animationView!!.isAnimating) }
+        onView(withId(R.id.animation_view)).check(matches(isDisplayed()))
+        scenario.onFragment { assertFalse(it.animationView!!.isAnimating) }
+        scenario.onFragment { it.requireView().scrollBy(0, 10_000) }
+        scenario.onFragment { it.requireView().scrollBy(0, -10_000) }
+        scenario.onFragment { assertFalse(it.animationView!!.isAnimating) }
     }
 
     private fun FragmentScenario<*>.waitForState(desiredState: Lifecycle.State) {
diff --git a/LottieSample/src/androidTest/java/com/airbnb/lottie/samples/LottieIdlingAnimationResource.kt b/LottieSample/src/androidTest/java/com/airbnb/lottie/samples/LottieIdlingAnimationResource.kt
new file mode 100644
index 0000000..1cac096
--- /dev/null
+++ b/LottieSample/src/androidTest/java/com/airbnb/lottie/samples/LottieIdlingAnimationResource.kt
@@ -0,0 +1,37 @@
+package com.airbnb.lottie.samples
+
+import android.animation.Animator
+import androidx.test.espresso.IdlingRegistry
+import androidx.test.espresso.IdlingResource
+import com.airbnb.lottie.LottieAnimationView
+
+class LottieIdlingAnimationResource(animationView: LottieAnimationView?, private val name: String = "Lottie") : IdlingResource {
+
+    init {
+        animationView?.addAnimatorListener(object : AnimatorListenerAdapter() {
+            override fun onAnimationStart(animation: Animator) {
+                isIdle = false
+            }
+
+            override fun onAnimationEnd(animation: Animator) {
+                isIdle = true
+                callback?.onTransitionToIdle()
+                animationView.removeAllAnimatorListeners()
+                IdlingRegistry.getInstance().unregister(this@LottieIdlingAnimationResource)
+            }
+        })
+    }
+
+    private var callback: IdlingResource.ResourceCallback? = null
+    private var isIdle = animationView?.isAnimating?.not() ?: true
+
+
+    override fun getName() = name
+
+    override fun isIdleNow() = isIdle
+
+    override fun registerIdleTransitionCallback(callback: IdlingResource.ResourceCallback?) {
+        this.callback = callback
+        if (isIdle) callback?.onTransitionToIdle()
+    }
+}
\ No newline at end of file
diff --git a/lottie/src/main/java/com/airbnb/lottie/LottieAnimationView.java b/lottie/src/main/java/com/airbnb/lottie/LottieAnimationView.java
index 7ff6e8e..0c11bac 100644
--- a/lottie/src/main/java/com/airbnb/lottie/LottieAnimationView.java
+++ b/lottie/src/main/java/com/airbnb/lottie/LottieAnimationView.java
@@ -295,6 +295,7 @@
       playAnimation();
       // Autoplay from xml should only apply once.
       autoPlay = false;
+      wasAnimatingWhenDetached = false;
     }
     if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
       // This is needed to mimic newer platform behavior.