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.VectorConverter 23 import androidx.compose.ui.geometry.Offset 24 import androidx.compose.ui.unit.Dp 25 import com.android.compose.animation.scene.content.state.TransitionState 26 import com.android.compose.animation.scene.transformation.AnchoredSize 27 import com.android.compose.animation.scene.transformation.AnchoredTranslate 28 import com.android.compose.animation.scene.transformation.DrawScale 29 import com.android.compose.animation.scene.transformation.EdgeTranslate 30 import com.android.compose.animation.scene.transformation.Fade 31 import com.android.compose.animation.scene.transformation.ScaleSize 32 import com.android.compose.animation.scene.transformation.SharedElementTransformation 33 import com.android.compose.animation.scene.transformation.Transformation 34 import com.android.compose.animation.scene.transformation.TransformationMatcher 35 import com.android.compose.animation.scene.transformation.TransformationRange 36 import com.android.compose.animation.scene.transformation.Translate 37 import com.android.internal.jank.Cuj.CujType 38 39 internal fun transitionsImpl(builder: SceneTransitionsBuilder.() -> Unit): SceneTransitions { 40 val impl = SceneTransitionsBuilderImpl().apply(builder) 41 return SceneTransitions( 42 transitionSpecs = impl.transitionSpecs, 43 interruptionHandler = impl.interruptionHandler, 44 ) 45 } 46 47 private class SceneTransitionsBuilderImpl : SceneTransitionsBuilder { 48 override var interruptionHandler: InterruptionHandler = DefaultInterruptionHandler 49 50 val transitionSpecs = mutableListOf<TransitionSpecImpl>() 51 tonull52 override fun to( 53 to: ContentKey, 54 key: TransitionKey?, 55 @CujType cuj: Int?, 56 preview: (TransitionBuilder.() -> Unit)?, 57 reversePreview: (TransitionBuilder.() -> Unit)?, 58 builder: TransitionBuilder.() -> Unit, 59 ) { 60 transition( 61 from = null, 62 to = to, 63 key = key, 64 cuj = cuj, 65 preview = preview, 66 reversePreview = reversePreview, 67 builder = builder, 68 ) 69 } 70 fromnull71 override fun from( 72 from: ContentKey, 73 to: ContentKey?, 74 key: TransitionKey?, 75 @CujType cuj: Int?, 76 preview: (TransitionBuilder.() -> Unit)?, 77 reversePreview: (TransitionBuilder.() -> Unit)?, 78 builder: TransitionBuilder.() -> Unit, 79 ) { 80 transition( 81 from = from, 82 to = to, 83 key = key, 84 cuj = cuj, 85 preview = preview, 86 reversePreview = reversePreview, 87 builder = builder, 88 ) 89 } 90 transitionnull91 private fun transition( 92 from: ContentKey?, 93 to: ContentKey?, 94 key: TransitionKey?, 95 @CujType cuj: Int?, 96 preview: (TransitionBuilder.() -> Unit)?, 97 reversePreview: (TransitionBuilder.() -> Unit)?, 98 builder: TransitionBuilder.() -> Unit, 99 ): TransitionSpec { 100 fun transformationSpec( 101 transition: TransitionState.Transition, 102 builder: TransitionBuilder.() -> Unit, 103 ): TransformationSpecImpl { 104 val impl = TransitionBuilderImpl(transition).apply(builder) 105 return TransformationSpecImpl( 106 progressSpec = impl.spec, 107 distance = impl.distance, 108 transformationMatchers = impl.transformationMatchers, 109 ) 110 } 111 112 val spec = 113 TransitionSpecImpl( 114 key = key, 115 from = from, 116 to = to, 117 cuj = cuj, 118 previewTransformationSpec = preview?.let { { t -> transformationSpec(t, it) } }, 119 reversePreviewTransformationSpec = 120 reversePreview?.let { { t -> transformationSpec(t, it) } }, 121 transformationSpec = { t -> transformationSpec(t, builder) }, 122 ) 123 transitionSpecs.add(spec) 124 return spec 125 } 126 } 127 128 internal abstract class BaseTransitionBuilderImpl : BaseTransitionBuilder { 129 val transformationMatchers = mutableListOf<TransformationMatcher>() 130 private var range: TransformationRange? = null 131 protected var reversed = false 132 override var distance: UserActionDistance? = null 133 fractionRangenull134 override fun fractionRange( 135 start: Float?, 136 end: Float?, 137 easing: Easing, 138 builder: PropertyTransformationBuilder.() -> Unit, 139 ) { 140 range = TransformationRange(start, end, easing) 141 builder() 142 range = null 143 } 144 addTransformationnull145 protected fun addTransformation( 146 matcher: ElementMatcher, 147 transformation: Transformation.Factory, 148 ) { 149 transformationMatchers.add( 150 TransformationMatcher( 151 matcher, 152 transformation, 153 range?.let { range -> 154 if (reversed) { 155 range.reversed() 156 } else { 157 range 158 } 159 }, 160 ) 161 ) 162 } 163 fadenull164 override fun fade(matcher: ElementMatcher) { 165 addTransformation(matcher, Fade.Factory) 166 } 167 translatenull168 override fun translate(matcher: ElementMatcher, x: Dp, y: Dp) { 169 addTransformation(matcher, Translate.Factory(x, y)) 170 } 171 translatenull172 override fun translate( 173 matcher: ElementMatcher, 174 edge: Edge, 175 startsOutsideLayoutBounds: Boolean, 176 ) { 177 addTransformation(matcher, EdgeTranslate.Factory(edge, startsOutsideLayoutBounds)) 178 } 179 anchoredTranslatenull180 override fun anchoredTranslate(matcher: ElementMatcher, anchor: ElementKey) { 181 addTransformation(matcher, AnchoredTranslate.Factory(anchor)) 182 } 183 scaleSizenull184 override fun scaleSize(matcher: ElementMatcher, width: Float, height: Float) { 185 addTransformation(matcher, ScaleSize.Factory(width, height)) 186 } 187 scaleDrawnull188 override fun scaleDraw(matcher: ElementMatcher, scaleX: Float, scaleY: Float, pivot: Offset) { 189 addTransformation(matcher, DrawScale.Factory(scaleX, scaleY, pivot)) 190 } 191 anchoredSizenull192 override fun anchoredSize( 193 matcher: ElementMatcher, 194 anchor: ElementKey, 195 anchorWidth: Boolean, 196 anchorHeight: Boolean, 197 ) { 198 addTransformation(matcher, AnchoredSize.Factory(anchor, anchorWidth, anchorHeight)) 199 } 200 transformationnull201 override fun transformation(matcher: ElementMatcher, transformation: Transformation.Factory) { 202 check(range == null) { "Custom transformations can not be applied inside a range" } 203 addTransformation(matcher, transformation) 204 } 205 } 206 207 internal class TransitionBuilderImpl(override val transition: TransitionState.Transition) : 208 BaseTransitionBuilderImpl(), TransitionBuilder { 209 override var spec: AnimationSpec<Float>? = null 210 override var distance: UserActionDistance? = null 211 override var cuj: Int? = null <lambda>null212 private val durationMillis: Int by lazy { 213 val spec = spec 214 if (spec !is DurationBasedAnimationSpec) { 215 error("timestampRange {} can only be used with a DurationBasedAnimationSpec") 216 } 217 218 spec.vectorize(Float.VectorConverter).durationMillis 219 } 220 reversednull221 override fun reversed(builder: TransitionBuilder.() -> Unit) { 222 reversed = true 223 builder() 224 reversed = false 225 } 226 sharedElementnull227 override fun sharedElement( 228 matcher: ElementMatcher, 229 enabled: Boolean, 230 elevateInContent: ContentKey?, 231 ) { 232 check( 233 elevateInContent == null || 234 elevateInContent == transition.fromContent || 235 elevateInContent == transition.toContent 236 ) { 237 "elevateInContent (${elevateInContent?.debugName}) should be either fromContent " + 238 "(${transition.fromContent.debugName}) or toContent " + 239 "(${transition.toContent.debugName})" 240 } 241 242 addTransformation( 243 matcher, 244 SharedElementTransformation.Factory(matcher, enabled, elevateInContent), 245 ) 246 } 247 timestampRangenull248 override fun timestampRange( 249 startMillis: Int?, 250 endMillis: Int?, 251 easing: Easing, 252 builder: PropertyTransformationBuilder.() -> Unit, 253 ) { 254 if (startMillis != null && (startMillis < 0 || startMillis > durationMillis)) { 255 error("invalid start value: startMillis=$startMillis durationMillis=$durationMillis") 256 } 257 258 if (endMillis != null && (endMillis < 0 || endMillis > durationMillis)) { 259 error("invalid end value: endMillis=$startMillis durationMillis=$durationMillis") 260 } 261 262 val start = startMillis?.let { it.toFloat() / durationMillis } 263 val end = endMillis?.let { it.toFloat() / durationMillis } 264 fractionRange(start, end, easing, builder) 265 } 266 } 267