Skip to content
Snippets Groups Projects
Commit fd6831c3 authored by Sukesh Ram's avatar Sukesh Ram
Browse files

Animate Handle Menu Open & Caption Handle Vanish

Animate the handle menu’s opening. For full screen, the caption handle should expand until it assumes the shape of the top handle menu pill (app info pill). The spec for freeform is not yet specified. As the handle menu opens, the caption handle vanishes.

Test: Manually tested in freeform, splitscreen, and fullscreen modes
Bug: 301494758
Change-Id: I8cc7305813c083a2b2ad6c19a523173162bc021e
parent b8625193
No related branches found
No related tags found
No related merge requests found
Showing
with 321 additions and 9 deletions
......@@ -22,6 +22,7 @@
android:orientation="vertical">
<LinearLayout
android:id="@+id/app_info_pill"
android:layout_width="match_parent"
android:layout_height="@dimen/desktop_mode_handle_menu_app_info_pill_height"
android:layout_marginTop="@dimen/desktop_mode_handle_menu_margin_top"
......@@ -66,6 +67,7 @@
</LinearLayout>
<LinearLayout
android:id="@+id/windowing_pill"
android:layout_width="match_parent"
android:layout_height="@dimen/desktop_mode_handle_menu_windowing_pill_height"
android:layout_marginTop="@dimen/desktop_mode_handle_menu_pill_spacing_margin"
......@@ -116,6 +118,7 @@
</LinearLayout>
<LinearLayout
android:id="@+id/more_actions_pill"
android:layout_width="match_parent"
android:layout_height="@dimen/desktop_mode_handle_menu_more_actions_pill_height"
android:layout_marginTop="@dimen/desktop_mode_handle_menu_pill_spacing_margin"
......
......@@ -455,7 +455,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
}
/**
* Create and display handle menu window
* Create and display handle menu window.
*/
void createHandleMenu() {
mHandleMenu = new HandleMenu.Builder(this)
......@@ -466,15 +466,18 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
.setLayoutId(mRelayoutParams.mLayoutResId)
.setCaptionPosition(mRelayoutParams.mCaptionX, mRelayoutParams.mCaptionY)
.setWindowingButtonsVisible(DesktopModeStatus.isEnabled())
.setCaptionHeight(mResult.mCaptionHeight)
.build();
mWindowDecorViewHolder.onHandleMenuOpened();
mHandleMenu.show();
}
/**
* Close the handle menu window
* Close the handle menu window.
*/
void closeHandleMenu() {
if (!isHandleMenuActive()) return;
mWindowDecorViewHolder.onHandleMenuClosed();
mHandleMenu.close();
mHandleMenu = null;
}
......
......@@ -71,10 +71,13 @@ class HandleMenu {
private int mMenuHeight;
private int mMenuWidth;
private final int mCaptionHeight;
HandleMenu(WindowDecoration parentDecor, int layoutResId, int captionX, int captionY,
View.OnClickListener onClickListener, View.OnTouchListener onTouchListener,
Drawable appIcon, CharSequence appName, boolean shouldShowWindowingPill) {
Drawable appIcon, CharSequence appName, boolean shouldShowWindowingPill,
int captionHeight) {
mParentDecor = parentDecor;
mContext = mParentDecor.mDecorWindowContext;
mTaskInfo = mParentDecor.mTaskInfo;
......@@ -86,6 +89,7 @@ class HandleMenu {
mAppIcon = appIcon;
mAppName = appName;
mShouldShowWindowingPill = shouldShowWindowingPill;
mCaptionHeight = captionHeight;
loadHandleMenuDimensions();
updateHandleMenuPillPositions();
}
......@@ -98,6 +102,7 @@ class HandleMenu {
ssg.addTransaction(t);
ssg.markSyncReady();
setupHandleMenu();
animateHandleMenu();
}
private void createHandleMenuWindow(SurfaceControl.Transaction t, SurfaceSyncGroup ssg) {
......@@ -108,6 +113,21 @@ class HandleMenu {
t, ssg, x, y, mMenuWidth, mMenuHeight);
}
/**
* 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();
} else {
handleMenuAnimator.animateOpen();
}
}
/**
* Set up all three pills of the handle menu: app info pill, windowing pill, & more actions
* pill.
......@@ -322,6 +342,7 @@ class HandleMenu {
private int mCaptionX;
private int mCaptionY;
private boolean mShowWindowingPill;
private int mCaptionHeight;
Builder(@NonNull WindowDecoration parent) {
......@@ -364,9 +385,14 @@ class HandleMenu {
return this;
}
Builder setCaptionHeight(int captionHeight) {
mCaptionHeight = captionHeight;
return this;
}
HandleMenu build() {
return new HandleMenu(mParent, mLayoutId, mCaptionX, mCaptionY, mOnClickListener,
mOnTouchListener, mAppIcon, mName, mShowWindowingPill);
mOnTouchListener, mAppIcon, mName, mShowWindowingPill, mCaptionHeight);
}
}
}
/*
* Copyright (C) 2023 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.wm.shell.windowdecor
import android.animation.Animator
import android.animation.AnimatorSet
import android.animation.ObjectAnimator
import android.view.View
import android.view.View.ALPHA
import android.view.View.SCALE_X
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.view.children
import com.android.wm.shell.R
import com.android.wm.shell.animation.Interpolators
/** Animates the Handle Menu opening. */
class HandleMenuAnimator(
private val handleMenu: View,
private val menuWidth: Int,
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
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
}
private val animators: MutableList<Animator> = mutableListOf()
private val appInfoPill: ViewGroup = handleMenu.requireViewById(R.id.app_info_pill)
private val windowingPill: ViewGroup = handleMenu.requireViewById(R.id.windowing_pill)
private val moreActionsPill: ViewGroup = handleMenu.requireViewById(R.id.more_actions_pill)
/** Animates the opening of the handle menu. */
fun animateOpen() {
prepareMenuForAnimation()
appInfoPillExpand()
animateAppInfoPill()
animateWindowingPill()
animateMoreActionsPill()
runAnimations()
}
/**
* Animates the opening of the handle menu. The caption handle in full screen and split screen
* will expand until it assumes the shape of the app info pill. Then, the other two pills will
* appear.
*/
fun animateCaptionHandleExpandToOpen() {
prepareMenuForAnimation()
captionHandleExpandIntoAppInfoPill()
animateAppInfoPill()
animateWindowingPill()
animateMoreActionsPill()
runAnimations()
}
/**
* Prepares the handle menu for animation. Presets the opacity of necessary menu components.
* Presets pivots of handle menu and body pills for scaling animation.
*/
private fun prepareMenuForAnimation() {
// Preset opacity
appInfoPill.children.forEach { it.alpha = 0f }
windowingPill.alpha = 0f
moreActionsPill.alpha = 0f
// Setup pivots.
handleMenu.pivotX = menuWidth / 2f
handleMenu.pivotY = 0f
windowingPill.pivotX = menuWidth / 2f
windowingPill.pivotY = appInfoPill.measuredHeight.toFloat()
moreActionsPill.pivotX = menuWidth / 2f
moreActionsPill.pivotY = appInfoPill.measuredHeight.toFloat()
}
private fun animateAppInfoPill() {
// Header Elevation Animation
animators +=
ObjectAnimator.ofFloat(appInfoPill, TRANSLATION_Z, 1f).apply {
startDelay = ELEVATION_DELAY
duration = HEADER_ELEVATION_DURATION
}
// Content Opacity Animation
appInfoPill.children.forEach {
animators +=
ObjectAnimator.ofFloat(it, ALPHA, 1f).apply {
startDelay = HEADER_CONTENT_ALPHA_DELAY
duration = HEADER_CONTENT_ALPHA_DURATION
}
}
}
private fun captionHandleExpandIntoAppInfoPill() {
// Header scaling animation
animators +=
ObjectAnimator.ofFloat(appInfoPill, SCALE_X, NONFREEFORM_HEADER_INITIAL_SCALE_X, 1f)
.apply { duration = HEADER_NONFREEFORM_SCALE_DURATION }
animators +=
ObjectAnimator.ofFloat(appInfoPill, SCALE_Y, NONFREEFORM_HEADER_INITIAL_SCALE_Y, 1f)
.apply { duration = HEADER_NONFREEFORM_SCALE_DURATION }
// Downward y-translation animation
val yStart: Float = -captionHeight / 2
animators +=
ObjectAnimator.ofFloat(handleMenu, TRANSLATION_Y, yStart, 0f).apply {
duration = MENU_Y_TRANSLATION_DURATION
}
}
private fun appInfoPillExpand() {
// Header scaling animation
animators +=
ObjectAnimator.ofFloat(appInfoPill, SCALE_X, HALF_INITIAL_SCALE, 1f).apply {
duration = HEADER_FREEFORM_SCALE_DURATION
}
animators +=
ObjectAnimator.ofFloat(appInfoPill, SCALE_Y, HALF_INITIAL_SCALE, 1f).apply {
duration = HEADER_FREEFORM_SCALE_DURATION
}
}
private fun animateWindowingPill() {
// Windowing X & Y Scaling Animation
animators +=
ObjectAnimator.ofFloat(windowingPill, SCALE_X, HALF_INITIAL_SCALE, 1f).apply {
startDelay = BODY_SCALE_DELAY
duration = BODY_SCALE_DURATION
}
animators +=
ObjectAnimator.ofFloat(windowingPill, SCALE_Y, HALF_INITIAL_SCALE, 1f).apply {
startDelay = BODY_SCALE_DELAY
duration = BODY_SCALE_DURATION
}
// Windowing Opacity Animation
animators +=
ObjectAnimator.ofFloat(windowingPill, ALPHA, 1f).apply {
startDelay = BODY_ALPHA_DELAY
duration = BODY_ALPHA_DURATION
}
// Windowing Elevation Animation
animators +=
ObjectAnimator.ofFloat(windowingPill, TRANSLATION_Z, 1f).apply {
startDelay = ELEVATION_DELAY
duration = BODY_ELEVATION_DURATION
}
// Windowing Content Opacity Animation
windowingPill.children.forEach {
animators +=
ObjectAnimator.ofFloat(it, ALPHA, 1f).apply {
startDelay = BODY_ALPHA_DELAY
duration = BODY_CONTENT_ALPHA_DURATION
interpolator = Interpolators.FAST_OUT_SLOW_IN
}
}
}
private fun animateMoreActionsPill() {
// 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
}
animators +=
ObjectAnimator.ofFloat(moreActionsPill, SCALE_Y, HALF_INITIAL_SCALE, 1f).apply {
startDelay = BODY_SCALE_DELAY
duration = BODY_SCALE_DURATION
}
// More Actions Opacity Animation
animators +=
ObjectAnimator.ofFloat(moreActionsPill, ALPHA, 1f).apply {
startDelay = BODY_ALPHA_DELAY
duration = BODY_ALPHA_DURATION
}
// More Actions Elevation Animation
animators +=
ObjectAnimator.ofFloat(moreActionsPill, TRANSLATION_Z, 1f).apply {
startDelay = ELEVATION_DELAY
duration = BODY_ELEVATION_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
interpolator = Interpolators.FAST_OUT_SLOW_IN
}
}
}
/** Runs the list of animators concurrently. */
private fun runAnimations() {
val animatorSet = AnimatorSet()
animatorSet.playTogether(animators)
animatorSet.start()
animators.clear()
}
}
......@@ -269,10 +269,10 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
.build();
}
final int captionHeight = loadDimensionPixelSize(resources, params.mCaptionHeightId);
outResult.mCaptionHeight = loadDimensionPixelSize(resources, params.mCaptionHeightId);
final int captionWidth = taskBounds.width();
startT.setWindowCrop(mCaptionContainerSurface, captionWidth, captionHeight)
startT.setWindowCrop(mCaptionContainerSurface, captionWidth, outResult.mCaptionHeight)
.setLayer(mCaptionContainerSurface, CAPTION_LAYER_Z_ORDER)
.show(mCaptionContainerSurface);
......@@ -283,7 +283,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
mCaptionInsetsRect.set(taskBounds);
if (mIsCaptionVisible) {
mCaptionInsetsRect.bottom =
mCaptionInsetsRect.top + captionHeight + params.mCaptionY;
mCaptionInsetsRect.top + outResult.mCaptionHeight + params.mCaptionY;
wct.addInsetsSource(mTaskInfo.token,
mOwner, 0 /* index */, WindowInsets.Type.captionBar(), mCaptionInsetsRect);
wct.addInsetsSource(mTaskInfo.token,
......@@ -348,7 +348,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
// Caption view
mCaptionWindowManager.setConfiguration(taskConfig);
final WindowManager.LayoutParams lp =
new WindowManager.LayoutParams(captionWidth, captionHeight,
new WindowManager.LayoutParams(captionWidth, outResult.mCaptionHeight,
WindowManager.LayoutParams.TYPE_APPLICATION,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT);
lp.setTitle("Caption of Task=" + mTaskInfo.taskId);
......@@ -569,6 +569,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
}
static class RelayoutResult<T extends View & TaskFocusStateConsumer> {
int mCaptionHeight;
int mWidth;
int mHeight;
T mRootView;
......@@ -576,6 +577,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
void reset() {
mWidth = 0;
mHeight = 0;
mCaptionHeight = 0;
mRootView = null;
}
}
......
......@@ -48,7 +48,6 @@ internal class DesktopModeAppControlsWindowDecorationViewHolder(
}
override fun bindData(taskInfo: RunningTaskInfo) {
val captionDrawable = captionView.background as GradientDrawable
taskInfo.taskDescription?.statusBarColor?.let {
captionDrawable.setColor(it)
......@@ -63,6 +62,10 @@ internal class DesktopModeAppControlsWindowDecorationViewHolder(
appNameTextView.setTextColor(getCaptionAppNameTextColor(taskInfo))
}
override fun onHandleMenuOpened() {}
override fun onHandleMenuClosed() {}
private fun getCaptionAppNameTextColor(taskInfo: RunningTaskInfo): Int {
return if (shouldUseLightCaptionColors(taskInfo)) {
context.getColor(R.color.desktop_mode_caption_app_name_light)
......
package com.android.wm.shell.windowdecor.viewholder
import android.animation.ObjectAnimator
import android.app.ActivityManager.RunningTaskInfo
import android.content.res.ColorStateList
import android.graphics.drawable.GradientDrawable
import android.view.View
import android.widget.ImageButton
import com.android.wm.shell.R
import com.android.wm.shell.animation.Interpolators
/**
* A desktop mode window decoration used when the window is in full "focus" (i.e. fullscreen). It
......@@ -17,6 +19,10 @@ internal class DesktopModeFocusedWindowDecorationViewHolder(
onCaptionButtonClickListener: View.OnClickListener
) : DesktopModeWindowDecorationViewHolder(rootView) {
companion object {
private const val CAPTION_HANDLE_ANIMATION_DURATION: Long = 100
}
private val captionView: View = rootView.requireViewById(R.id.desktop_mode_caption)
private val captionHandle: ImageButton = rootView.requireViewById(R.id.caption_handle)
......@@ -35,6 +41,14 @@ internal class DesktopModeFocusedWindowDecorationViewHolder(
captionHandle.imageTintList = ColorStateList.valueOf(getCaptionHandleBarColor(taskInfo))
}
override fun onHandleMenuOpened() {
animateCaptionHandleAlpha(startValue = 0f, endValue = 1f)
}
override fun onHandleMenuClosed() {
animateCaptionHandleAlpha(startValue = 1f, endValue = 0f)
}
private fun getCaptionHandleBarColor(taskInfo: RunningTaskInfo): Int {
return if (shouldUseLightCaptionColors(taskInfo)) {
context.getColor(R.color.desktop_mode_caption_handle_bar_light)
......@@ -42,4 +56,14 @@ internal class DesktopModeFocusedWindowDecorationViewHolder(
context.getColor(R.color.desktop_mode_caption_handle_bar_dark)
}
}
/** Animate appearance/disappearance of caption handle as the handle menu is animated. */
private fun animateCaptionHandleAlpha(startValue: Float, endValue: Float) {
val animator =
ObjectAnimator.ofFloat(captionHandle, View.ALPHA, startValue, endValue).apply {
duration = CAPTION_HANDLE_ANIMATION_DURATION
interpolator = Interpolators.FAST_OUT_SLOW_IN
}
animator.start()
}
}
......@@ -35,4 +35,10 @@ internal abstract class DesktopModeWindowDecorationViewHolder(rootView: View) {
}
} ?: false
}
/** Callback when the handle menu is opened. */
abstract fun onHandleMenuOpened()
/** Callback when the handle menu is closed. */
abstract fun onHandleMenuClosed()
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment