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