1 /*
<lambda>null2  * Copyright (C) 2020 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.systemui.controls.management
18 
19 import android.animation.Animator
20 import android.animation.AnimatorListenerAdapter
21 import android.animation.AnimatorSet
22 import android.animation.ObjectAnimator
23 import android.annotation.IdRes
24 import android.content.Intent
25 import android.transition.Transition
26 import android.transition.TransitionValues
27 import android.util.Log
28 import android.view.View
29 import android.view.ViewGroup
30 import android.view.Window
31 import androidx.lifecycle.Lifecycle
32 import androidx.lifecycle.LifecycleObserver
33 import androidx.lifecycle.OnLifecycleEvent
34 import com.android.systemui.res.R
35 import com.android.app.animation.Interpolators
36 import com.android.systemui.controls.ui.ControlsUiController
37 
38 object ControlsAnimations {
39 
40     private const val ALPHA_EXIT_DURATION = 183L
41     private const val ALPHA_ENTER_DELAY = ALPHA_EXIT_DURATION
42     private const val ALPHA_ENTER_DURATION = 350L - ALPHA_ENTER_DELAY
43 
44     private const val Y_TRANSLATION_EXIT_DURATION = 183L
45     private const val Y_TRANSLATION_ENTER_DELAY = Y_TRANSLATION_EXIT_DURATION - ALPHA_ENTER_DELAY
46     private const val Y_TRANSLATION_ENTER_DURATION = 400L - Y_TRANSLATION_EXIT_DURATION
47     private var translationY: Float = -1f
48 
49     /**
50      * Setup an activity to handle enter/exit animations. [view] should be the root of the content.
51      * Fade and translate together.
52      */
53     fun observerForAnimations(
54             view: ViewGroup,
55             window: Window,
56             intent: Intent,
57             animateY: Boolean = true
58     ): LifecycleObserver {
59         return object : LifecycleObserver {
60             var showAnimation = intent.getBooleanExtra(ControlsUiController.EXTRA_ANIMATE, false)
61 
62             init {
63                 // Must flag the parent group to move it all together, and set the initial
64                 // transitionAlpha to 0.0f. This property is reserved for fade animations.
65                 view.setTransitionGroup(true)
66                 view.transitionAlpha = 0.0f
67 
68                 if (translationY == -1f) {
69                     if (animateY) {
70                         translationY = view.context.resources.getDimensionPixelSize(
71                                 R.dimen.global_actions_controls_y_translation).toFloat()
72                     } else {
73                         translationY = 0f
74                     }
75                 }
76             }
77 
78             @OnLifecycleEvent(Lifecycle.Event.ON_START)
79             fun setup() {
80                 with(window) {
81                     allowEnterTransitionOverlap = true
82                     enterTransition = enterWindowTransition(view.getId())
83                     exitTransition = exitWindowTransition(view.getId())
84                     reenterTransition = enterWindowTransition(view.getId())
85                     returnTransition = exitWindowTransition(view.getId())
86                 }
87             }
88 
89             @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
90             fun enterAnimation() {
91                 if (showAnimation) {
92                     ControlsAnimations.enterAnimation(view).start()
93                     showAnimation = false
94                 }
95             }
96 
97             @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
98             fun resetAnimation() {
99                 view.translationY = 0f
100             }
101         }
102     }
103 
104     fun enterAnimation(view: View): Animator {
105         Log.d(ControlsUiController.TAG, "Enter animation for $view")
106 
107         view.transitionAlpha = 0.0f
108         view.alpha = 1.0f
109 
110         view.translationY = translationY
111 
112         val alphaAnimator = ObjectAnimator.ofFloat(view, "transitionAlpha", 0.0f, 1.0f).apply {
113             interpolator = Interpolators.DECELERATE_QUINT
114             startDelay = ALPHA_ENTER_DELAY
115             duration = ALPHA_ENTER_DURATION
116         }
117 
118         val yAnimator = ObjectAnimator.ofFloat(view, "translationY", 0.0f).apply {
119             interpolator = Interpolators.DECELERATE_QUINT
120             startDelay = Y_TRANSLATION_ENTER_DURATION
121             duration = Y_TRANSLATION_ENTER_DURATION
122         }
123 
124         return AnimatorSet().apply {
125             playTogether(alphaAnimator, yAnimator)
126         }
127     }
128 
129     /**
130      * Properly handle animations originating from dialogs. Activity transitions require
131      * transitioning between two activities, so expose this method for dialogs to animate
132      * on exit.
133      */
134     @JvmStatic
135     fun exitAnimation(view: View, onEnd: Runnable? = null): Animator {
136         Log.d(ControlsUiController.TAG, "Exit animation for $view")
137 
138         val alphaAnimator = ObjectAnimator.ofFloat(view, "transitionAlpha", 0.0f).apply {
139             interpolator = Interpolators.ACCELERATE
140             duration = ALPHA_EXIT_DURATION
141         }
142 
143         view.translationY = 0.0f
144         val yAnimator = ObjectAnimator.ofFloat(view, "translationY", -translationY).apply {
145             interpolator = Interpolators.ACCELERATE
146             duration = Y_TRANSLATION_EXIT_DURATION
147         }
148 
149         return AnimatorSet().apply {
150             playTogether(alphaAnimator, yAnimator)
151             onEnd?.let {
152                 addListener(object : AnimatorListenerAdapter() {
153                     override fun onAnimationEnd(animation: Animator) {
154                         it.run()
155                     }
156                 })
157             }
158         }
159     }
160 
161     fun enterWindowTransition(@IdRes id: Int) =
162         WindowTransition({ view: View -> enterAnimation(view) }).apply {
163             addTarget(id)
164         }
165 
166     fun exitWindowTransition(@IdRes id: Int) =
167         WindowTransition({ view: View -> exitAnimation(view) }).apply {
168             addTarget(id)
169         }
170 }
171 
172 /**
173  * In order to animate, at least one property must be marked on each view that should move.
174  * Setting "item" is just a flag to indicate that it should move by the animator.
175  */
176 class WindowTransition(
177     val animator: (view: View) -> Animator
178 ) : Transition() {
captureStartValuesnull179     override fun captureStartValues(tv: TransitionValues) {
180         tv.values["item"] = 0.0f
181     }
182 
captureEndValuesnull183     override fun captureEndValues(tv: TransitionValues) {
184         tv.values["item"] = 1.0f
185     }
186 
createAnimatornull187     override fun createAnimator(
188         sceneRoot: ViewGroup,
189         startValues: TransitionValues?,
190         endValues: TransitionValues?
191     ): Animator? = animator(startValues!!.view)
192 }
193