1 /*
2 * 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.Spring
22 import androidx.compose.animation.core.SpringSpec
23 import androidx.compose.animation.core.VectorConverter
24 import androidx.compose.animation.core.snap
25 import androidx.compose.animation.core.spring
26 import androidx.compose.foundation.gestures.Orientation
27 import androidx.compose.ui.geometry.Offset
28 import androidx.compose.ui.unit.Dp
29 import com.android.compose.animation.scene.transformation.AnchoredSize
30 import com.android.compose.animation.scene.transformation.AnchoredTranslate
31 import com.android.compose.animation.scene.transformation.DrawScale
32 import com.android.compose.animation.scene.transformation.EdgeTranslate
33 import com.android.compose.animation.scene.transformation.Fade
34 import com.android.compose.animation.scene.transformation.OverscrollTranslate
35 import com.android.compose.animation.scene.transformation.PropertyTransformation
36 import com.android.compose.animation.scene.transformation.RangedPropertyTransformation
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.TransformationRange
41 import com.android.compose.animation.scene.transformation.Translate
42
transitionsImplnull43 internal fun transitionsImpl(
44 builder: SceneTransitionsBuilder.() -> Unit,
45 ): SceneTransitions {
46 val impl = SceneTransitionsBuilderImpl().apply(builder)
47 return SceneTransitions(
48 impl.defaultSwipeSpec,
49 impl.transitionSpecs,
50 impl.transitionOverscrollSpecs,
51 impl.interruptionHandler,
52 )
53 }
54
55 private class SceneTransitionsBuilderImpl : SceneTransitionsBuilder {
56 override var defaultSwipeSpec: SpringSpec<Float> = SceneTransitions.DefaultSwipeSpec
57 override var interruptionHandler: InterruptionHandler = DefaultInterruptionHandler
58
59 val transitionSpecs = mutableListOf<TransitionSpecImpl>()
60 val transitionOverscrollSpecs = mutableListOf<OverscrollSpecImpl>()
61
tonull62 override fun to(
63 to: SceneKey,
64 key: TransitionKey?,
65 builder: TransitionBuilder.() -> Unit
66 ): TransitionSpec {
67 return transition(from = null, to = to, key = key, builder)
68 }
69
fromnull70 override fun from(
71 from: SceneKey,
72 to: SceneKey?,
73 key: TransitionKey?,
74 builder: TransitionBuilder.() -> Unit
75 ): TransitionSpec {
76 return transition(from = from, to = to, key = key, builder)
77 }
78
overscrollnull79 override fun overscroll(
80 scene: SceneKey,
81 orientation: Orientation,
82 builder: OverscrollBuilder.() -> Unit
83 ): OverscrollSpec {
84 fun transformationSpec(): TransformationSpecImpl {
85 val impl = OverscrollBuilderImpl().apply(builder)
86 return TransformationSpecImpl(
87 progressSpec = snap(),
88 swipeSpec = null,
89 distance = impl.distance,
90 transformations = impl.transformations,
91 )
92 }
93 val spec = OverscrollSpecImpl(scene, orientation, transformationSpec())
94 transitionOverscrollSpecs.add(spec)
95 return spec
96 }
97
transitionnull98 private fun transition(
99 from: SceneKey?,
100 to: SceneKey?,
101 key: TransitionKey?,
102 builder: TransitionBuilder.() -> Unit,
103 ): TransitionSpec {
104 fun transformationSpec(): TransformationSpecImpl {
105 val impl = TransitionBuilderImpl().apply(builder)
106 return TransformationSpecImpl(
107 progressSpec = impl.spec,
108 swipeSpec = impl.swipeSpec,
109 distance = impl.distance,
110 transformations = impl.transformations,
111 )
112 }
113
114 val spec = TransitionSpecImpl(key, from, to, ::transformationSpec)
115 transitionSpecs.add(spec)
116 return spec
117 }
118 }
119
120 internal abstract class BaseTransitionBuilderImpl : BaseTransitionBuilder {
121 val transformations = mutableListOf<Transformation>()
122 private var range: TransformationRange? = null
123 protected var reversed = false
124 override var distance: UserActionDistance? = null
125
fractionRangenull126 override fun fractionRange(
127 start: Float?,
128 end: Float?,
129 builder: PropertyTransformationBuilder.() -> Unit
130 ) {
131 range = TransformationRange(start, end)
132 builder()
133 range = null
134 }
135
transformationnull136 protected fun transformation(transformation: PropertyTransformation<*>) {
137 val transformation =
138 if (range != null) {
139 RangedPropertyTransformation(transformation, range!!)
140 } else {
141 transformation
142 }
143
144 transformations.add(
145 if (reversed) {
146 transformation.reversed()
147 } else {
148 transformation
149 }
150 )
151 }
152
fadenull153 override fun fade(matcher: ElementMatcher) {
154 transformation(Fade(matcher))
155 }
156
translatenull157 override fun translate(matcher: ElementMatcher, x: Dp, y: Dp) {
158 transformation(Translate(matcher, x, y))
159 }
160
translatenull161 override fun translate(
162 matcher: ElementMatcher,
163 edge: Edge,
164 startsOutsideLayoutBounds: Boolean
165 ) {
166 transformation(EdgeTranslate(matcher, edge, startsOutsideLayoutBounds))
167 }
168
anchoredTranslatenull169 override fun anchoredTranslate(matcher: ElementMatcher, anchor: ElementKey) {
170 transformation(AnchoredTranslate(matcher, anchor))
171 }
172
scaleSizenull173 override fun scaleSize(matcher: ElementMatcher, width: Float, height: Float) {
174 transformation(ScaleSize(matcher, width, height))
175 }
176
scaleDrawnull177 override fun scaleDraw(matcher: ElementMatcher, scaleX: Float, scaleY: Float, pivot: Offset) {
178 transformation(DrawScale(matcher, scaleX, scaleY, pivot))
179 }
180
anchoredSizenull181 override fun anchoredSize(
182 matcher: ElementMatcher,
183 anchor: ElementKey,
184 anchorWidth: Boolean,
185 anchorHeight: Boolean,
186 ) {
187 transformation(AnchoredSize(matcher, anchor, anchorWidth, anchorHeight))
188 }
189 }
190
191 internal class TransitionBuilderImpl : BaseTransitionBuilderImpl(), TransitionBuilder {
192 override var spec: AnimationSpec<Float> = spring(stiffness = Spring.StiffnessLow)
193 override var swipeSpec: SpringSpec<Float>? = null
194 override var distance: UserActionDistance? = null
<lambda>null195 private val durationMillis: Int by lazy {
196 val spec = spec
197 if (spec !is DurationBasedAnimationSpec) {
198 error("timestampRange {} can only be used with a DurationBasedAnimationSpec")
199 }
200
201 spec.vectorize(Float.VectorConverter).durationMillis
202 }
203
reversednull204 override fun reversed(builder: TransitionBuilder.() -> Unit) {
205 reversed = true
206 builder()
207 reversed = false
208 }
209
sharedElementnull210 override fun sharedElement(matcher: ElementMatcher, enabled: Boolean) {
211 transformations.add(SharedElementTransformation(matcher, enabled))
212 }
213
timestampRangenull214 override fun timestampRange(
215 startMillis: Int?,
216 endMillis: Int?,
217 builder: PropertyTransformationBuilder.() -> Unit
218 ) {
219 if (startMillis != null && (startMillis < 0 || startMillis > durationMillis)) {
220 error("invalid start value: startMillis=$startMillis durationMillis=$durationMillis")
221 }
222
223 if (endMillis != null && (endMillis < 0 || endMillis > durationMillis)) {
224 error("invalid end value: endMillis=$startMillis durationMillis=$durationMillis")
225 }
226
227 val start = startMillis?.let { it.toFloat() / durationMillis }
228 val end = endMillis?.let { it.toFloat() / durationMillis }
229 fractionRange(start, end, builder)
230 }
231 }
232
233 internal open class OverscrollBuilderImpl : BaseTransitionBuilderImpl(), OverscrollBuilder {
translatenull234 override fun translate(
235 matcher: ElementMatcher,
236 x: OverscrollScope.() -> Float,
237 y: OverscrollScope.() -> Float
238 ) {
239 transformation(OverscrollTranslate(matcher, x, y))
240 }
241 }
242