[go: nahoru, domu]

Adds Elevation state animation and applies to Buttons.

Relnote: Adds Elevation for animating elevation state changes and applies it to all Buttons with elevation.
Test: To be added in follow-up. Tested locally with mouse.
Change-Id: I3fed2eb1e17af63e2d7d8ad5f23b4eec7bc2706c
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Elevation.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Elevation.kt
new file mode 100644
index 0000000..b104e20
--- /dev/null
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Elevation.kt
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2021 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.material3
+
+import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.AnimationSpec
+import androidx.compose.animation.core.CubicBezierEasing
+import androidx.compose.animation.core.Easing
+import androidx.compose.animation.core.FastOutSlowInEasing
+import androidx.compose.animation.core.TweenSpec
+import androidx.compose.foundation.interaction.DragInteraction
+import androidx.compose.foundation.interaction.FocusInteraction
+import androidx.compose.foundation.interaction.HoverInteraction
+import androidx.compose.foundation.interaction.Interaction
+import androidx.compose.foundation.interaction.PressInteraction
+import androidx.compose.ui.unit.Dp
+
+/**
+ * Animates the [Dp] value of [this] between [from] and [to] [Interaction]s, to [target]. The
+ * [AnimationSpec] used depends on the values for [from] and [to], see
+ * [ElevationDefaults.incomingAnimationSpecForInteraction] and
+ * [ElevationDefaults.outgoingAnimationSpecForInteraction] for more details.
+ *
+ * @param target the [Dp] target elevation for this component, corresponding to the elevation
+ * desired for the [to] state.
+ * @param from the previous [Interaction] that was used to calculate elevation. `null` if there
+ * was no previous [Interaction], such as when the component is in its default state.
+ * @param to the [Interaction] that this component is moving to, such as [PressInteraction.Press]
+ * when this component is being pressed. `null` if this component is moving back to its default
+ * state.
+ */
+internal suspend fun Animatable<Dp, *>.animateElevation(
+    target: Dp,
+    from: Interaction? = null,
+    to: Interaction? = null
+) {
+    val spec = when {
+        // Moving to a new state
+        to != null -> ElevationDefaults.incomingAnimationSpecForInteraction(to)
+        // Moving to default, from a previous state
+        from != null -> ElevationDefaults.outgoingAnimationSpecForInteraction(from)
+        // Loading the initial state, or moving back to the baseline state from a disabled /
+        // unknown state, so just snap to the final value.
+        else -> null
+    }
+    if (spec != null) animateTo(target, spec) else snapTo(target)
+}
+
+/**
+ * Contains default [AnimationSpec]s used for animating elevation between different [Interaction]s.
+ *
+ * Typically you should use [animateElevation] instead, which uses these [AnimationSpec]s
+ * internally. [animateElevation] in turn is used by the defaults for cards and buttons.
+ *
+ * @see animateElevation
+ */
+private object ElevationDefaults {
+    /**
+     * Returns the [AnimationSpec]s used when animating elevation to [interaction], either from a
+     * previous [Interaction], or from the default state. If [interaction] is unknown, then
+     * returns `null`.
+     *
+     * @param interaction the [Interaction] that is being animated to
+     */
+    fun incomingAnimationSpecForInteraction(interaction: Interaction): AnimationSpec<Dp>? {
+        return when (interaction) {
+            is PressInteraction.Press -> DefaultIncomingSpec
+            is DragInteraction.Start -> DefaultIncomingSpec
+            is HoverInteraction.Enter -> DefaultIncomingSpec
+            is FocusInteraction.Focus -> DefaultIncomingSpec
+            else -> null
+        }
+    }
+
+    /**
+     * Returns the [AnimationSpec]s used when animating elevation away from [interaction], to the
+     * default state. If [interaction] is unknown, then returns `null`.
+     *
+     * @param interaction the [Interaction] that is being animated away from
+     */
+    fun outgoingAnimationSpecForInteraction(interaction: Interaction): AnimationSpec<Dp>? {
+        return when (interaction) {
+            is PressInteraction.Press -> DefaultOutgoingSpec
+            is DragInteraction.Start -> DefaultOutgoingSpec
+            is HoverInteraction.Enter -> HoveredOutgoingSpec
+            is FocusInteraction.Focus -> DefaultOutgoingSpec
+            else -> null
+        }
+    }
+}
+
+private val OutgoingSpecEasing: Easing = CubicBezierEasing(0.40f, 0.00f, 0.60f, 1.00f)
+
+private val DefaultIncomingSpec = TweenSpec<Dp>(
+    durationMillis = 120,
+    easing = FastOutSlowInEasing
+)
+
+private val DefaultOutgoingSpec = TweenSpec<Dp>(
+    durationMillis = 150,
+    easing = OutgoingSpecEasing
+)
+
+private val HoveredOutgoingSpec = TweenSpec<Dp>(
+    durationMillis = 120,
+    easing = OutgoingSpecEasing
+)