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

Commit 2976b029 authored by Sukesh Ram's avatar Sukesh Ram
Browse files

Animate Handle Menu Closing

Animate the handle menu’s closing.

Test: Manually tested in freeform, split screen, and fullscreen modes.
Bug: 307387055
Change-Id: I4fb9aeeed563f1c3acf49b7ee38cd747e770e63f
parent e9fc0fb0
Loading
Loading
Loading
Loading
+15 −8
Original line number Diff line number Diff line
@@ -70,8 +70,8 @@ class HandleMenu {
    private int mMarginMenuStart;
    private int mMenuHeight;
    private int mMenuWidth;

    private final int mCaptionHeight;
    private HandleMenuAnimator mHandleMenuAnimator;


    HandleMenu(WindowDecoration parentDecor, int layoutResId, int captionX, int captionY,
@@ -111,20 +111,19 @@ class HandleMenu {
        mHandleMenuWindow = mParentDecor.addWindow(
                R.layout.desktop_mode_window_decor_handle_menu, "Handle Menu",
                t, ssg, x, y, mMenuWidth, mMenuHeight);
        final View handleMenuView = mHandleMenuWindow.mWindowViewHost.getView();
        mHandleMenuAnimator = new HandleMenuAnimator(handleMenuView, mMenuWidth, mCaptionHeight);
    }

    /**
     * Animates the appearance of the handle menu and its three pills.
     */
    private void animateHandleMenu() {
        final View handleMenuView = mHandleMenuWindow.mWindowViewHost.getView();
        final HandleMenuAnimator handleMenuAnimator = new HandleMenuAnimator(handleMenuView,
                mMenuWidth, mCaptionHeight);
        if (mTaskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN
                || mTaskInfo.getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW) {
            handleMenuAnimator.animateCaptionHandleExpandToOpen();
            mHandleMenuAnimator.animateCaptionHandleExpandToOpen();
        } else {
            handleMenuAnimator.animateOpen();
            mHandleMenuAnimator.animateOpen();
        }
    }

@@ -328,8 +327,16 @@ class HandleMenu {
    }

    void close() {
        final Runnable after = () -> {
            mHandleMenuWindow.releaseView();
            mHandleMenuWindow = null;
        };
        if (mTaskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN
                || mTaskInfo.getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW) {
            mHandleMenuAnimator.animateCollapseIntoHandleClose(after);
        } else {
            mHandleMenuAnimator.animateClose(after);
        }
    }

    static final class Builder {
+218 −58
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ import android.view.View.SCALE_Y
import android.view.View.TRANSLATION_Y
import android.view.View.TRANSLATION_Z
import android.view.ViewGroup
import androidx.core.animation.doOnEnd
import androidx.core.view.children
import com.android.wm.shell.R
import com.android.wm.shell.animation.Interpolators
@@ -37,27 +38,36 @@ class HandleMenuAnimator(
    private val captionHeight: Float
) {
    companion object {
        private const val MENU_Y_TRANSLATION_DURATION: Long = 150
        private const val HEADER_NONFREEFORM_SCALE_DURATION: Long = 150
        private const val HEADER_FREEFORM_SCALE_DURATION: Long = 217
        private const val HEADER_ELEVATION_DURATION: Long = 83
        private const val HEADER_CONTENT_ALPHA_DURATION: Long = 100
        private const val BODY_SCALE_DURATION: Long = 180
        private const val BODY_ALPHA_DURATION: Long = 150
        private const val BODY_ELEVATION_DURATION: Long = 83
        private const val BODY_CONTENT_ALPHA_DURATION: Long = 167

        private const val ELEVATION_DELAY: Long = 33
        private const val HEADER_CONTENT_ALPHA_DELAY: Long = 67
        private const val BODY_SCALE_DELAY: Long = 50
        private const val BODY_ALPHA_DELAY: Long = 133
        // Open animation constants
        private const val MENU_Y_TRANSLATION_OPEN_DURATION: Long = 150
        private const val HEADER_NONFREEFORM_SCALE_OPEN_DURATION: Long = 150
        private const val HEADER_FREEFORM_SCALE_OPEN_DURATION: Long = 217
        private const val HEADER_ELEVATION_OPEN_DURATION: Long = 83
        private const val HEADER_CONTENT_ALPHA_OPEN_DURATION: Long = 100
        private const val BODY_SCALE_OPEN_DURATION: Long = 180
        private const val BODY_ALPHA_OPEN_DURATION: Long = 150
        private const val BODY_ELEVATION_OPEN_DURATION: Long = 83
        private const val BODY_CONTENT_ALPHA_OPEN_DURATION: Long = 167

        private const val ELEVATION_OPEN_DELAY: Long = 33
        private const val HEADER_CONTENT_ALPHA_OPEN_DELAY: Long = 67
        private const val BODY_SCALE_OPEN_DELAY: Long = 50
        private const val BODY_ALPHA_OPEN_DELAY: Long = 133

        private const val HALF_INITIAL_SCALE: Float = 0.5f
        private const val NONFREEFORM_HEADER_INITIAL_SCALE_X: Float = 0.6f
        private const val NONFREEFORM_HEADER_INITIAL_SCALE_Y: Float = 0.05f

        // Close animation constants
        private const val HEADER_CLOSE_DELAY: Long = 20
        private const val HEADER_CLOSE_DURATION: Long = 50
        private const val HEADER_CONTENT_OPACITY_CLOSE_DELAY: Long = 25
        private const val HEADER_CONTENT_OPACITY_CLOSE_DURATION: Long = 25
        private const val BODY_CLOSE_DURATION: Long = 50
    }

    private val animators: MutableList<Animator> = mutableListOf()
    private var runningAnimation: AnimatorSet? = null

    private val appInfoPill: ViewGroup = handleMenu.requireViewById(R.id.app_info_pill)
    private val windowingPill: ViewGroup = handleMenu.requireViewById(R.id.windowing_pill)
@@ -67,9 +77,9 @@ class HandleMenuAnimator(
    fun animateOpen() {
        prepareMenuForAnimation()
        appInfoPillExpand()
        animateAppInfoPill()
        animateWindowingPill()
        animateMoreActionsPill()
        animateAppInfoPillOpen()
        animateWindowingPillOpen()
        animateMoreActionsPillOpen()
        runAnimations()
    }

@@ -81,12 +91,43 @@ class HandleMenuAnimator(
    fun animateCaptionHandleExpandToOpen() {
        prepareMenuForAnimation()
        captionHandleExpandIntoAppInfoPill()
        animateAppInfoPill()
        animateWindowingPill()
        animateMoreActionsPill()
        animateAppInfoPillOpen()
        animateWindowingPillOpen()
        animateMoreActionsPillOpen()
        runAnimations()
    }

    /**
     * Animates the closing of the handle menu. The windowing and more actions pill vanish. Then,
     * the app info pill will collapse into the shape of the caption handle in full screen and split
     * screen.
     *
     * @param after runs after the animation finishes.
     */
    fun animateCollapseIntoHandleClose(after: Runnable) {
        appInfoCollapseToHandle()
        animateAppInfoPillFadeOut()
        windowingPillClose()
        moreActionsPillClose()
        runAnimations(after)
    }

    /**
     * Animates the closing of the handle menu. The windowing and more actions pill vanish. Then,
     * the app info pill will collapse into the shape of the caption handle in full screen and split
     * screen.
     *
     * @param after runs after animation finishes.
     *
     */
    fun animateClose(after: Runnable) {
        appInfoPillCollapse()
        animateAppInfoPillFadeOut()
        windowingPillClose()
        moreActionsPillClose()
        runAnimations(after)
    }

    /**
     * Prepares the handle menu for animation. Presets the opacity of necessary menu components.
     * Presets pivots of handle menu and body pills for scaling animation.
@@ -108,20 +149,20 @@ class HandleMenuAnimator(
        moreActionsPill.pivotY = appInfoPill.measuredHeight.toFloat()
    }

    private fun animateAppInfoPill() {
    private fun animateAppInfoPillOpen() {
        // Header Elevation Animation
        animators +=
            ObjectAnimator.ofFloat(appInfoPill, TRANSLATION_Z, 1f).apply {
                startDelay = ELEVATION_DELAY
                duration = HEADER_ELEVATION_DURATION
                startDelay = ELEVATION_OPEN_DELAY
                duration = HEADER_ELEVATION_OPEN_DURATION
            }

        // Content Opacity Animation
        appInfoPill.children.forEach {
            animators +=
                ObjectAnimator.ofFloat(it, ALPHA, 1f).apply {
                    startDelay = HEADER_CONTENT_ALPHA_DELAY
                    duration = HEADER_CONTENT_ALPHA_DURATION
                    startDelay = HEADER_CONTENT_ALPHA_OPEN_DELAY
                    duration = HEADER_CONTENT_ALPHA_OPEN_DURATION
                }
        }
    }
@@ -130,17 +171,17 @@ class HandleMenuAnimator(
        // Header scaling animation
        animators +=
            ObjectAnimator.ofFloat(appInfoPill, SCALE_X, NONFREEFORM_HEADER_INITIAL_SCALE_X, 1f)
                .apply { duration = HEADER_NONFREEFORM_SCALE_DURATION }
                .apply { duration = HEADER_NONFREEFORM_SCALE_OPEN_DURATION }

        animators +=
            ObjectAnimator.ofFloat(appInfoPill, SCALE_Y, NONFREEFORM_HEADER_INITIAL_SCALE_Y, 1f)
                .apply { duration = HEADER_NONFREEFORM_SCALE_DURATION }
                .apply { duration = HEADER_NONFREEFORM_SCALE_OPEN_DURATION }

        // Downward y-translation animation
        val yStart: Float = -captionHeight / 2
        animators +=
            ObjectAnimator.ofFloat(handleMenu, TRANSLATION_Y, yStart, 0f).apply {
                duration = MENU_Y_TRANSLATION_DURATION
                duration = MENU_Y_TRANSLATION_OPEN_DURATION
            }
    }

@@ -148,98 +189,217 @@ class HandleMenuAnimator(
        // Header scaling animation
        animators +=
            ObjectAnimator.ofFloat(appInfoPill, SCALE_X, HALF_INITIAL_SCALE, 1f).apply {
                duration = HEADER_FREEFORM_SCALE_DURATION
                duration = HEADER_FREEFORM_SCALE_OPEN_DURATION
            }

        animators +=
            ObjectAnimator.ofFloat(appInfoPill, SCALE_Y, HALF_INITIAL_SCALE, 1f).apply {
                duration = HEADER_FREEFORM_SCALE_DURATION
                duration = HEADER_FREEFORM_SCALE_OPEN_DURATION
            }
    }

    private fun animateWindowingPill() {
    private fun animateWindowingPillOpen() {
        // Windowing X & Y Scaling Animation
        animators +=
            ObjectAnimator.ofFloat(windowingPill, SCALE_X, HALF_INITIAL_SCALE, 1f).apply {
                startDelay = BODY_SCALE_DELAY
                duration = BODY_SCALE_DURATION
                startDelay = BODY_SCALE_OPEN_DELAY
                duration = BODY_SCALE_OPEN_DURATION
            }

        animators +=
            ObjectAnimator.ofFloat(windowingPill, SCALE_Y, HALF_INITIAL_SCALE, 1f).apply {
                startDelay = BODY_SCALE_DELAY
                duration = BODY_SCALE_DURATION
                startDelay = BODY_SCALE_OPEN_DELAY
                duration = BODY_SCALE_OPEN_DURATION
            }

        // Windowing Opacity Animation
        animators +=
            ObjectAnimator.ofFloat(windowingPill, ALPHA, 1f).apply {
                startDelay = BODY_ALPHA_DELAY
                duration = BODY_ALPHA_DURATION
                startDelay = BODY_ALPHA_OPEN_DELAY
                duration = BODY_ALPHA_OPEN_DURATION
            }

        // Windowing Elevation Animation
        animators +=
            ObjectAnimator.ofFloat(windowingPill, TRANSLATION_Z, 1f).apply {
                startDelay = ELEVATION_DELAY
                duration = BODY_ELEVATION_DURATION
                startDelay = ELEVATION_OPEN_DELAY
                duration = BODY_ELEVATION_OPEN_DURATION
            }

        // Windowing Content Opacity Animation
        windowingPill.children.forEach {
            animators +=
                ObjectAnimator.ofFloat(it, ALPHA, 1f).apply {
                    startDelay = BODY_ALPHA_DELAY
                    duration = BODY_CONTENT_ALPHA_DURATION
                    startDelay = BODY_ALPHA_OPEN_DELAY
                    duration = BODY_CONTENT_ALPHA_OPEN_DURATION
                    interpolator = Interpolators.FAST_OUT_SLOW_IN
                }
        }
    }

    private fun animateMoreActionsPill() {
    private fun animateMoreActionsPillOpen() {
        // More Actions X & Y Scaling Animation
        animators +=
            ObjectAnimator.ofFloat(moreActionsPill, SCALE_X, HALF_INITIAL_SCALE, 1f).apply {
                startDelay = BODY_SCALE_DELAY
                duration = BODY_SCALE_DURATION
                startDelay = BODY_SCALE_OPEN_DELAY
                duration = BODY_SCALE_OPEN_DURATION
            }

        animators +=
            ObjectAnimator.ofFloat(moreActionsPill, SCALE_Y, HALF_INITIAL_SCALE, 1f).apply {
                startDelay = BODY_SCALE_DELAY
                duration = BODY_SCALE_DURATION
                startDelay = BODY_SCALE_OPEN_DELAY
                duration = BODY_SCALE_OPEN_DURATION
            }

        // More Actions Opacity Animation
        animators +=
            ObjectAnimator.ofFloat(moreActionsPill, ALPHA, 1f).apply {
                startDelay = BODY_ALPHA_DELAY
                duration = BODY_ALPHA_DURATION
                startDelay = BODY_ALPHA_OPEN_DELAY
                duration = BODY_ALPHA_OPEN_DURATION
            }

        // More Actions Elevation Animation
        animators +=
            ObjectAnimator.ofFloat(moreActionsPill, TRANSLATION_Z, 1f).apply {
                startDelay = ELEVATION_DELAY
                duration = BODY_ELEVATION_DURATION
                startDelay = ELEVATION_OPEN_DELAY
                duration = BODY_ELEVATION_OPEN_DURATION
            }

        // More Actions Content Opacity Animation
        moreActionsPill.children.forEach {
            animators +=
                ObjectAnimator.ofFloat(it, ALPHA, 1f).apply {
                    startDelay = BODY_ALPHA_DELAY
                    duration = BODY_CONTENT_ALPHA_DURATION
                    startDelay = BODY_ALPHA_OPEN_DELAY
                    duration = BODY_CONTENT_ALPHA_OPEN_DURATION
                    interpolator = Interpolators.FAST_OUT_SLOW_IN
                }
        }
    }

    /** Runs the list of animators concurrently. */
    private fun runAnimations() {
        val animatorSet = AnimatorSet()
        animatorSet.playTogether(animators)
        animatorSet.start()
    private fun appInfoPillCollapse() {
        // Header scaling animation
        animators +=
            ObjectAnimator.ofFloat(appInfoPill, SCALE_X, 0f).apply {
                startDelay = HEADER_CLOSE_DELAY
                duration = HEADER_CLOSE_DURATION
            }

        animators +=
            ObjectAnimator.ofFloat(appInfoPill, SCALE_Y, 0f).apply {
                startDelay = HEADER_CLOSE_DELAY
                duration = HEADER_CLOSE_DURATION
            }
    }

    private fun appInfoCollapseToHandle() {
        // Header X & Y Scaling Animation
        animators +=
            ObjectAnimator.ofFloat(appInfoPill, SCALE_X, NONFREEFORM_HEADER_INITIAL_SCALE_X).apply {
                startDelay = HEADER_CLOSE_DELAY
                duration = HEADER_CLOSE_DURATION
            }

        animators +=
            ObjectAnimator.ofFloat(appInfoPill, SCALE_Y, NONFREEFORM_HEADER_INITIAL_SCALE_Y).apply {
                startDelay = HEADER_CLOSE_DELAY
                duration = HEADER_CLOSE_DURATION
            }
        // Upward y-translation animation
        val yStart: Float = -captionHeight / 2
        animators +=
            ObjectAnimator.ofFloat(appInfoPill, TRANSLATION_Y, yStart).apply {
                startDelay = HEADER_CLOSE_DELAY
                duration = HEADER_CLOSE_DURATION
            }
    }

    private fun animateAppInfoPillFadeOut() {
        // Header Content Opacity Animation
        appInfoPill.children.forEach {
            animators +=
                ObjectAnimator.ofFloat(it, ALPHA, 0f).apply {
                    startDelay = HEADER_CONTENT_OPACITY_CLOSE_DELAY
                    duration = HEADER_CONTENT_OPACITY_CLOSE_DURATION
                }
        }
    }

    private fun windowingPillClose() {
        // Windowing X & Y Scaling Animation
        animators +=
            ObjectAnimator.ofFloat(windowingPill, SCALE_X, HALF_INITIAL_SCALE).apply {
                duration = BODY_CLOSE_DURATION
            }

        animators +=
            ObjectAnimator.ofFloat(windowingPill, SCALE_Y, HALF_INITIAL_SCALE).apply {
                duration = BODY_CLOSE_DURATION
            }

        // windowing Animation
        animators +=
            ObjectAnimator.ofFloat(windowingPill, ALPHA, 0f).apply {
                duration = BODY_CLOSE_DURATION
            }

        animators +=
            ObjectAnimator.ofFloat(windowingPill, ALPHA, 0f).apply {
                duration = BODY_CLOSE_DURATION
            }
    }

    private fun moreActionsPillClose() {
        // More Actions X & Y Scaling Animation
        animators +=
            ObjectAnimator.ofFloat(moreActionsPill, SCALE_X, HALF_INITIAL_SCALE).apply {
                duration = BODY_CLOSE_DURATION
            }

        animators +=
            ObjectAnimator.ofFloat(moreActionsPill, SCALE_Y, HALF_INITIAL_SCALE).apply {
                duration = BODY_CLOSE_DURATION
            }

        // More Actions Opacity Animation
        animators +=
            ObjectAnimator.ofFloat(moreActionsPill, ALPHA, 0f).apply {
                duration = BODY_CLOSE_DURATION
            }

        animators +=
            ObjectAnimator.ofFloat(moreActionsPill, ALPHA, 0f).apply {
                duration = BODY_CLOSE_DURATION
            }

        // upward more actions pill y-translation animation
        val yStart: Float = -captionHeight / 2
        animators +=
            ObjectAnimator.ofFloat(moreActionsPill, TRANSLATION_Y, yStart).apply {
                duration = BODY_CLOSE_DURATION
            }
    }

    /**
     * Runs the list of hide animators concurrently.
     *
     * @param after runs after animation finishes.
     */
    private fun runAnimations(after: Runnable? = null) {
        runningAnimation?.apply {
            // Remove all listeners, so that after runnable isn't triggered upon cancel.
            removeAllListeners()
            // If an animation runs while running animation is triggered, gracefully cancel.
            cancel()
        }

        runningAnimation = AnimatorSet().apply {
            playTogether(animators)
            animators.clear()
            doOnEnd {
                after?.run()
                runningAnimation = null
            }
            start()
        }
    }
}