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.transformation 18 19 import androidx.compose.ui.geometry.Offset 20 import androidx.compose.ui.unit.Density 21 import androidx.compose.ui.unit.Dp 22 import com.android.compose.animation.scene.ContentKey 23 import com.android.compose.animation.scene.ElementKey 24 import com.android.compose.animation.scene.OverscrollScope 25 import com.android.compose.animation.scene.content.state.TransitionState 26 27 internal class Translate private constructor(private val x: Dp, private val y: Dp) : 28 InterpolatedPropertyTransformation<Offset> { 29 override val property = PropertyTransformation.Property.Offset 30 transformnull31 override fun PropertyTransformationScope.transform( 32 content: ContentKey, 33 element: ElementKey, 34 transition: TransitionState.Transition, 35 idleValue: Offset, 36 ): Offset { 37 return Offset(idleValue.x + x.toPx(), idleValue.y + y.toPx()) 38 } 39 40 class Factory(private val x: Dp, private val y: Dp) : Transformation.Factory { createnull41 override fun create(): Transformation = Translate(x, y) 42 } 43 } 44 45 internal class OverscrollTranslate 46 private constructor( 47 private val x: OverscrollScope.() -> Float, 48 private val y: OverscrollScope.() -> Float, 49 ) : InterpolatedPropertyTransformation<Offset> { 50 override val property = PropertyTransformation.Property.Offset 51 52 private val cachedOverscrollScope = CachedOverscrollScope() 53 54 override fun PropertyTransformationScope.transform( 55 content: ContentKey, 56 element: ElementKey, 57 transition: TransitionState.Transition, 58 value: Offset, 59 ): Offset { 60 // As this object is created by OverscrollBuilderImpl and we retrieve the current 61 // OverscrollSpec only when the transition implements HasOverscrollProperties, we can assume 62 // that this method was invoked after performing this check. 63 val overscrollProperties = transition as TransitionState.HasOverscrollProperties 64 val overscrollScope = 65 cachedOverscrollScope.getFromCacheOrCompute(density = this, overscrollProperties) 66 67 return Offset(x = value.x + overscrollScope.x(), y = value.y + overscrollScope.y()) 68 } 69 70 class Factory( 71 private val x: OverscrollScope.() -> Float, 72 private val y: OverscrollScope.() -> Float, 73 ) : Transformation.Factory { 74 override fun create(): Transformation = OverscrollTranslate(x, y) 75 } 76 } 77 78 /** 79 * A helper class to cache a [OverscrollScope] given a [Density] and 80 * [TransitionState.HasOverscrollProperties]. This helps avoid recreating a scope every frame 81 * whenever an overscroll transition is computed. 82 */ 83 private class CachedOverscrollScope { 84 private var previousScope: OverscrollScope? = null 85 private var previousDensity: Density? = null 86 private var previousOverscrollProperties: TransitionState.HasOverscrollProperties? = null 87 getFromCacheOrComputenull88 fun getFromCacheOrCompute( 89 density: Density, 90 overscrollProperties: TransitionState.HasOverscrollProperties, 91 ): OverscrollScope { 92 if ( 93 previousScope == null || 94 density != previousDensity || 95 previousOverscrollProperties != overscrollProperties 96 ) { 97 val scope = 98 object : OverscrollScope, Density by density { 99 override val absoluteDistance: Float 100 get() = overscrollProperties.absoluteDistance 101 } 102 103 previousScope = scope 104 previousDensity = density 105 previousOverscrollProperties = overscrollProperties 106 return scope 107 } 108 109 return checkNotNull(previousScope) 110 } 111 } 112