1 /* <lambda>null2 * Copyright 2023 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 package com.android.compose.animation.scene 18 19 import androidx.compose.animation.core.AnimationSpec 20 import androidx.compose.animation.core.DurationBasedAnimationSpec 21 import androidx.compose.animation.core.Easing 22 import androidx.compose.animation.core.Spring 23 import androidx.compose.animation.core.SpringSpec 24 import androidx.compose.animation.core.VectorConverter 25 import androidx.compose.animation.core.snap 26 import androidx.compose.animation.core.spring 27 import androidx.compose.foundation.gestures.Orientation 28 import androidx.compose.ui.geometry.Offset 29 import androidx.compose.ui.unit.Dp 30 import com.android.compose.animation.scene.content.state.TransitionState 31 import com.android.compose.animation.scene.transformation.AnchoredSize 32 import com.android.compose.animation.scene.transformation.AnchoredTranslate 33 import com.android.compose.animation.scene.transformation.DrawScale 34 import com.android.compose.animation.scene.transformation.EdgeTranslate 35 import com.android.compose.animation.scene.transformation.Fade 36 import com.android.compose.animation.scene.transformation.OverscrollTranslate 37 import com.android.compose.animation.scene.transformation.ScaleSize 38 import com.android.compose.animation.scene.transformation.SharedElementTransformation 39 import com.android.compose.animation.scene.transformation.Transformation 40 import com.android.compose.animation.scene.transformation.TransformationMatcher 41 import com.android.compose.animation.scene.transformation.TransformationRange 42 import com.android.compose.animation.scene.transformation.Translate 43 44 internal fun transitionsImpl(builder: SceneTransitionsBuilder.() -> Unit): SceneTransitions { 45 val impl = SceneTransitionsBuilderImpl().apply(builder) 46 return SceneTransitions( 47 impl.defaultSwipeSpec, 48 impl.transitionSpecs, 49 impl.transitionOverscrollSpecs, 50 impl.interruptionHandler, 51 impl.defaultOverscrollProgressConverter, 52 ) 53 } 54 55 private class SceneTransitionsBuilderImpl : SceneTransitionsBuilder { 56 override var defaultSwipeSpec: SpringSpec<Float> = SceneTransitions.DefaultSwipeSpec 57 override var interruptionHandler: InterruptionHandler = DefaultInterruptionHandler 58 override var defaultOverscrollProgressConverter: ProgressConverter = ProgressConverter.Default 59 60 val transitionSpecs = mutableListOf<TransitionSpecImpl>() 61 val transitionOverscrollSpecs = mutableListOf<OverscrollSpecImpl>() 62 tonull63 override fun to( 64 to: ContentKey, 65 key: TransitionKey?, 66 preview: (TransitionBuilder.() -> Unit)?, 67 reversePreview: (TransitionBuilder.() -> Unit)?, 68 builder: TransitionBuilder.() -> Unit, 69 ) { 70 transition(from = null, to = to, key = key, preview, reversePreview, builder) 71 } 72 fromnull73 override fun from( 74 from: ContentKey, 75 to: ContentKey?, 76 key: TransitionKey?, 77 preview: (TransitionBuilder.() -> Unit)?, 78 reversePreview: (TransitionBuilder.() -> Unit)?, 79 builder: TransitionBuilder.() -> Unit, 80 ) { 81 transition(from = from, to = to, key = key, preview, reversePreview, builder) 82 } 83 overscrollnull84 override fun overscroll( 85 content: ContentKey, 86 orientation: Orientation, 87 builder: OverscrollBuilder.() -> Unit, 88 ) { 89 val impl = OverscrollBuilderImpl().apply(builder) 90 check(impl.transformationMatchers.isNotEmpty()) { 91 "This method does not allow empty transformations. " + 92 "Use overscrollDisabled($content, $orientation) instead." 93 } 94 overscrollSpec(content, orientation, impl) 95 } 96 overscrollDisablednull97 override fun overscrollDisabled(content: ContentKey, orientation: Orientation) { 98 overscrollSpec(content, orientation, OverscrollBuilderImpl()) 99 } 100 overscrollSpecnull101 private fun overscrollSpec( 102 content: ContentKey, 103 orientation: Orientation, 104 impl: OverscrollBuilderImpl, 105 ): OverscrollSpec { 106 val spec = 107 OverscrollSpecImpl( 108 content = content, 109 orientation = orientation, 110 transformationSpec = 111 TransformationSpecImpl( 112 progressSpec = snap(), 113 swipeSpec = null, 114 distance = impl.distance, 115 transformationMatchers = impl.transformationMatchers, 116 ), 117 progressConverter = impl.progressConverter, 118 ) 119 transitionOverscrollSpecs.add(spec) 120 return spec 121 } 122 transitionnull123 private fun transition( 124 from: ContentKey?, 125 to: ContentKey?, 126 key: TransitionKey?, 127 preview: (TransitionBuilder.() -> Unit)?, 128 reversePreview: (TransitionBuilder.() -> Unit)?, 129 builder: TransitionBuilder.() -> Unit, 130 ): TransitionSpec { 131 fun transformationSpec( 132 transition: TransitionState.Transition, 133 builder: TransitionBuilder.() -> Unit, 134 ): TransformationSpecImpl { 135 val impl = TransitionBuilderImpl(transition).apply(builder) 136 return TransformationSpecImpl( 137 progressSpec = impl.spec, 138 swipeSpec = impl.swipeSpec, 139 distance = impl.distance, 140 transformationMatchers = impl.transformationMatchers, 141 ) 142 } 143 144 val spec = 145 TransitionSpecImpl( 146 key, 147 from, 148 to, 149 previewTransformationSpec = preview?.let { { t -> transformationSpec(t, it) } }, 150 reversePreviewTransformationSpec = 151 reversePreview?.let { { t -> transformationSpec(t, it) } }, 152 transformationSpec = { t -> transformationSpec(t, builder) }, 153 ) 154 transitionSpecs.add(spec) 155 return spec 156 } 157 } 158 159 internal abstract class BaseTransitionBuilderImpl : BaseTransitionBuilder { 160 val transformationMatchers = mutableListOf<TransformationMatcher>() 161 private var range: TransformationRange? = null 162 protected var reversed = false 163 override var distance: UserActionDistance? = null 164 fractionRangenull165 override fun fractionRange( 166 start: Float?, 167 end: Float?, 168 easing: Easing, 169 builder: PropertyTransformationBuilder.() -> Unit, 170 ) { 171 range = TransformationRange(start, end, easing) 172 builder() 173 range = null 174 } 175 addTransformationnull176 protected fun addTransformation( 177 matcher: ElementMatcher, 178 transformation: Transformation.Factory, 179 ) { 180 transformationMatchers.add( 181 TransformationMatcher( 182 matcher, 183 transformation, 184 range?.let { range -> 185 if (reversed) { 186 range.reversed() 187 } else { 188 range 189 } 190 }, 191 ) 192 ) 193 } 194 fadenull195 override fun fade(matcher: ElementMatcher) { 196 addTransformation(matcher, Fade.Factory) 197 } 198 translatenull199 override fun translate(matcher: ElementMatcher, x: Dp, y: Dp) { 200 addTransformation(matcher, Translate.Factory(x, y)) 201 } 202 translatenull203 override fun translate( 204 matcher: ElementMatcher, 205 edge: Edge, 206 startsOutsideLayoutBounds: Boolean, 207 ) { 208 addTransformation(matcher, EdgeTranslate.Factory(edge, startsOutsideLayoutBounds)) 209 } 210 anchoredTranslatenull211 override fun anchoredTranslate(matcher: ElementMatcher, anchor: ElementKey) { 212 addTransformation(matcher, AnchoredTranslate.Factory(anchor)) 213 } 214 scaleSizenull215 override fun scaleSize(matcher: ElementMatcher, width: Float, height: Float) { 216 addTransformation(matcher, ScaleSize.Factory(width, height)) 217 } 218 scaleDrawnull219 override fun scaleDraw(matcher: ElementMatcher, scaleX: Float, scaleY: Float, pivot: Offset) { 220 addTransformation(matcher, DrawScale.Factory(scaleX, scaleY, pivot)) 221 } 222 anchoredSizenull223 override fun anchoredSize( 224 matcher: ElementMatcher, 225 anchor: ElementKey, 226 anchorWidth: Boolean, 227 anchorHeight: Boolean, 228 ) { 229 addTransformation(matcher, AnchoredSize.Factory(anchor, anchorWidth, anchorHeight)) 230 } 231 transformationnull232 override fun transformation(matcher: ElementMatcher, transformation: Transformation.Factory) { 233 check(range == null) { "Custom transformations can not be applied inside a range" } 234 addTransformation(matcher, transformation) 235 } 236 } 237 238 internal class TransitionBuilderImpl(override val transition: TransitionState.Transition) : 239 BaseTransitionBuilderImpl(), TransitionBuilder { 240 override var spec: AnimationSpec<Float> = spring(stiffness = Spring.StiffnessLow) 241 override var swipeSpec: SpringSpec<Float>? = null 242 override var distance: UserActionDistance? = null <lambda>null243 private val durationMillis: Int by lazy { 244 val spec = spec 245 if (spec !is DurationBasedAnimationSpec) { 246 error("timestampRange {} can only be used with a DurationBasedAnimationSpec") 247 } 248 249 spec.vectorize(Float.VectorConverter).durationMillis 250 } 251 reversednull252 override fun reversed(builder: TransitionBuilder.() -> Unit) { 253 reversed = true 254 builder() 255 reversed = false 256 } 257 sharedElementnull258 override fun sharedElement( 259 matcher: ElementMatcher, 260 enabled: Boolean, 261 elevateInContent: ContentKey?, 262 ) { 263 check( 264 elevateInContent == null || 265 elevateInContent == transition.fromContent || 266 elevateInContent == transition.toContent 267 ) { 268 "elevateInContent (${elevateInContent?.debugName}) should be either fromContent " + 269 "(${transition.fromContent.debugName}) or toContent " + 270 "(${transition.toContent.debugName})" 271 } 272 273 addTransformation( 274 matcher, 275 SharedElementTransformation.Factory(matcher, enabled, elevateInContent), 276 ) 277 } 278 timestampRangenull279 override fun timestampRange( 280 startMillis: Int?, 281 endMillis: Int?, 282 easing: Easing, 283 builder: PropertyTransformationBuilder.() -> Unit, 284 ) { 285 if (startMillis != null && (startMillis < 0 || startMillis > durationMillis)) { 286 error("invalid start value: startMillis=$startMillis durationMillis=$durationMillis") 287 } 288 289 if (endMillis != null && (endMillis < 0 || endMillis > durationMillis)) { 290 error("invalid end value: endMillis=$startMillis durationMillis=$durationMillis") 291 } 292 293 val start = startMillis?.let { it.toFloat() / durationMillis } 294 val end = endMillis?.let { it.toFloat() / durationMillis } 295 fractionRange(start, end, easing, builder) 296 } 297 } 298 299 internal open class OverscrollBuilderImpl : BaseTransitionBuilderImpl(), OverscrollBuilder { 300 override var progressConverter: ProgressConverter? = null 301 translatenull302 override fun translate( 303 matcher: ElementMatcher, 304 x: OverscrollScope.() -> Float, 305 y: OverscrollScope.() -> Float, 306 ) { 307 addTransformation(matcher, OverscrollTranslate.Factory(x, y)) 308 } 309 } 310