• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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