diff --git a/kotlin/src/androidTest/java/app/rive/runtime/kotlin/core/RiveViewTest.kt b/kotlin/src/androidTest/java/app/rive/runtime/kotlin/core/RiveViewTest.kt new file mode 100644 index 000000000..20b705570 --- /dev/null +++ b/kotlin/src/androidTest/java/app/rive/runtime/kotlin/core/RiveViewTest.kt @@ -0,0 +1,115 @@ +package app.rive.runtime.kotlin.core + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.internal.runner.junit4.statement.UiThreadStatement +import app.rive.runtime.kotlin.RiveAnimationView +import app.rive.runtime.kotlin.test.R +import org.junit.Assert.assertEquals +import org.junit.Test +import org.junit.runner.RunWith + + +@RunWith(AndroidJUnit4::class) +class RiveViewTest { + + @Test + fun viewNoDefaults() { + UiThreadStatement.runOnUiThread { + val appContext = initTests() + val view = RiveAnimationView(appContext) + + assertEquals(view.isPlaying, false) + } + } + + @Test + fun viewDefaultsLoadResouce() { + UiThreadStatement.runOnUiThread { + val appContext = initTests() + + val view = RiveAnimationView(appContext) + view.setRiveResource(R.raw.multipleartboards) + + assertEquals(view.isPlaying, true) + assertEquals(view.file?.artboardNames, listOf("artboard2", "artboard1")) + assertEquals( + view.animations.map { it.animation.name }.toList(), + listOf("artboard2animation1", "artboard2animation2") + ) + } + } + + @Test + fun viewDefaultsChangeArtboard() { + UiThreadStatement.runOnUiThread { + val appContext = initTests() + val view = RiveAnimationView(appContext) + view.setRiveResource(R.raw.multipleartboards) + assertEquals(view.isPlaying, true) + view.artboardName = "artboard1" + assertEquals( + view.animations.map { it.animation.name }.toList(), + listOf("artboard1animation1") + ) + } + + } + + @Test + fun viewDefaultsNoAutoplay() { + UiThreadStatement.runOnUiThread { + val appContext = initTests() + + val view = RiveAnimationView(appContext) + view.autoplay = false + view.setRiveResource(R.raw.multipleartboards) + + assertEquals(view.isPlaying, false) + view.artboardName = "artboard2" + assertEquals( + view.animations.map { it.animation.name }.toList(), + listOf() + ) + view.play(listOf("artboard2animation1", "artboard2animation2")) + assertEquals( + view.animations.map { it.animation.name }.toList(), + listOf("artboard2animation1", "artboard2animation2") + ) + } + } + + @Test + fun viewPause() { + UiThreadStatement.runOnUiThread { + val appContext = initTests() + + val view = RiveAnimationView(appContext) + view.setRiveResource(R.raw.multipleartboards) + assertEquals(view.isPlaying, true) + assertEquals(view.animations.size, 2) + view.pause() + assertEquals(view.isPlaying, false) + } + } + + @Test + fun viewPauseOneByOne() { + UiThreadStatement.runOnUiThread { + val appContext = initTests() + + val view = RiveAnimationView(appContext) + view.setRiveResource(R.raw.multipleartboards) + assertEquals(view.isPlaying, true) + assertEquals( + view.animations.map { it.animation.name }.toList(), + listOf("artboard2animation1", "artboard2animation2") + ) + view.pause("artboard2animation1") + assertEquals(view.isPlaying, true) + view.pause("artboard2animation2") + assertEquals(view.isPlaying, false) + } + } + + +} \ No newline at end of file diff --git a/kotlin/src/main/java/app/rive/runtime/kotlin/RiveAnimationView.kt b/kotlin/src/main/java/app/rive/runtime/kotlin/RiveAnimationView.kt index 6d8c21b34..d54abd759 100644 --- a/kotlin/src/main/java/app/rive/runtime/kotlin/RiveAnimationView.kt +++ b/kotlin/src/main/java/app/rive/runtime/kotlin/RiveAnimationView.kt @@ -6,14 +6,8 @@ import android.view.View import androidx.annotation.RawRes import app.rive.runtime.kotlin.core.* -class RiveAnimationView(context: Context, attrs: AttributeSet?) : View(context, attrs) { - private var animationName: String? = null; - private var artboardName: String? = null; - private var autoplay: Boolean = true; - private var fit: Fit = Fit.CONTAIN - private var loop: Loop = Loop.LOOP - private var alignment: Alignment = Alignment.CENTER - private var drawable: RiveDrawable = RiveDrawable(fit, alignment, loop); +class RiveAnimationView(context: Context, attrs: AttributeSet? = null) : View(context, attrs) { + private var drawable: RiveDrawable = RiveDrawable(); private var resourceId: Int? = null; init { @@ -24,20 +18,20 @@ class RiveAnimationView(context: Context, attrs: AttributeSet?) : View(context, ).apply { try { val alignmentIndex = getInteger(R.styleable.RiveAnimationView_riveAlignment, 4) - alignment = Alignment.values()[alignmentIndex] - val fitIndex = getInteger(R.styleable.RiveAnimationView_riveFit, 1) - fit = Fit.values()[fitIndex] - val loopIndex = getInteger(R.styleable.RiveAnimationView_riveLoop, 1) - loop = Loop.values()[loopIndex] - - autoplay = getBoolean(R.styleable.RiveAnimationView_riveAutoPlay, true) + val autoplay = getBoolean(R.styleable.RiveAnimationView_riveAutoPlay, autoplay) + val artboardName = getString(R.styleable.RiveAnimationView_riveArtboard) + val animationName = getString(R.styleable.RiveAnimationView_riveAnimation) + val resourceId = getResourceId(R.styleable.RiveAnimationView_riveResource, -1) - artboardName = getString(R.styleable.RiveAnimationView_riveArtboard) - animationName = getString(R.styleable.RiveAnimationView_riveAnimation) + drawable.alignment = Alignment.values()[alignmentIndex] + drawable.fit = Fit.values()[fitIndex] + drawable.loop = Loop.values()[loopIndex] + drawable.autoplay = autoplay + drawable.artboardName = artboardName + drawable.animationName = animationName - val resourceId = getResourceId(R.styleable.RiveAnimationView_riveResource, -1) if (resourceId != -1) { setRiveResource(resourceId) } @@ -65,14 +59,16 @@ class RiveAnimationView(context: Context, attrs: AttributeSet?) : View(context, ) { drawable.play(loop) } + fun play( animationNames: List, loop: Loop = Loop.NONE ) { drawable.play(animationNames, loop) } + fun play( - animationName: String? = null, + animationName: String, loop: Loop = Loop.NONE ) { drawable.play(animationName, loop) @@ -98,26 +94,30 @@ class RiveAnimationView(context: Context, attrs: AttributeSet?) : View(context, fun setRiveResource(@RawRes resId: Int) { resourceId = resId val file = File(resources.openRawResource(resId).readBytes()) - setAnimationFile(file) + setRiveFile(file) } - fun setAnimationFile(file: File) { + fun setRiveFile(file: File) { drawable.run { reset() - destroy() + } + + // TODO: we maybe not be cleaning something up here, + // as we shouldnt have create a new drawable drawable = RiveDrawable( - fit = fit, - alignment = alignment, - loop = loop, - artboardName = artboardName, - animationName = animationName, - autoplay = autoplay - ).apply { - setAnimationFile(file) - background = this - } + fit = drawable.fit, + alignment = drawable.alignment, + loop = drawable.loop, + autoplay = drawable.autoplay, + animationName = drawable.animationName, + artboardName = drawable.artboardName, + ) + + drawable.setRiveFile(file) + background = drawable + requestLayout() } @@ -141,8 +141,8 @@ class RiveAnimationView(context: Context, attrs: AttributeSet?) : View(context, // Lets work out how much space our artboard is going to actually use. var usedBounds = Rive.calculateRequiredBounds( - fit, - alignment, + drawable.fit, + drawable.alignment, AABB(providedWidth.toFloat(), providedHeight.toFloat()), drawable.arboardBounds() ) @@ -175,6 +175,24 @@ class RiveAnimationView(context: Context, attrs: AttributeSet?) : View(context, setMeasuredDimension(width, height); } + val file: File? + get() = drawable.file + + var artboardName: String? + get() = drawable.artboardName + set(name) { + drawable.setArtboardByName(name) + } + + var autoplay: Boolean + get() = drawable.autoplay + set(value) { + drawable.autoplay = value + } + + val animations: List + get() = drawable.animations + override fun onDetachedFromWindow() { super.onDetachedFromWindow() pause() diff --git a/kotlin/src/main/java/app/rive/runtime/kotlin/RiveDrawable.kt b/kotlin/src/main/java/app/rive/runtime/kotlin/RiveDrawable.kt index 4dbca0517..aa22306e5 100644 --- a/kotlin/src/main/java/app/rive/runtime/kotlin/RiveDrawable.kt +++ b/kotlin/src/main/java/app/rive/runtime/kotlin/RiveDrawable.kt @@ -10,18 +10,18 @@ import android.graphics.drawable.Drawable import app.rive.runtime.kotlin.core.* class RiveDrawable( - private var fit: Fit = Fit.CONTAIN, - private var alignment: Alignment = Alignment.CENTER, - private var loop: Loop = Loop.NONE, - private var artboardName: String? = null, - private var animationName: String? = null, - private var autoplay: Boolean = true + var fit: Fit = Fit.CONTAIN, + var alignment: Alignment = Alignment.CENTER, + var loop: Loop = Loop.NONE, + var artboardName: String? = null, + var animationName: String? = null, + var autoplay: Boolean = true ) : Drawable(), Animatable { private val renderer = Renderer() private val animator = TimeAnimator() - private var animations = mutableListOf() - private var file: File? = null + var animations = mutableListOf() + var file: File? = null private var artboard: Artboard? = null private var targetBounds: AABB @@ -31,7 +31,7 @@ class RiveDrawable( targetBounds = AABB(bounds.width().toFloat(), bounds.height().toFloat()) animator.setTimeListener { _, _, delta -> - var continuePlaying = true; + artboard?.let { ab -> val elapsed = delta.toFloat() / 1000 @@ -55,9 +55,10 @@ class RiveDrawable( } } ab.advance(elapsed) + } // TODO: set continuePlaying to false if all animations have come to an end. - if (!continuePlaying) { + if (playingAnimations.isEmpty()) { animator.pause() } invalidateSelf() @@ -68,20 +69,49 @@ class RiveDrawable( } } - fun setAnimationFile(file: File) { + fun setRiveFile(file: File) { this.file = file + selectArtboard() + } + + private fun selectArtboard() { + file?.let { file -> + artboardName?.let { + setArtboard(file.artboard(it)) + } ?: run { + setArtboard(file.firstArtboard) + } + } + } + + fun setArtboardByName(artboardName: String?) { + stop() + if (file == null) { + this.artboardName = artboardName + } else { + file?.let { + if (!it.artboardNames.contains(artboardName)) { + throw RiveException("Artboard $artboardName not found") + } + this.artboardName = artboardName + selectArtboard() + } + } - artboardName?.let { - setArtboard(file.artboard(it)) - } ?: run { - setArtboard(file.firstArtboard) + this.file?.let { + setRiveFile(it) } } - fun setArtboard(artboard: Artboard) { + private fun setArtboard(artboard: Artboard) { this.artboard = artboard if (autoplay) { - play(animationName = animationName) + animationName?.let { + play(animationName = it) + } ?: run { + play() + } + } else { artboard.advance(0f) } @@ -159,7 +189,7 @@ class RiveDrawable( playingAnimations.clear() animations.clear() file?.let { - setAnimationFile(it) + setRiveFile(it) } invalidateSelf() } @@ -246,11 +276,13 @@ class RiveDrawable( } override fun stop() { + animations.clear() + playingAnimations.clear() animator.cancel() } val isPlaying: Boolean - get() = animator.isRunning && !animator.isPaused + get() = playingAnimations.isNotEmpty() override fun isRunning(): Boolean {