1 /*
<lambda>null2  * Copyright (C) 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 package com.android.systemui.shared.animation
17 
18 import android.graphics.Point
19 import android.view.Surface
20 import android.view.Surface.Rotation
21 import android.view.View
22 import android.view.WindowManager
23 import com.android.systemui.unfold.UnfoldTransitionProgressProvider
24 import java.lang.ref.WeakReference
25 
26 /**
27  * Creates an animation where all registered views are moved into their final location
28  * by moving from the center of the screen to the sides
29  */
30 class UnfoldMoveFromCenterAnimator @JvmOverloads constructor(
31     private val windowManager: WindowManager,
32     /**
33      * Allows to set custom translation applier
34      * Could be useful when a view could be translated from
35      * several sources and we want to set the translation
36      * using custom methods instead of [View.setTranslationX] or
37      * [View.setTranslationY]
38      */
39     private val translationApplier: TranslationApplier = object : TranslationApplier {},
40     /**
41      * Allows to set custom implementation for getting
42      * view location. Could be useful if logical view bounds
43      * are different than actual bounds (e.g. view container may
44      * have larger width than width of the items in the container)
45      */
46     private val viewCenterProvider: ViewCenterProvider = object : ViewCenterProvider {},
47     /** Allows to set the alpha based on the progress. */
48     private val alphaProvider: AlphaProvider? = null
49 ) : UnfoldTransitionProgressProvider.TransitionProgressListener {
50 
51     private val screenSize = Point()
52     private var isVerticalFold = false
53 
54     private val animatedViews: MutableList<AnimatedView> = arrayListOf()
55 
56     private var lastAnimationProgress: Float = 1f
57 
58     /**
59      * Updates display properties in order to calculate the initial position for the views
60      * Must be called before [registerViewForAnimation]
61      */
62     @JvmOverloads
updateDisplayPropertiesnull63     fun updateDisplayProperties(@Rotation rotation: Int = windowManager.defaultDisplay.rotation) {
64         windowManager.defaultDisplay.getSize(screenSize)
65 
66         // Simple implementation to get current fold orientation,
67         // this might not be correct on all devices
68         // TODO: use JetPack WindowManager library to get the fold orientation
69         isVerticalFold = rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180
70     }
71 
72     /**
73      * If target view positions have changed (e.g. because of layout changes) call this method
74      * to re-query view positions and update the translations
75      */
updateViewPositionsnull76     fun updateViewPositions() {
77         animatedViews.forEach { animatedView ->
78             animatedView.view.get()?.let {
79                 animatedView.updateAnimatedView(it)
80             }
81         }
82         onTransitionProgress(lastAnimationProgress)
83     }
84 
85     /**
86      * Registers a view to be animated, the view should be measured and layouted
87      * After finishing the animation it is necessary to clear
88      * the views using [clearRegisteredViews]
89      */
registerViewForAnimationnull90     fun registerViewForAnimation(view: View) {
91         val animatedView = createAnimatedView(view)
92         animatedViews.add(animatedView)
93     }
94 
95     /**
96      * Unregisters all registered views and resets their translation
97      */
clearRegisteredViewsnull98     fun clearRegisteredViews() {
99         onTransitionProgress(1f)
100         animatedViews.clear()
101     }
102 
onTransitionProgressnull103     override fun onTransitionProgress(progress: Float) {
104         animatedViews.forEach {
105             it.applyTransition(progress)
106             it.applyAlpha(progress)
107         }
108         lastAnimationProgress = progress
109     }
110 
AnimatedViewnull111     private fun AnimatedView.applyTransition(progress: Float) {
112         view.get()?.let { view ->
113             translationApplier.apply(
114                 view = view,
115                 x = startTranslationX * (1 - progress),
116                 y = startTranslationY * (1 - progress)
117             )
118         }
119     }
120 
AnimatedViewnull121     private fun AnimatedView.applyAlpha(progress: Float) {
122         if (alphaProvider == null) return
123         view.get()?.alpha = alphaProvider.getAlpha(progress)
124     }
125 
createAnimatedViewnull126     private fun createAnimatedView(view: View): AnimatedView =
127         AnimatedView(view = WeakReference(view)).updateAnimatedView(view)
128 
129     private fun AnimatedView.updateAnimatedView(view: View): AnimatedView {
130         val viewCenter = Point()
131         viewCenterProvider.getViewCenter(view, viewCenter)
132 
133         val viewCenterX = viewCenter.x
134         val viewCenterY = viewCenter.y
135 
136         if (isVerticalFold) {
137             val distanceFromScreenCenterToViewCenter = screenSize.x / 2 - viewCenterX
138             startTranslationX = distanceFromScreenCenterToViewCenter * TRANSLATION_PERCENTAGE
139             startTranslationY = 0f
140         } else {
141             val distanceFromScreenCenterToViewCenter = screenSize.y / 2 - viewCenterY
142             startTranslationX = 0f
143             startTranslationY = distanceFromScreenCenterToViewCenter * TRANSLATION_PERCENTAGE
144         }
145 
146         return this
147     }
148 
149     /**
150      * Interface that allows to use custom logic to apply translation to view
151      */
152     interface TranslationApplier {
153         /**
154          * Called when we need to apply [x] and [y] translation to [view]
155          */
applynull156         fun apply(view: View, x: Float, y: Float) {
157             view.translationX = x
158             view.translationY = y
159         }
160     }
161 
162     /** Allows to set a custom alpha based on the progress. */
163     interface AlphaProvider {
164 
165         /** Returns the alpha views should have at a given progress. */
getAlphanull166         fun getAlpha(progress: Float): Float
167     }
168 
169     /**
170      * Interface that allows to use custom logic to get the center of the view
171      */
172     interface ViewCenterProvider {
173         /**
174          * Called when we need to get the center of the view
175          */
176         fun getViewCenter(view: View, outPoint: Point) {
177             val viewLocation = IntArray(2)
178             view.getLocationOnScreen(viewLocation)
179 
180             val viewX = viewLocation[0]
181             val viewY = viewLocation[1]
182 
183             outPoint.x = viewX + view.width / 2
184             outPoint.y = viewY + view.height / 2
185         }
186     }
187 
188     private class AnimatedView(
189         val view: WeakReference<View>,
190         var startTranslationX: Float = 0f,
191         var startTranslationY: Float = 0f
192     )
193 }
194 
195 private const val TRANSLATION_PERCENTAGE = 0.08f
196