[go: nahoru, domu]

blob: 308af1dddf8bc5d4156be819cfe5d97fbca17927 [file] [log] [blame]
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.compose.animation.core
import androidx.compose.animation.VectorConverter
import androidx.compose.animation.animateColor
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.runtime.withFrameNanos
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import junit.framework.TestCase.assertEquals
import junit.framework.TestCase.assertFalse
import junit.framework.TestCase.assertTrue
import kotlinx.coroutines.delay
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
@LargeTest
class TransitionTest {
@get:Rule
val rule = createComposeRule()
private enum class AnimStates {
From,
To
}
@OptIn(InternalAnimationApi::class)
@Test
fun transitionTest() {
val target = mutableStateOf(AnimStates.From)
val floatAnim1 = TargetBasedAnimation(
spring(dampingRatio = Spring.DampingRatioHighBouncy),
Float.VectorConverter,
0f,
1f
)
val floatAnim2 = TargetBasedAnimation(
spring(dampingRatio = Spring.DampingRatioLowBouncy, stiffness = Spring.StiffnessLow),
Float.VectorConverter,
1f,
0f
)
val colorAnim1 = TargetBasedAnimation(
tween(1000),
Color.VectorConverter(Color.Red.colorSpace),
Color.Red,
Color.Green
)
val colorAnim2 = TargetBasedAnimation(
tween(1000),
Color.VectorConverter(Color.Red.colorSpace),
Color.Green,
Color.Red,
)
// Animate from 0f to 0f for 1000ms
val keyframes1 = keyframes<Float> {
durationMillis = 1000
0f at 0
200f at 400
1000f at 1000
}
val keyframes2 = keyframes<Float> {
durationMillis = 800
0f at 0
-500f at 400
-1000f at 800
}
val keyframesAnim1 = TargetBasedAnimation(
keyframes1,
Float.VectorConverter,
0f,
0f
)
val keyframesAnim2 = TargetBasedAnimation(
keyframes2,
Float.VectorConverter,
0f,
0f
)
val animFloat = mutableStateOf(-1f)
val animColor = mutableStateOf(Color.Gray)
val animFloatWithKeyframes = mutableStateOf(-1f)
rule.setContent {
val transition = updateTransition(target.value)
animFloat.value = transition.animateFloat(
transitionSpec = {
if (AnimStates.From isTransitioningTo AnimStates.To) {
spring(dampingRatio = Spring.DampingRatioHighBouncy)
} else {
spring(
dampingRatio = Spring.DampingRatioLowBouncy,
stiffness = Spring.StiffnessLow
)
}
}
) {
when (it) {
AnimStates.From -> 0f
AnimStates.To -> 1f
}
}.value
animColor.value = transition.animateColor(
transitionSpec = { tween(durationMillis = 1000) }
) {
when (it) {
AnimStates.From -> Color.Red
AnimStates.To -> Color.Green
}
}.value
animFloatWithKeyframes.value = transition.animateFloat(
transitionSpec = {
if (AnimStates.From isTransitioningTo AnimStates.To) {
keyframes1
} else {
keyframes2
}
}
) {
// Same values for all states, but different transitions from state to state.
0f
}.value
if (transition.isRunning) {
if (transition.targetState == AnimStates.To) {
assertEquals(
floatAnim1.getValueFromNanos(transition.playTimeNanos),
animFloat.value, 0.00001f
)
assertEquals(
colorAnim1.getValueFromNanos(transition.playTimeNanos),
animColor.value
)
assertEquals(
keyframesAnim1.getValueFromNanos(transition.playTimeNanos),
animFloatWithKeyframes.value, 0.00001f
)
assertEquals(AnimStates.To, transition.segment.targetState)
assertEquals(AnimStates.From, transition.segment.initialState)
} else {
assertEquals(
floatAnim2.getValueFromNanos(transition.playTimeNanos),
animFloat.value, 0.00001f
)
assertEquals(
colorAnim2.getValueFromNanos(transition.playTimeNanos),
animColor.value
)
assertEquals(
keyframesAnim2.getValueFromNanos(transition.playTimeNanos),
animFloatWithKeyframes.value, 0.00001f
)
assertEquals(AnimStates.From, transition.segment.targetState)
assertEquals(AnimStates.To, transition.segment.initialState)
}
}
}
assertEquals(0f, animFloat.value)
assertEquals(Color.Red, animColor.value)
rule.runOnIdle {
target.value = AnimStates.To
}
rule.waitForIdle()
assertEquals(1f, animFloat.value)
assertEquals(Color.Green, animColor.value)
// Animate back to the `from` state
rule.runOnIdle {
target.value = AnimStates.From
}
rule.waitForIdle()
assertEquals(0f, animFloat.value)
assertEquals(Color.Red, animColor.value)
}
@Test
fun startPulsingNextFrameTest() {
val target = mutableStateOf(AnimStates.From)
var playTime by mutableStateOf(0L)
rule.setContent {
val transition = updateTransition(target.value)
val actual = transition.animateFloat(
transitionSpec = { tween(200) }
) {
if (it == AnimStates.From) 0f else 1000f
}
val anim = TargetBasedAnimation(tween(200), Float.VectorConverter, 0f, 1000f)
if (target.value == AnimStates.To) {
LaunchedEffect(transition) {
val startTime = withFrameNanos { it }
assertEquals(0f, actual.value)
do {
playTime = withFrameNanos { it } - startTime
assertEquals(anim.getValueFromNanos(playTime), actual.value)
} while (playTime <= 200 * MillisToNanos)
}
}
}
rule.runOnIdle {
target.value = AnimStates.To
}
rule.waitForIdle()
assertTrue(playTime > 200 * MillisToNanos)
}
@OptIn(InternalAnimationApi::class)
@Test
fun addNewAnimationInFlightTest() {
val target = mutableStateOf(AnimStates.From)
var playTime by mutableStateOf(0L)
rule.setContent {
val transition = updateTransition(target.value)
transition.animateFloat(
transitionSpec = { tween(1000) }
) {
if (it == AnimStates.From) -100f else 0f
}
if (transition.playTimeNanos > 0) {
val startTime = remember { transition.playTimeNanos }
val laterAdded = transition.animateFloat(
transitionSpec = { tween(800) }
) {
if (it == AnimStates.From) 0f else 1000f
}
val anim = TargetBasedAnimation(tween(800), Float.VectorConverter, 0f, 1000f)
playTime = transition.playTimeNanos - startTime
assertEquals(anim.getValueFromNanos(playTime), laterAdded.value)
}
}
rule.runOnIdle {
target.value = AnimStates.To
}
rule.waitForIdle()
assertTrue(playTime > 800 * MillisToNanos)
}
@Test
fun initialStateTest() {
val target = MutableTransitionState(AnimStates.From)
target.targetState = AnimStates.To
var playTime by mutableStateOf(0L)
var floatAnim: State<Float>? = null
rule.setContent {
val transition = rememberTransition(target)
floatAnim = transition.animateFloat(
transitionSpec = { tween(800) }
) {
if (it == AnimStates.From) 0f else 1000f
}
// Verify that animation starts right away
LaunchedEffect(transition) {
val startTime = withFrameNanos { it }
val anim = TargetBasedAnimation(tween(800), Float.VectorConverter, 0f, 1000f)
while (!anim.isFinishedFromNanos(playTime)) {
playTime = withFrameNanos { it } - startTime
assertEquals(anim.getValueFromNanos(playTime), floatAnim?.value)
}
}
}
rule.waitForIdle()
assertTrue(playTime >= 800 * MillisToNanos)
assertEquals(1000f, floatAnim?.value)
}
@Test
fun recreatingMutableStatesAmidTransition() {
var playTime by mutableStateOf(0L)
var targetRecreated by mutableStateOf(false)
rule.setContent {
var target by remember { mutableStateOf(MutableTransitionState(AnimStates.From)) }
target.targetState = AnimStates.To
val transition = rememberTransition(target)
val floatAnim = transition.animateFloat(
transitionSpec = { tween(800) }
) {
if (it == AnimStates.From) 0f else 1000f
}
LaunchedEffect(Unit) {
delay(100)
target = MutableTransitionState(AnimStates.From)
target.targetState = AnimStates.To
targetRecreated = true
}
if (targetRecreated) {
LaunchedEffect(transition) {
// Verify that animation restarted
assertEquals(0f, floatAnim.value)
val startTime = withFrameNanos { it }
val anim = TargetBasedAnimation(tween(800), Float.VectorConverter, 0f, 1000f)
while (!anim.isFinishedFromNanos(playTime)) {
playTime = withFrameNanos { it } - startTime
assertEquals(anim.getValueFromNanos(playTime), floatAnim.value)
}
}
}
}
rule.waitForIdle()
assertTrue(targetRecreated)
assertTrue(playTime >= 800 * MillisToNanos)
}
@OptIn(ExperimentalTransitionApi::class)
@Test
fun testMutableTransitionStateIsIdle() {
val mutableTransitionState = MutableTransitionState(false)
var transition: Transition<Boolean>? = null
rule.setContent {
transition = rememberTransition(mutableTransitionState).apply {
animateFloat {
if (it) 1f else 0f
}
}
}
rule.mainClock.autoAdvance = false
rule.runOnIdle {
assertTrue(mutableTransitionState.isIdle)
mutableTransitionState.targetState = true
assertFalse(mutableTransitionState.isIdle)
}
while (transition?.currentState != true) {
// Animation has not finished or even started from Transition's perspective
assertFalse(mutableTransitionState.isIdle)
rule.mainClock.advanceTimeByFrame()
}
assertTrue(mutableTransitionState.isIdle)
// Now that transition false -> true finished, go back to false
rule.runOnIdle {
assertTrue(mutableTransitionState.isIdle)
mutableTransitionState.targetState = false
assertFalse(mutableTransitionState.isIdle)
}
while (transition?.currentState == true) {
// Animation has not finished or even started from Transition's perspective
assertFalse(mutableTransitionState.isIdle)
rule.mainClock.advanceTimeByFrame()
}
assertTrue(mutableTransitionState.isIdle)
}
@OptIn(ExperimentalTransitionApi::class, InternalAnimationApi::class)
@Test
fun testCreateChildTransition() {
val intState = mutableStateOf(1)
val parentTransitionFloat = mutableStateOf(1f)
val childTransitionFloat = mutableStateOf(1f)
rule.setContent {
val transition = updateTransition(intState.value)
parentTransitionFloat.value = transition.animateFloat({ tween(100) }) {
when (it) {
0 -> 0f
1 -> 1f
else -> 2f
}
}.value
val booleanTransition = transition.createChildTransition {
it == 1
}
childTransitionFloat.value = booleanTransition.animateFloat({ tween(500) }) {
if (it) 1f else 0f
}.value
LaunchedEffect(intState.value) {
while (true) {
if (transition.targetState == transition.currentState) {
break
}
withFrameNanos { it }
if (intState.value == 0) {
// 1 -> 0
if (
transition.playTimeNanos > 0 && transition.playTimeNanos <= 500_000_000L
) {
assertTrue(transition.isRunning)
assertTrue(booleanTransition.isRunning)
}
} else if (intState.value == 2) {
// 0 -> 2
assertFalse(booleanTransition.isRunning)
if (transition.playTimeNanos > 120_000_000L) {
assertFalse(transition.isRunning)
} else if (transition.playTimeNanos > 0) {
assertTrue(transition.isRunning)
}
}
}
}
}
rule.runOnIdle {
assertEquals(1f, parentTransitionFloat.value)
assertEquals(1f, childTransitionFloat.value)
intState.value = 0
}
rule.runOnIdle {
assertEquals(0f, parentTransitionFloat.value)
assertEquals(0f, childTransitionFloat.value)
intState.value = 2
}
rule.runOnIdle {
assertEquals(2f, parentTransitionFloat.value)
assertEquals(0f, childTransitionFloat.value)
}
}
}