blob: 4aa597ef3d28eede25acf5d63a9870bad34fc53e [file] [log] [blame]
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.controls.management
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.AnimatorSet
import android.animation.ObjectAnimator
import android.annotation.IdRes
import android.content.Intent
import android.transition.Transition
import android.transition.TransitionValues
import android.util.Log
import android.view.View
import android.view.ViewGroup
import android.view.Window
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.OnLifecycleEvent
import com.android.systemui.R
import com.android.systemui.animation.Interpolators
import com.android.systemui.controls.ui.ControlsUiController
object ControlsAnimations {
private const val ALPHA_EXIT_DURATION = 183L
private const val ALPHA_ENTER_DELAY = ALPHA_EXIT_DURATION
private const val ALPHA_ENTER_DURATION = 350L - ALPHA_ENTER_DELAY
private const val Y_TRANSLATION_EXIT_DURATION = 183L
private const val Y_TRANSLATION_ENTER_DELAY = Y_TRANSLATION_EXIT_DURATION - ALPHA_ENTER_DELAY
private const val Y_TRANSLATION_ENTER_DURATION = 400L - Y_TRANSLATION_EXIT_DURATION
private var translationY: Float = -1f
/**
* Setup an activity to handle enter/exit animations. [view] should be the root of the content.
* Fade and translate together.
*/
fun observerForAnimations(view: ViewGroup, window: Window, intent: Intent): LifecycleObserver {
return object : LifecycleObserver {
var showAnimation = intent.getBooleanExtra(ControlsUiController.EXTRA_ANIMATE, false)
init {
// Must flag the parent group to move it all together, and set the initial
// transitionAlpha to 0.0f. This property is reserved for fade animations.
view.setTransitionGroup(true)
view.transitionAlpha = 0.0f
if (translationY == -1f) {
translationY = view.context.resources.getDimensionPixelSize(
R.dimen.global_actions_controls_y_translation).toFloat()
}
}
@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun setup() {
with(window) {
allowEnterTransitionOverlap = true
enterTransition = enterWindowTransition(view.getId())
exitTransition = exitWindowTransition(view.getId())
reenterTransition = enterWindowTransition(view.getId())
returnTransition = exitWindowTransition(view.getId())
}
}
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
fun enterAnimation() {
if (showAnimation) {
ControlsAnimations.enterAnimation(view).start()
showAnimation = false
}
}
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun resetAnimation() {
view.translationY = 0f
}
}
}
fun enterAnimation(view: View): Animator {
Log.d(ControlsUiController.TAG, "Enter animation for $view")
view.transitionAlpha = 0.0f
view.alpha = 1.0f
view.translationY = translationY
val alphaAnimator = ObjectAnimator.ofFloat(view, "transitionAlpha", 0.0f, 1.0f).apply {
interpolator = Interpolators.DECELERATE_QUINT
startDelay = ALPHA_ENTER_DELAY
duration = ALPHA_ENTER_DURATION
}
val yAnimator = ObjectAnimator.ofFloat(view, "translationY", 0.0f).apply {
interpolator = Interpolators.DECELERATE_QUINT
startDelay = Y_TRANSLATION_ENTER_DURATION
duration = Y_TRANSLATION_ENTER_DURATION
}
return AnimatorSet().apply {
playTogether(alphaAnimator, yAnimator)
}
}
/**
* Properly handle animations originating from dialogs. Activity transitions require
* transitioning between two activities, so expose this method for dialogs to animate
* on exit.
*/
@JvmStatic
fun exitAnimation(view: View, onEnd: Runnable? = null): Animator {
Log.d(ControlsUiController.TAG, "Exit animation for $view")
val alphaAnimator = ObjectAnimator.ofFloat(view, "transitionAlpha", 0.0f).apply {
interpolator = Interpolators.ACCELERATE
duration = ALPHA_EXIT_DURATION
}
view.translationY = 0.0f
val yAnimator = ObjectAnimator.ofFloat(view, "translationY", -translationY).apply {
interpolator = Interpolators.ACCELERATE
duration = Y_TRANSLATION_EXIT_DURATION
}
return AnimatorSet().apply {
playTogether(alphaAnimator, yAnimator)
onEnd?.let {
addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
it.run()
}
})
}
}
}
fun enterWindowTransition(@IdRes id: Int) =
WindowTransition({ view: View -> enterAnimation(view) }).apply {
addTarget(id)
}
fun exitWindowTransition(@IdRes id: Int) =
WindowTransition({ view: View -> exitAnimation(view) }).apply {
addTarget(id)
}
}
/**
* In order to animate, at least one property must be marked on each view that should move.
* Setting "item" is just a flag to indicate that it should move by the animator.
*/
class WindowTransition(
val animator: (view: View) -> Animator
) : Transition() {
override fun captureStartValues(tv: TransitionValues) {
tv.values["item"] = 0.0f
}
override fun captureEndValues(tv: TransitionValues) {
tv.values["item"] = 1.0f
}
override fun createAnimator(
sceneRoot: ViewGroup,
startValues: TransitionValues?,
endValues: TransitionValues?
): Animator? = animator(startValues!!.view)
}