Add open/close animation for material menus
Test: only manual visual testing
Bug: 156230937
Change-Id: I672ace24e52fd8e1582fe38a328542a2f1c947a8
diff --git a/ui/ui-material/src/androidTest/java/androidx/ui/material/MenuTest.kt b/ui/ui-material/src/androidTest/java/androidx/ui/material/MenuTest.kt
index fd7cfb4..9fd340d3 100644
--- a/ui/ui-material/src/androidTest/java/androidx/ui/material/MenuTest.kt
+++ b/ui/ui-material/src/androidTest/java/androidx/ui/material/MenuTest.kt
@@ -37,6 +37,7 @@
import androidx.ui.test.hasTestTag
import androidx.ui.test.isPopup
import androidx.ui.test.runOnIdleCompose
+import androidx.ui.test.waitForIdle
import androidx.ui.unit.dp
import com.google.common.truth.Truth.assertThat
import org.junit.Rule
@@ -48,12 +49,13 @@
@RunWith(JUnit4::class)
class MenuTest {
@get:Rule
- val composeTestRule = createComposeRule(disableTransitions = true)
+ val composeTestRule = createComposeRule()
@Test
fun menu_canBeTriggered() {
var expanded by mutableStateOf(false)
+ composeTestRule.clockTestRule.pauseClock()
composeTestRule.setContent {
DropdownMenu(
expanded = expanded,
@@ -69,13 +71,21 @@
}
}
}
+ findByTag("MenuContent").assertDoesNotExist()
- findByTag("MenuContent").assertDoesNotExist()
runOnIdleCompose { expanded = true }
+ waitForIdle()
+ composeTestRule.clockTestRule.advanceClock(InTransitionDuration.toLong())
findByTag("MenuContent").assertExists()
+
runOnIdleCompose { expanded = false }
+ waitForIdle()
+ composeTestRule.clockTestRule.advanceClock(OutTransitionDuration.toLong())
findByTag("MenuContent").assertDoesNotExist()
+
runOnIdleCompose { expanded = true }
+ waitForIdle()
+ composeTestRule.clockTestRule.advanceClock(InTransitionDuration.toLong())
findByTag("MenuContent").assertExists()
}
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/Menu.kt b/ui/ui-material/src/main/java/androidx/ui/material/Menu.kt
index 86e37bf..a0715098 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/Menu.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/Menu.kt
@@ -16,10 +16,18 @@
package androidx.ui.material
+import androidx.animation.FloatPropKey
+import androidx.animation.LinearOutSlowInEasing
+import androidx.animation.transitionDefinition
import androidx.compose.Composable
+import androidx.compose.getValue
+import androidx.compose.setValue
+import androidx.compose.state
+import androidx.ui.animation.Transition
import androidx.ui.core.DensityAmbient
import androidx.ui.core.DropdownPopup
import androidx.ui.core.Modifier
+import androidx.ui.core.drawLayer
import androidx.ui.foundation.Box
import androidx.ui.foundation.ContentGravity
import androidx.ui.foundation.ProvideTextStyle
@@ -63,10 +71,13 @@
dropdownModifier: Modifier = Modifier,
dropdownContent: @Composable ColumnScope.() -> Unit
) {
+ var visibleMenu by state { expanded }
+ if (expanded) visibleMenu = true
+
Box(toggleModifier) {
toggle()
- if (expanded) {
+ if (visibleMenu) {
DropdownPopup(
isFocusable = true,
>
@@ -76,17 +87,30 @@
IntPxPosition(-MenuElevation.toIntPx(), -MenuElevation.toIntPx())
}
) {
- Card(
- // Padding to account for the elevation, otherwise it is clipped.
- modifier = Modifier.padding(MenuElevation),
- elevation = MenuElevation
- ) {
- Column(
- dropdownModifier
- .padding(vertical = DropdownMenuVerticalPadding)
- .preferredWidth(IntrinsicSize.Max),
- children = dropdownContent
- )
+ Transition(
+ definition = DropdownMenuOpenCloseTransition,
+ initState = !expanded,
+ toState = expanded,
+ >
+ visibleMenu = it
+ }
+ ) { state ->
+ val scale = state[Scale]
+ val alpha = state[Alpha]
+ Card(
+ modifier = Modifier
+ .drawLayer(scaleX = scale, scaleY = scale, alpha = alpha, clip = true)
+ // Padding to account for the elevation, otherwise it is clipped.
+ .padding(MenuElevation),
+ elevation = MenuElevation
+ ) {
+ Column(
+ dropdownModifier
+ .padding(vertical = DropdownMenuVerticalPadding)
+ .preferredWidth(IntrinsicSize.Max),
+ children = dropdownContent
+ )
+ }
}
}
}
@@ -141,3 +165,41 @@
internal val DropdownMenuItemDefaultMinWidth = 112.dp
internal val DropdownMenuItemDefaultMaxWidth = 280.dp
internal val DropdownMenuItemDefaultMinHeight = 48.dp
+
+private val Scale = FloatPropKey()
+private val Alpha = FloatPropKey()
+internal val InTransitionDuration = 120
+internal val OutTransitionDuration = 75
+
+private val DropdownMenuOpenCloseTransition = transitionDefinition {
+ state(false) {
+ // Menu is dismissed.
+ this[Scale] = 0f
+ this[Alpha] = 0f
+ }
+ state(true) {
+ // Menu is expanded.
+ this[Scale] = 1f
+ this[Alpha] = 1f
+ }
+ transition(false, true) {
+ // Dismissed to expanded.
+ Scale using tween {
+ duration = InTransitionDuration
+ easing = LinearOutSlowInEasing
+ }
+ Alpha using tween {
+ duration = 30
+ }
+ }
+ transition(true, false) {
+ // Expanded to dismissed.
+ Scale using tween {
+ duration = 1
+ delay = OutTransitionDuration - 1
+ }
+ Alpha using tween {
+ duration = OutTransitionDuration
+ }
+ }
+}