1 /*
<lambda>null2 * Copyright 2021 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 androidx.compose.animation.graphics.vector.compat
18
19 import android.content.res.Resources
20 import android.content.res.TypedArray
21 import android.util.AttributeSet
22 import android.util.TypedValue
23 import android.view.animation.PathInterpolator
24 import androidx.compose.animation.core.CubicBezierEasing
25 import androidx.compose.animation.core.Easing
26 import androidx.compose.animation.core.LinearEasing
27 import androidx.compose.animation.core.RepeatMode
28 import androidx.compose.animation.graphics.res.AccelerateDecelerateEasing
29 import androidx.compose.animation.graphics.res.AccelerateEasing
30 import androidx.compose.animation.graphics.res.AnticipateEasing
31 import androidx.compose.animation.graphics.res.AnticipateOvershootEasing
32 import androidx.compose.animation.graphics.res.BounceEasing
33 import androidx.compose.animation.graphics.res.CycleEasing
34 import androidx.compose.animation.graphics.res.DecelerateEasing
35 import androidx.compose.animation.graphics.res.OvershootEasing
36 import androidx.compose.animation.graphics.res.loadInterpolatorResource
37 import androidx.compose.animation.graphics.res.toEasing
38 import androidx.compose.animation.graphics.vector.Animator
39 import androidx.compose.animation.graphics.vector.AnimatorSet
40 import androidx.compose.animation.graphics.vector.Keyframe
41 import androidx.compose.animation.graphics.vector.ObjectAnimator
42 import androidx.compose.animation.graphics.vector.Ordering
43 import androidx.compose.animation.graphics.vector.PropertyValuesHolder
44 import androidx.compose.animation.graphics.vector.PropertyValuesHolder1D
45 import androidx.compose.animation.graphics.vector.PropertyValuesHolder2D
46 import androidx.compose.animation.graphics.vector.PropertyValuesHolderColor
47 import androidx.compose.animation.graphics.vector.PropertyValuesHolderFloat
48 import androidx.compose.animation.graphics.vector.PropertyValuesHolderInt
49 import androidx.compose.animation.graphics.vector.PropertyValuesHolderPath
50 import androidx.compose.ui.graphics.Color
51 import androidx.compose.ui.graphics.vector.PathNode
52 import androidx.compose.ui.graphics.vector.addPathNodes
53 import androidx.core.graphics.PathParser
54 import org.xmlpull.v1.XmlPullParser
55
56 internal const val TagSet = "set"
57 internal const val TagObjectAnimator = "objectAnimator"
58 private const val TagPropertyValuesHolder = "propertyValuesHolder"
59 private const val TagKeyframe = "keyframe"
60
61 private const val ValueTypeFloat = 0
62 private const val ValueTypeInt = 1
63 private const val ValueTypePath = 2
64 private const val ValueTypeColor = 3
65 private const val ValueTypeUndefined = 4
66
67 private const val RepeatModeReverse = 2
68
69 private enum class ValueType {
70 Float,
71 Int,
72 Color,
73 Path
74 }
75
76 private val FallbackValueType = ValueType.Float
77
TypedArraynull78 private fun TypedArray.getInterpolator(
79 res: Resources,
80 theme: Resources.Theme?,
81 index: Int,
82 defaultValue: Easing
83 ): Easing {
84 val id = getResourceId(index, 0)
85 return if (id == 0) {
86 defaultValue
87 } else {
88 loadInterpolatorResource(theme, res, id)
89 }
90 }
91
parseKeyframenull92 private fun parseKeyframe(
93 res: Resources,
94 theme: Resources.Theme?,
95 attrs: AttributeSet,
96 holderValueType: ValueType?,
97 defaultInterpolator: Easing
98 ): Pair<Keyframe<Any>, ValueType> {
99 return attrs.attrs(res, theme, AndroidVectorResources.STYLEABLE_KEYFRAME) { a ->
100 val inferredValueType =
101 // The type is specified in <propertyValuesHolder>.
102 holderValueType
103 ?: inferValueType( // Identify the type from our attribute values.
104 a.getInt(
105 AndroidVectorResources.STYLEABLE_KEYFRAME_VALUE_TYPE,
106 ValueTypeUndefined
107 ),
108 a.peekValue(AndroidVectorResources.STYLEABLE_KEYFRAME_VALUE).type
109 )
110 // We didn't have any clue until the end.
111 ?: FallbackValueType
112 a.getKeyframe(
113 a.getFloat(AndroidVectorResources.STYLEABLE_KEYFRAME_FRACTION, 0f),
114 a.getInterpolator(
115 res,
116 theme,
117 AndroidVectorResources.STYLEABLE_KEYFRAME_INTERPOLATOR,
118 defaultInterpolator
119 ),
120 inferredValueType,
121 AndroidVectorResources.STYLEABLE_KEYFRAME_VALUE
122 ) to inferredValueType // Report back the type to <propertyValuesHolder>.
123 }
124 }
125
126 /**
127 * Extracts a [Keyframe] value from this [TypedArray]. This [TypedArray] can come from either
128 * `<propertyValuesHolder>` or `<keyframe>`
129 */
TypedArraynull130 private fun TypedArray.getKeyframe(
131 fraction: Float,
132 interpolator: Easing,
133 valueType: ValueType,
134 valueIndex: Int
135 ): Keyframe<Any> {
136 return when (valueType) {
137 ValueType.Float -> Keyframe(fraction, getFloat(valueIndex, 0f), interpolator)
138 ValueType.Int -> Keyframe(fraction, getInt(valueIndex, 0), interpolator)
139 ValueType.Color -> Keyframe(fraction, Color(getColor(valueIndex, 0)), interpolator)
140 ValueType.Path -> Keyframe(fraction, addPathNodes(getString(valueIndex)), interpolator)
141 }
142 }
143
parsePropertyValuesHoldernull144 private fun XmlPullParser.parsePropertyValuesHolder(
145 res: Resources,
146 theme: Resources.Theme?,
147 attrs: AttributeSet,
148 interpolator: Easing
149 ): PropertyValuesHolder<*> {
150 return attrs.attrs(res, theme, AndroidVectorResources.STYLEABLE_PROPERTY_VALUES_HOLDER) { a ->
151 a.getPropertyValuesHolder1D(
152 a.getString(AndroidVectorResources.STYLEABLE_PROPERTY_VALUES_HOLDER_PROPERTY_NAME)!!,
153 AndroidVectorResources.STYLEABLE_PROPERTY_VALUES_HOLDER_VALUE_TYPE,
154 AndroidVectorResources.STYLEABLE_PROPERTY_VALUES_HOLDER_VALUE_FROM,
155 AndroidVectorResources.STYLEABLE_PROPERTY_VALUES_HOLDER_VALUE_TO,
156 interpolator
157 ) { valueType, keyframes ->
158 var vt: ValueType? = null
159 forEachChildOf(TagPropertyValuesHolder) {
160 if (eventType == XmlPullParser.START_TAG && name == TagKeyframe) {
161 val (keyframe, keyframeValueType) =
162 parseKeyframe(res, theme, attrs, valueType, interpolator)
163 if (vt == null) vt = keyframeValueType
164 keyframes.add(keyframe)
165 }
166 }
167 // This determines the final ValueType of the PropertyValuesHolder.
168 vt ?: valueType ?: FallbackValueType
169 }
170 }
171 }
172
173 /**
174 * Infers a [ValueType] from various information from XML.
175 *
176 * @param valueType The `valueType` attribute specified in the XML.
177 * @param typedValueTypes [TypedValue.type] values taken from multiple [TypedValue]s.
178 * @return A [ValueType] identified by the information so far, or `null` if it is uncertain.
179 */
inferValueTypenull180 private fun inferValueType(valueType: Int, vararg typedValueTypes: Int): ValueType? {
181 return when (valueType) {
182 ValueTypeFloat -> ValueType.Float
183 ValueTypeInt -> ValueType.Int
184 ValueTypeColor -> ValueType.Color
185 ValueTypePath -> ValueType.Path
186 else ->
187 if (
188 typedValueTypes.all {
189 it in TypedValue.TYPE_FIRST_COLOR_INT..TypedValue.TYPE_LAST_COLOR_INT
190 }
191 ) {
192 ValueType.Color
193 } else {
194 null
195 }
196 }
197 }
198
199 /**
200 * Extracts attribute values related to [PropertyValuesHolder]. This [TypedArray] can be taken from
201 * either `<objectAnimator>` or `<propertyValuesHolder>`.
202 *
203 * @param parseKeyframes The caller should parse `<keyframe>`s inside of this
204 * `<propertyValuesHolder>` and store them in the `keyframes` [MutableList]. The lambda receives a
205 * [ValueType] if it has been identified so far. The lambda has to return [ValueType] in case it
206 * is first identified while parsing keyframes.
207 */
getPropertyValuesHolder1Dnull208 private fun TypedArray.getPropertyValuesHolder1D(
209 propertyName: String,
210 valueTypeIndex: Int,
211 valueFromIndex: Int,
212 valueToIndex: Int,
213 interpolator: Easing,
214 parseKeyframes: (valueType: ValueType?, keyframes: MutableList<Keyframe<Any>>) -> ValueType =
215 { vt, _ ->
216 vt ?: FallbackValueType
217 }
218 ): PropertyValuesHolder1D<*> {
219 val valueType = getInt(valueTypeIndex, ValueTypeUndefined)
220
221 val valueFrom = peekValue(valueFromIndex)
222 val hasFrom = valueFrom != null
223 val typeFrom = valueFrom?.type ?: ValueTypeUndefined
224
225 val valueTo = peekValue(valueToIndex)
226 val hasTo = valueTo != null
227 val typeTo = valueTo?.type ?: ValueTypeUndefined
228
229 var inferredValueType = inferValueType(valueType, typeFrom, typeTo)
230 val keyframes = mutableListOf<Keyframe<Any>>()
231 if (inferredValueType == null && (hasFrom || hasTo)) {
232 inferredValueType = ValueType.Float
233 }
234 if (hasFrom) {
235 keyframes.add(getKeyframe(0f, interpolator, inferredValueType!!, valueFromIndex))
236 }
237 if (hasTo) {
238 keyframes.add(getKeyframe(1f, interpolator, inferredValueType!!, valueToIndex))
239 }
240 inferredValueType = parseKeyframes(inferredValueType, keyframes)
<lambda>null241 keyframes.sortBy { it.fraction }
242 @Suppress("UNCHECKED_CAST")
243 return when (inferredValueType) {
244 ValueType.Float ->
245 PropertyValuesHolderFloat(propertyName, keyframes as List<Keyframe<Float>>)
246 ValueType.Int -> PropertyValuesHolderInt(propertyName, keyframes as List<Keyframe<Int>>)
247 ValueType.Color ->
248 PropertyValuesHolderColor(propertyName, keyframes as List<Keyframe<Color>>)
249 ValueType.Path ->
250 PropertyValuesHolderPath(propertyName, keyframes as List<Keyframe<List<PathNode>>>)
251 }
252 }
253
convertRepeatModenull254 private fun convertRepeatMode(repeatMode: Int) =
255 when (repeatMode) {
256 RepeatModeReverse -> RepeatMode.Reverse
257 else -> RepeatMode.Restart
258 }
259
parseObjectAnimatornull260 internal fun XmlPullParser.parseObjectAnimator(
261 res: Resources,
262 theme: Resources.Theme?,
263 attrs: AttributeSet
264 ): ObjectAnimator {
265 return attrs.attrs(res, theme, AndroidVectorResources.STYLEABLE_ANIMATOR) { a ->
266 attrs.attrs(res, theme, AndroidVectorResources.STYLEABLE_PROPERTY_ANIMATOR) { oa ->
267 val interpolator =
268 a.getInterpolator(
269 res,
270 theme,
271 AndroidVectorResources.STYLEABLE_ANIMATOR_INTERPOLATOR,
272 AccelerateDecelerateEasing
273 )
274 val holders = mutableListOf<PropertyValuesHolder<*>>()
275 val pathData =
276 oa.getString(AndroidVectorResources.STYLEABLE_PROPERTY_ANIMATOR_PATH_DATA)
277 if (pathData != null) {
278 // 2D; This <objectAnimator> has `pathData`. It should also have `propertyXName`
279 // and `propertyYName`.
280 holders.add(
281 PropertyValuesHolder2D(
282 oa.getString(
283 AndroidVectorResources.STYLEABLE_PROPERTY_ANIMATOR_PROPERTY_X_NAME
284 )!!,
285 oa.getString(
286 AndroidVectorResources.STYLEABLE_PROPERTY_ANIMATOR_PROPERTY_Y_NAME
287 )!!,
288 addPathNodes(pathData),
289 interpolator
290 )
291 )
292 } else {
293 // 1D; This <objectAnimator> has `propertyName`, `valueFrom`, and `valueTo`.
294 oa.getString(AndroidVectorResources.STYLEABLE_PROPERTY_ANIMATOR_PROPERTY_NAME)
295 ?.let { propertyName ->
296 holders.add(
297 a.getPropertyValuesHolder1D(
298 propertyName,
299 AndroidVectorResources.STYLEABLE_ANIMATOR_VALUE_TYPE,
300 AndroidVectorResources.STYLEABLE_ANIMATOR_VALUE_FROM,
301 AndroidVectorResources.STYLEABLE_ANIMATOR_VALUE_TO,
302 interpolator
303 )
304 )
305 }
306 // This <objectAnimator> has <propertyValuesHolder> inside.
307 forEachChildOf(TagObjectAnimator) {
308 if (eventType == XmlPullParser.START_TAG && name == TagPropertyValuesHolder) {
309 holders.add(parsePropertyValuesHolder(res, theme, attrs, interpolator))
310 }
311 }
312 }
313
314 ObjectAnimator(
315 duration = a.getInt(AndroidVectorResources.STYLEABLE_ANIMATOR_DURATION, 300),
316 startDelay = a.getInt(AndroidVectorResources.STYLEABLE_ANIMATOR_START_OFFSET, 0),
317 repeatCount = a.getInt(AndroidVectorResources.STYLEABLE_ANIMATOR_REPEAT_COUNT, 0),
318 repeatMode =
319 convertRepeatMode(
320 a.getInt(AndroidVectorResources.STYLEABLE_ANIMATOR_REPEAT_MODE, 0)
321 ),
322 holders = holders
323 )
324 }
325 }
326 }
327
parseAnimatorSetnull328 internal fun XmlPullParser.parseAnimatorSet(
329 res: Resources,
330 theme: Resources.Theme?,
331 attrs: AttributeSet
332 ): AnimatorSet {
333 return attrs.attrs(res, theme, AndroidVectorResources.STYLEABLE_ANIMATOR_SET) { a ->
334 val ordering = a.getInt(AndroidVectorResources.STYLEABLE_ANIMATOR_SET_ORDERING, 0)
335 val animators = mutableListOf<Animator>()
336 forEachChildOf(TagSet) {
337 if (eventType == XmlPullParser.START_TAG) {
338 when (name) {
339 TagSet -> animators.add(parseAnimatorSet(res, theme, attrs))
340 TagObjectAnimator -> animators.add(parseObjectAnimator(res, theme, attrs))
341 }
342 }
343 }
344 AnimatorSet(animators, if (ordering != 0) Ordering.Sequentially else Ordering.Together)
345 }
346 }
347
parseInterpolatornull348 internal fun XmlPullParser.parseInterpolator(
349 res: Resources,
350 theme: Resources.Theme?,
351 attrs: AttributeSet
352 ): Easing {
353 return when (name) {
354 "linearInterpolator" -> LinearEasing
355 "accelerateInterpolator" ->
356 attrs.attrs(res, theme, AndroidVectorResources.STYLEABLE_ACCELERATE_INTERPOLATOR) { a ->
357 val factor =
358 a.getFloat(
359 AndroidVectorResources.STYLEABLE_ACCELERATE_INTERPOLATOR_FACTOR,
360 1.0f
361 )
362 if (factor == 1.0f) AccelerateEasing else AccelerateEasing(factor)
363 }
364 "decelerateInterpolator" ->
365 attrs.attrs(res, theme, AndroidVectorResources.STYLEABLE_DECELERATE_INTERPOLATOR) { a ->
366 val factor =
367 a.getFloat(
368 AndroidVectorResources.STYLEABLE_DECELERATE_INTERPOLATOR_FACTOR,
369 1.0f
370 )
371 if (factor == 1.0f) DecelerateEasing else DecelerateEasing(factor)
372 }
373 "accelerateDecelerateInterpolator" -> AccelerateDecelerateEasing
374 "cycleInterpolator" ->
375 attrs.attrs(res, theme, AndroidVectorResources.STYLEABLE_CYCLE_INTERPOLATOR) { a ->
376 CycleEasing(
377 a.getFloat(AndroidVectorResources.STYLEABLE_CYCLE_INTERPOLATOR_CYCLES, 1.0f)
378 )
379 }
380 "anticipateInterpolator" ->
381 attrs.attrs(
382 res,
383 theme,
384 AndroidVectorResources.STYLEABLE_ANTICIPATEOVERSHOOT_INTERPOLATOR
385 ) { a ->
386 AnticipateEasing(
387 a.getFloat(
388 AndroidVectorResources.STYLEABLE_ANTICIPATEOVERSHOOT_INTERPOLATOR_TENSION,
389 2.0f
390 )
391 )
392 }
393 "overshootInterpolator" ->
394 attrs.attrs(res, theme, AndroidVectorResources.STYLEABLE_OVERSHOOT_INTERPOLATOR) { a ->
395 OvershootEasing(
396 a.getFloat(
397 AndroidVectorResources.STYLEABLE_OVERSHOOT_INTERPOLATOR_TENSION,
398 2.0f
399 )
400 )
401 }
402 "anticipateOvershootInterpolator" ->
403 attrs.attrs(
404 res,
405 theme,
406 AndroidVectorResources.STYLEABLE_ANTICIPATEOVERSHOOT_INTERPOLATOR
407 ) { a ->
408 AnticipateOvershootEasing(
409 a.getFloat(
410 AndroidVectorResources.STYLEABLE_ANTICIPATEOVERSHOOT_INTERPOLATOR_TENSION,
411 2.0f
412 ),
413 a.getFloat(
414 AndroidVectorResources
415 .STYLEABLE_ANTICIPATEOVERSHOOT_INTERPOLATOR_EXTRA_TENSION,
416 1.5f
417 )
418 )
419 }
420 "bounceInterpolator" -> BounceEasing
421 "pathInterpolator" ->
422 attrs.attrs(res, theme, AndroidVectorResources.STYLEABLE_PATH_INTERPOLATOR) { a ->
423 val pathData =
424 a.getString(AndroidVectorResources.STYLEABLE_PATH_INTERPOLATOR_PATH_DATA)
425 if (pathData != null) {
426 PathInterpolator(PathParser.createPathFromPathData(pathData)).toEasing()
427 } else if (
428 !a.hasValue(AndroidVectorResources.STYLEABLE_PATH_INTERPOLATOR_CONTROL_X_2) ||
429 !a.hasValue(AndroidVectorResources.STYLEABLE_PATH_INTERPOLATOR_CONTROL_Y_2)
430 ) {
431 PathInterpolator(
432 a.getFloat(
433 AndroidVectorResources.STYLEABLE_PATH_INTERPOLATOR_CONTROL_X_1,
434 0f
435 ),
436 a.getFloat(
437 AndroidVectorResources.STYLEABLE_PATH_INTERPOLATOR_CONTROL_Y_1,
438 0f
439 )
440 )
441 .toEasing()
442 } else {
443 CubicBezierEasing(
444 a.getFloat(
445 AndroidVectorResources.STYLEABLE_PATH_INTERPOLATOR_CONTROL_X_1,
446 0f
447 ),
448 a.getFloat(
449 AndroidVectorResources.STYLEABLE_PATH_INTERPOLATOR_CONTROL_Y_1,
450 0f
451 ),
452 a.getFloat(
453 AndroidVectorResources.STYLEABLE_PATH_INTERPOLATOR_CONTROL_X_2,
454 1f
455 ),
456 a.getFloat(
457 AndroidVectorResources.STYLEABLE_PATH_INTERPOLATOR_CONTROL_Y_2,
458 1f
459 )
460 )
461 }
462 }
463 else -> throw RuntimeException("Unknown interpolator: $name")
464 }
465 }
466