1 /* 2 * Copyright 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 @file:Suppress("NOTHING_TO_INLINE", "KotlinRedundantDiagnosticSuppress") 18 19 package androidx.compose.animation.core 20 21 import androidx.compose.animation.core.AnimationConstants.DefaultDurationMillis 22 import androidx.compose.animation.core.internal.JvmDefaultWithCompatibility 23 import androidx.compose.ui.util.fastCoerceIn 24 25 /** 26 * [FloatAnimationSpec] interface is similar to [VectorizedAnimationSpec], except it deals 27 * exclusively with floats. 28 * 29 * Like [VectorizedAnimationSpec], [FloatAnimationSpec] is entirely stateless as well. It requires 30 * start/end values and start velocity to be passed in for the query of velocity and value of the 31 * animation. The [FloatAnimationSpec] itself stores only the animation configuration (such as the 32 * delay, duration and easing curve for [FloatTweenSpec], or spring constants for [FloatSpringSpec]. 33 * 34 * A [FloatAnimationSpec] can be converted to an [VectorizedAnimationSpec] using [vectorize]. 35 * 36 * @see [VectorizedAnimationSpec] 37 */ 38 @JvmDefaultWithCompatibility 39 public interface FloatAnimationSpec : AnimationSpec<Float> { 40 /** 41 * Calculates the value of the animation at given the playtime, with the provided start/end 42 * values, and start velocity. 43 * 44 * @param playTimeNanos time since the start of the animation 45 * @param initialValue start value of the animation 46 * @param targetValue end value of the animation 47 * @param initialVelocity start velocity of the animation 48 */ getValueFromNanosnull49 public fun getValueFromNanos( 50 playTimeNanos: Long, 51 initialValue: Float, 52 targetValue: Float, 53 initialVelocity: Float 54 ): Float 55 56 /** 57 * Calculates the velocity of the animation at given the playtime, with the provided start/end 58 * values, and start velocity. 59 * 60 * @param playTimeNanos time since the start of the animation 61 * @param initialValue start value of the animation 62 * @param targetValue end value of the animation 63 * @param initialVelocity start velocity of the animation 64 */ 65 public fun getVelocityFromNanos( 66 playTimeNanos: Long, 67 initialValue: Float, 68 targetValue: Float, 69 initialVelocity: Float 70 ): Float 71 72 /** 73 * Calculates the end velocity of the animation with the provided start/end values, and start 74 * velocity. For duration-based animations, end velocity will be the velocity of the animation 75 * at the duration time. This is also the default assumption. However, for spring animations, 76 * the transient trailing velocity will be snapped to zero. 77 * 78 * @param initialValue start value of the animation 79 * @param targetValue end value of the animation 80 * @param initialVelocity start velocity of the animation 81 */ 82 public fun getEndVelocity( 83 initialValue: Float, 84 targetValue: Float, 85 initialVelocity: Float 86 ): Float = 87 getVelocityFromNanos( 88 getDurationNanos(initialValue, targetValue, initialVelocity), 89 initialValue, 90 targetValue, 91 initialVelocity 92 ) 93 94 /** 95 * Calculates the duration of an animation. For duration-based animations, this will return the 96 * pre-defined duration. For physics-based animations, the duration will be estimated based on 97 * the physics configuration (such as spring stiffness, damping ratio, visibility threshold) as 98 * well as the [initialValue], [targetValue] values, and [initialVelocity]. 99 * 100 * __Note__: this may be a computation that is expensive - especially with spring based 101 * animations 102 * 103 * @param initialValue start value of the animation 104 * @param targetValue end value of the animation 105 * @param initialVelocity start velocity of the animation 106 */ 107 @Suppress("MethodNameUnits") 108 public fun getDurationNanos( 109 initialValue: Float, 110 targetValue: Float, 111 initialVelocity: Float 112 ): Long 113 114 /** 115 * Create an [VectorizedAnimationSpec] that animates [AnimationVector] from a 116 * [FloatAnimationSpec]. Every dimension of the [AnimationVector] will be animated using the 117 * given [FloatAnimationSpec]. 118 */ 119 override fun <V : AnimationVector> vectorize( 120 converter: TwoWayConverter<Float, V> 121 ): VectorizedFloatAnimationSpec<V> = VectorizedFloatAnimationSpec<V>(this) 122 } 123 124 /** 125 * [FloatSpringSpec] animation uses a spring animation to animate a [Float] value. Its configuration 126 * can be tuned via adjusting the spring parameters, namely damping ratio and stiffness. 127 * 128 * @param dampingRatio damping ratio of the spring. Defaults to [Spring.DampingRatioNoBouncy] 129 * @param stiffness Stiffness of the spring. Defaults to [Spring.StiffnessMedium] 130 * @param visibilityThreshold The value threshold such that the animation is no longer significant. 131 * e.g. 1px for translation animations. Defaults to [Spring.DefaultDisplacementThreshold] 132 */ 133 public class FloatSpringSpec( 134 public val dampingRatio: Float = Spring.DampingRatioNoBouncy, 135 public val stiffness: Float = Spring.StiffnessMedium, 136 private val visibilityThreshold: Float = Spring.DefaultDisplacementThreshold 137 ) : FloatAnimationSpec { 138 139 private val spring = 140 SpringSimulation(1f).also { 141 it.dampingRatio = dampingRatio 142 it.stiffness = stiffness 143 } 144 145 override fun getValueFromNanos( 146 playTimeNanos: Long, 147 initialValue: Float, 148 targetValue: Float, 149 initialVelocity: Float 150 ): Float { 151 // TODO: Properly support Nanos in the spring impl 152 val playTimeMillis = playTimeNanos / MillisToNanos 153 spring.finalPosition = targetValue 154 return spring.updateValues(initialValue, initialVelocity, playTimeMillis).value 155 } 156 157 override fun getVelocityFromNanos( 158 playTimeNanos: Long, 159 initialValue: Float, 160 targetValue: Float, 161 initialVelocity: Float 162 ): Float { 163 // TODO: Properly support Nanos in the spring impl 164 val playTimeMillis = playTimeNanos / MillisToNanos 165 spring.finalPosition = targetValue 166 return spring.updateValues(initialValue, initialVelocity, playTimeMillis).velocity 167 } 168 169 override fun getEndVelocity( 170 initialValue: Float, 171 targetValue: Float, 172 initialVelocity: Float 173 ): Float = 0f 174 175 @Suppress("MethodNameUnits") 176 override fun getDurationNanos( 177 initialValue: Float, 178 targetValue: Float, 179 initialVelocity: Float 180 ): Long = 181 estimateAnimationDurationMillis( 182 stiffness = spring.stiffness, 183 dampingRatio = spring.dampingRatio, 184 initialDisplacement = (initialValue - targetValue) / visibilityThreshold, 185 initialVelocity = initialVelocity / visibilityThreshold, 186 delta = 1f 187 ) * MillisToNanos 188 } 189 190 /** 191 * [FloatTweenSpec] animates a Float value from any start value to any end value using a provided 192 * [easing] function. The animation will finish within the [duration] time. Unless a [delay] is 193 * specified, the animation will start right away. 194 * 195 * @param duration the amount of time (in milliseconds) the animation will take to finish. Defaults 196 * to [DefaultDurationMillis] 197 * @param delay the amount of time the animation will wait before it starts running. Defaults to 0. 198 * @param easing the easing function that will be used to interoplate between the start and end 199 * value of the animation. Defaults to [FastOutSlowInEasing]. 200 */ 201 public class FloatTweenSpec( 202 public val duration: Int = DefaultDurationMillis, 203 public val delay: Int = 0, 204 private val easing: Easing = FastOutSlowInEasing 205 ) : FloatAnimationSpec { 206 private val durationNanos: Long = duration * MillisToNanos 207 208 private val delayNanos: Long = delay * MillisToNanos 209 getValueFromNanosnull210 override fun getValueFromNanos( 211 playTimeNanos: Long, 212 initialValue: Float, 213 targetValue: Float, 214 initialVelocity: Float 215 ): Float { 216 val clampedPlayTimeNanos = clampPlayTimeNanos(playTimeNanos) 217 val rawFraction = if (duration == 0) 1f else clampedPlayTimeNanos / durationNanos.toFloat() 218 val fraction = easing.transform(rawFraction) 219 return lerp(initialValue, targetValue, fraction) 220 } 221 clampPlayTimeNanosnull222 private inline fun clampPlayTimeNanos(playTimeNanos: Long): Long { 223 return (playTimeNanos - delayNanos).fastCoerceIn(0, durationNanos) 224 } 225 226 @Suppress("MethodNameUnits") getDurationNanosnull227 override fun getDurationNanos( 228 initialValue: Float, 229 targetValue: Float, 230 initialVelocity: Float 231 ): Long { 232 return delayNanos + durationNanos 233 } 234 235 // Calculate velocity by difference between the current value and the value 1 ms ago. This is a 236 // preliminary way of calculating velocity used by easing curve based animations, and keyframe 237 // animations. Physics-based animations give a much more accurate velocity. getVelocityFromNanosnull238 override fun getVelocityFromNanos( 239 playTimeNanos: Long, 240 initialValue: Float, 241 targetValue: Float, 242 initialVelocity: Float 243 ): Float { 244 val clampedPlayTimeNanos = clampPlayTimeNanos(playTimeNanos) 245 if (clampedPlayTimeNanos == 0L) { 246 return initialVelocity 247 } 248 val startNum = 249 getValueFromNanos( 250 clampedPlayTimeNanos - MillisToNanos, 251 initialValue, 252 targetValue, 253 initialVelocity 254 ) 255 val endNum = 256 getValueFromNanos(clampedPlayTimeNanos, initialValue, targetValue, initialVelocity) 257 return (endNum - startNum) * 1000f 258 } 259 } 260