[go: nahoru, domu]

blob: 20412802a43a2399dda220f90ade1a678f07cf82 [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
import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.animation.core.LinearOutSlowInEasing
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.requiredSize
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.captureToImage
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.lerp
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
@LargeTest
@OptIn(ExperimentalTestApi::class)
class AnimatedVisibilityTest {
@get:Rule
val rule = createComposeRule()
private val frameDuration = 16
@OptIn(ExperimentalAnimationApi::class)
@Test
fun animateVisibilityExpandShrinkTest() {
val testModifier by mutableStateOf(TestModifier())
var visible by mutableStateOf(false)
var offset by mutableStateOf(Offset(0f, 0f))
var disposed by mutableStateOf(false)
rule.mainClock.autoAdvance = false
rule.setContent {
CompositionLocalProvider(LocalDensity provides Density(1f)) {
AnimatedVisibility(
visible, testModifier,
enter = expandIn(
Alignment.BottomEnd,
{ fullSize -> IntSize(fullSize.width / 4, fullSize.height / 2) },
tween(160, easing = LinearOutSlowInEasing)
),
exit = shrinkOut(
Alignment.CenterStart,
{ fullSize -> IntSize(fullSize.width / 10, fullSize.height / 5) },
tween(160, easing = FastOutSlowInEasing)
)
) {
Box(
Modifier.onGloballyPositioned {
offset = it.localToRoot(Offset.Zero)
}.requiredSize(100.dp, 100.dp)
) {
DisposableEffect(Unit) {
onDispose {
disposed = true
}
}
}
}
}
}
rule.runOnIdle {
visible = true
}
rule.mainClock.advanceTimeByFrame()
rule.mainClock.advanceTimeByFrame()
val startWidth = 100 / 4f
val startHeight = 100 / 2f
val fullSize = 100f
assertFalse(disposed)
for (i in 0..160 step frameDuration) {
val fraction = LinearOutSlowInEasing.transform(i / 160f)
val animWidth = lerp(startWidth, fullSize, fraction)
val animHeight = lerp(startHeight, fullSize, fraction)
// Check size
assertEquals(animWidth, testModifier.width.toFloat(), 2f)
assertEquals(animHeight, testModifier.height.toFloat(), 2f)
// Check offset
assertEquals(animWidth - fullSize, offset.x, 2f)
assertEquals(animHeight - fullSize, offset.y, 2f)
rule.mainClock.advanceTimeBy(frameDuration.toLong())
rule.waitForIdle()
}
rule.runOnIdle {
visible = false
}
rule.mainClock.advanceTimeByFrame()
rule.mainClock.advanceTimeByFrame()
val endWidth = 100 / 10f
val endHeight = 100 / 5f
for (i in 0..160 step frameDuration) {
val fraction = FastOutSlowInEasing.transform(i / 160f)
val animWidth = lerp(fullSize, endWidth, fraction)
val animHeight = lerp(fullSize, endHeight, fraction)
// Check size
assertEquals(animWidth, testModifier.width.toFloat(), 2f)
assertEquals(animHeight, testModifier.height.toFloat(), 2f)
// Check offset
assertEquals(0f, offset.x, 2f)
assertEquals((animHeight - fullSize) / 2f, offset.y, 2f)
rule.mainClock.advanceTimeBy(frameDuration.toLong())
rule.waitForIdle()
}
// Check that the composable children in AnimatedVisibility are skipped after exit animation
rule.mainClock.autoAdvance = true
rule.waitUntil { disposed }
rule.mainClock.autoAdvance = false
// Make it visible again, and test that it behaves the same as before
rule.runOnIdle {
visible = true
}
rule.mainClock.advanceTimeByFrame()
rule.mainClock.advanceTimeByFrame()
for (i in 0..160 step frameDuration) {
val fraction = LinearOutSlowInEasing.transform(i / 160f)
val animWidth = lerp(startWidth, fullSize, fraction)
val animHeight = lerp(startHeight, fullSize, fraction)
// Check size
assertEquals(animWidth, testModifier.width.toFloat(), 2f)
assertEquals(animHeight, testModifier.height.toFloat(), 2f)
// Check offset
assertEquals(animWidth - fullSize, offset.x, 2f)
assertEquals(animHeight - fullSize, offset.y, 2f)
rule.mainClock.advanceTimeBy(frameDuration.toLong())
rule.waitForIdle()
}
}
@OptIn(ExperimentalAnimationApi::class)
@Test
fun animateVisibilitySlideTest() {
val testModifier by mutableStateOf(TestModifier())
var visible by mutableStateOf(false)
var density = 0f
var offset by mutableStateOf(Offset(0f, 0f))
var disposed by mutableStateOf(false)
rule.mainClock.autoAdvance = false
rule.setContent {
AnimatedVisibility(
visible, testModifier,
enter = slideIn(
{ fullSize -> IntOffset(fullSize.width / 4, -fullSize.height / 2) },
tween(160, easing = LinearOutSlowInEasing)
),
exit = slideOut(
{ fullSize -> IntOffset(-fullSize.width / 10, fullSize.height / 5) },
tween(160, easing = FastOutSlowInEasing)
)
) {
Box(
Modifier.onGloballyPositioned {
offset = it.localToRoot(Offset.Zero)
}.requiredSize(100.dp, 100.dp)
) {
DisposableEffect(Unit) {
onDispose {
disposed = true
}
}
}
}
density = LocalDensity.current.density
}
rule.runOnIdle {
visible = true
}
rule.mainClock.advanceTimeByFrame()
rule.mainClock.advanceTimeByFrame()
val startX = density * 100 / 4f
val startY = -density * 100 / 2f
val fullSize = density * 100
assertFalse(disposed)
for (i in 0..160 step frameDuration) {
val fraction = LinearOutSlowInEasing.transform(i / 160f)
val animX = lerp(startX, 0f, fraction)
val animY = lerp(startY, 0f, fraction)
// Check size
assertEquals(fullSize, testModifier.width.toFloat(), 2f)
assertEquals(fullSize, testModifier.height.toFloat(), 2f)
// Check offset
assertEquals(animX, offset.x, 2f)
assertEquals(animY, offset.y, 2f)
rule.mainClock.advanceTimeBy(frameDuration.toLong())
rule.waitForIdle()
}
rule.runOnIdle {
visible = false
}
rule.mainClock.advanceTimeByFrame()
rule.mainClock.advanceTimeByFrame()
val endX = -density * 100 / 10f
val endY = density * 100 / 5f
for (i in 0..160 step frameDuration) {
val fraction = FastOutSlowInEasing.transform(i / 160f)
val animX = lerp(0f, endX, fraction)
val animY = lerp(0f, endY, fraction)
// Check size
assertEquals(fullSize, testModifier.width.toFloat(), 2f)
assertEquals(fullSize, testModifier.height.toFloat(), 2f)
// Check offset
assertEquals(animX, offset.x, 2f)
assertEquals(animY, offset.y, 2f)
rule.mainClock.advanceTimeBy(frameDuration.toLong())
rule.waitForIdle()
}
// Check that the composable children in AnimatedVisibility are skipped after exit animation
rule.mainClock.autoAdvance = true
rule.waitUntil { disposed }
rule.mainClock.autoAdvance = false
// Make it visible again, and test that it behaves the same as before
rule.runOnIdle {
visible = true
}
rule.mainClock.advanceTimeByFrame()
rule.mainClock.advanceTimeByFrame()
for (i in 0..160 step frameDuration) {
val fraction = LinearOutSlowInEasing.transform(i / 160f)
val animX = lerp(startX, 0f, fraction)
val animY = lerp(startY, 0f, fraction)
// Check size
assertEquals(fullSize, testModifier.width.toFloat(), 2f)
assertEquals(fullSize, testModifier.height.toFloat(), 2f)
// Check offset
assertEquals(animX, offset.x, 2f)
assertEquals(animY, offset.y, 2f)
rule.mainClock.advanceTimeBy(frameDuration.toLong())
rule.waitForIdle()
}
}
@OptIn(ExperimentalAnimationApi::class)
@Test
fun animateVisibilityContentSizeChangeTest() {
val size = mutableStateOf(40.dp)
val testModifier by mutableStateOf(TestModifier())
var visible by mutableStateOf(true)
rule.setContent {
CompositionLocalProvider(LocalDensity provides Density(1f)) {
AnimatedVisibility(visible, testModifier) {
Box(modifier = Modifier.size(size = size.value))
}
}
}
rule.runOnIdle {
assertEquals(40, testModifier.height)
assertEquals(40, testModifier.width)
size.value = 60.dp
}
rule.runOnIdle {
assertEquals(60, testModifier.height)
assertEquals(60, testModifier.width)
}
rule.runOnIdle {
visible = false
}
rule.runOnIdle {
visible = true
}
rule.runOnIdle {
assertEquals(60, testModifier.height)
assertEquals(60, testModifier.width)
size.value = 30.dp
}
rule.runOnIdle {
assertEquals(30, testModifier.height)
assertEquals(30, testModifier.width)
}
}
@OptIn(ExperimentalAnimationApi::class)
@Test
fun animateVisibilityFadeTest() {
var visible by mutableStateOf(false)
val colors = mutableListOf<Int>()
rule.setContent {
Box(Modifier.size(size = 20.dp).background(Color.Black)) {
AnimatedVisibility(
visible,
enter = fadeIn(animationSpec = tween(500)),
exit = fadeOut(animationSpec = tween(500)),
modifier = Modifier.testTag("AnimV")
) {
Box(modifier = Modifier.size(size = 20.dp).background(Color.White))
}
}
}
rule.runOnIdle {
visible = true
}
rule.mainClock.autoAdvance = false
while (colors.isEmpty() || colors.last() != 0xffffffff.toInt()) {
rule.mainClock.advanceTimeByFrame()
rule.onNodeWithTag("AnimV").apply {
val data = IntArray(1)
data[0] = 0
captureToImage().readPixels(data, 10, 10, 1, 1)
colors.add(data[0])
}
}
for (i in 1 until colors.size) {
// Check every color against the previous one to ensure the alpha is non-decreasing
// during fade in.
assertTrue(colors[i] >= colors[i - 1])
}
assertTrue(colors[0] < 0xfffffffff)
colors.clear()
rule.runOnIdle {
visible = false
}
while (colors.isEmpty() || colors.last() != 0xff000000.toInt()) {
rule.mainClock.advanceTimeByFrame()
rule.onNodeWithTag("AnimV").apply {
val data = IntArray(1)
data[0] = 0
captureToImage().readPixels(data, 10, 10, 1, 1)
colors.add(data[0])
}
}
for (i in 1 until colors.size) {
// Check every color against the previous one to ensure the alpha is non-increasing
// during fade out.
assertTrue(colors[i] <= colors[i - 1])
}
}
}