Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b905ab10 authored by Jordan Demeulenaere's avatar Jordan Demeulenaere
Browse files

Animate the Internet dialog height changes

This CL animates the height of the Internet dialog when it changes,
using a custom layout animator.

Note that I have tried using TransitionManager to get a better animation
(that would also properly fade elements in & out) but hit a few bugs
that made me lose confidence in TM (see b/203045010).

See b/201046726#comment20 for a video.

Bug: 201046726
Test: Manual
Change-Id: I02a0bfe67b705ea68e0d82a310a4361a94c1200d
parent 4233b5b6
Loading
Loading
Loading
Loading
+113 −4
Original line number Diff line number Diff line
@@ -16,11 +16,16 @@

package com.android.systemui.animation

import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
import android.app.Dialog
import android.content.Context
import android.graphics.Color
import android.graphics.Rect
import android.os.Looper
import android.util.Log
import android.util.MathUtils
import android.view.GhostView
import android.view.Gravity
import android.view.View
@@ -32,6 +37,7 @@ import android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR
import android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
import android.view.WindowManagerPolicyConstants
import android.widget.FrameLayout
import kotlin.math.roundToInt

private const val TAG = "DialogLaunchAnimator"

@@ -52,7 +58,8 @@ class DialogLaunchAnimator(
    private val currentAnimations = hashSetOf<DialogLaunchAnimation>()

    /**
     * Show [dialog] by expanding it from [view].
     * Show [dialog] by expanding it from [view]. If [animateBackgroundBoundsChange] is true, then
     * the background of the dialog will be animated when the dialog bounds change.
     *
     * Caveats: When calling this function, the dialog content view will actually be stolen and
     * attached to a different dialog (and thus a different window) which means that the actual
@@ -60,7 +67,12 @@ class DialogLaunchAnimator(
     * must call dismiss(), hide() and show() on the [Dialog] returned by this function to actually
     * dismiss, hide or show the dialog.
     */
    fun showFromView(dialog: Dialog, view: View): Dialog {
    @JvmOverloads
    fun showFromView(
        dialog: Dialog,
        view: View,
        animateBackgroundBoundsChange: Boolean = false
    ): Dialog {
        if (Looper.myLooper() != Looper.getMainLooper()) {
            throw IllegalStateException(
                "showFromView must be called from the main thread and dialog must be created in " +
@@ -78,7 +90,8 @@ class DialogLaunchAnimator(

        val launchAnimation = DialogLaunchAnimation(
            context, launchAnimator, hostDialogProvider, view,
            onDialogDismissed = { currentAnimations.remove(it) }, originalDialog = dialog)
            onDialogDismissed = { currentAnimations.remove(it) }, originalDialog = dialog,
            animateBackgroundBoundsChange)
        val hostDialog = launchAnimation.hostDialog
        currentAnimations.add(launchAnimation)

@@ -208,7 +221,10 @@ private class DialogLaunchAnimation(
    private val onDialogDismissed: (DialogLaunchAnimation) -> Unit,

    /** The original dialog whose content will be shown and animate in/out in [hostDialog]. */
    private val originalDialog: Dialog
    private val originalDialog: Dialog,

    /** Whether we should animate the dialog background when its bounds change. */
    private val animateBackgroundBoundsChange: Boolean
) {
    /**
     * The fullscreen dialog to which we will add the content view [originalDialogView] of
@@ -247,6 +263,11 @@ private class DialogLaunchAnimation(

    private var isTouchSurfaceGhostDrawn = false
    private var isOriginalDialogViewLaidOut = false
    private var backgroundLayoutListener = if (animateBackgroundBoundsChange) {
        AnimatedBoundsLayoutListener()
    } else {
        null
    }

    fun start() {
        // Show the host (fullscreen) dialog, to which we will add the stolen dialog view.
@@ -494,6 +515,13 @@ private class DialogLaunchAnimation(
                if (dismissRequested) {
                    hostDialog.dismiss()
                }

                // If necessary, we animate the dialog background when its bounds change. We do it
                // at the end of the launch animation, because the lauch animation already correctly
                // handles bounds changes.
                if (backgroundLayoutListener != null) {
                    dialogContentParent.addOnLayoutChangeListener(backgroundLayoutListener)
                }
            }
        )
    }
@@ -565,6 +593,10 @@ private class DialogLaunchAnimation(
                touchSurface.visibility = View.VISIBLE
                dialogContentParent.visibility = View.INVISIBLE

                if (backgroundLayoutListener != null) {
                    dialogContentParent.removeOnLayoutChangeListener(backgroundLayoutListener)
                }

                // The animated ghost was just removed. We create a temporary ghost that will be
                // removed only once we draw the touch surface, to avoid flickering that would
                // happen when removing the ghost too early (before the touch surface is drawn).
@@ -675,4 +707,81 @@ private class DialogLaunchAnimation(

        return (touchSurface.parent as? View)?.isShown ?: true
    }

    /** A layout listener to animate the change of bounds of the dialog background.  */
    class AnimatedBoundsLayoutListener : View.OnLayoutChangeListener {
        companion object {
            private const val ANIMATION_DURATION = 500L
        }

        private var lastBounds: Rect? = null
        private var currentAnimator: ValueAnimator? = null

        override fun onLayoutChange(
            view: View,
            left: Int,
            top: Int,
            right: Int,
            bottom: Int,
            oldLeft: Int,
            oldTop: Int,
            oldRight: Int,
            oldBottom: Int
        ) {
            // Don't animate if bounds didn't actually change.
            if (left == oldLeft && top == oldTop && right == oldRight && bottom == oldBottom) {
                // Make sure that we that the last bounds set by the animator were not overridden.
                lastBounds?.let { bounds ->
                    view.left = bounds.left
                    view.top = bounds.top
                    view.right = bounds.right
                    view.bottom = bounds.bottom
                }
                return
            }

            if (lastBounds == null) {
                lastBounds = Rect(oldLeft, oldTop, oldRight, oldBottom)
            }

            val bounds = lastBounds!!
            val startLeft = bounds.left
            val startTop = bounds.top
            val startRight = bounds.right
            val startBottom = bounds.bottom

            currentAnimator?.cancel()
            currentAnimator = null

            val animator = ValueAnimator.ofFloat(0f, 1f).apply {
                duration = ANIMATION_DURATION
                interpolator = Interpolators.STANDARD

                addListener(object : AnimatorListenerAdapter() {
                    override fun onAnimationEnd(animation: Animator) {
                        currentAnimator = null
                    }
                })

                addUpdateListener { animatedValue ->
                    val progress = animatedValue.animatedFraction

                    // Compute new bounds.
                    bounds.left = MathUtils.lerp(startLeft, left, progress).roundToInt()
                    bounds.top = MathUtils.lerp(startTop, top, progress).roundToInt()
                    bounds.right = MathUtils.lerp(startRight, right, progress).roundToInt()
                    bounds.bottom = MathUtils.lerp(startBottom, bottom, progress).roundToInt()

                    // Set the new bounds.
                    view.left = bounds.left
                    view.top = bounds.top
                    view.right = bounds.right
                    view.bottom = bounds.bottom
                }
            }

            currentAnimator = animator
            animator.start()
        }
    }
}
+2 −1
Original line number Diff line number Diff line
@@ -63,7 +63,8 @@ class InternetDialogFactory @Inject constructor(
                    canConfigMobileData, canConfigWifi, aboveStatusBar, uiEventLogger, handler,
                    executor)
            if (view != null) {
                dialogLaunchAnimator.showFromView(internetDialog!!, view)
                dialogLaunchAnimator.showFromView(internetDialog!!, view,
                    animateBackgroundBoundsChange = true)
            } else {
                internetDialog?.show()
            }