From 26ed4201737ffd3eb1e2811a38a9122dc7adbbfa Mon Sep 17 00:00:00 2001 From: mpodolian Date: Tue, 24 Sep 2024 11:49:22 -0700 Subject: [PATCH] Add teleport animation to the navigation bar. Whenever the location of the bubble bar is changed, the navigation bar moves with a "teleport" animation to the opposite side. Bug: 346381754 Flag: com.android.wm.shell.enable_bubble_bar_in_persistent_task_bar Test: manual. Drag bubble bar from one location to another observe that navigation is animated to the opposite side. Video: http://recall/-/gx8ASgewUeUS3QYohfrd1J/g3zFtGYWFpCsBTkoCAhBUH Change-Id: I6e32bb7ff7be56a0b616fec8485cc6a97d7ac872 --- .../taskbar/BarsLocationAnimatorHelper.kt | 178 ++++++++++++++++++ .../taskbar/NavbarButtonsViewController.java | 41 ++-- .../taskbar/TaskbarViewController.java | 3 +- .../taskbar/bubbles/BubbleBarView.java | 111 +++-------- 4 files changed, 233 insertions(+), 100 deletions(-) create mode 100644 quickstep/src/com/android/launcher3/taskbar/BarsLocationAnimatorHelper.kt diff --git a/quickstep/src/com/android/launcher3/taskbar/BarsLocationAnimatorHelper.kt b/quickstep/src/com/android/launcher3/taskbar/BarsLocationAnimatorHelper.kt new file mode 100644 index 0000000000..b8060e1792 --- /dev/null +++ b/quickstep/src/com/android/launcher3/taskbar/BarsLocationAnimatorHelper.kt @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2024 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.launcher3.taskbar + +import android.animation.Animator +import android.animation.AnimatorSet +import android.animation.ObjectAnimator +import android.animation.ValueAnimator +import android.view.View +import androidx.dynamicanimation.animation.SpringForce +import com.android.app.animation.Interpolators +import com.android.launcher3.LauncherAnimUtils +import com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X +import com.android.launcher3.anim.SpringAnimationBuilder +import com.android.wm.shell.shared.bubbles.BubbleBarLocation + +/** Animator helper that creates bars animators. */ +object BarsLocationAnimatorHelper { + + private const val FADE_OUT_ANIM_ALPHA_DURATION_MS: Long = 50L + private const val FADE_OUT_ANIM_ALPHA_DELAY_MS: Long = 50L + private const val FADE_OUT_ANIM_POSITION_DURATION_MS: Long = 100L + private const val FADE_IN_ANIM_ALPHA_DURATION_MS: Long = 100L + + // Use STIFFNESS_MEDIUMLOW which is not defined in the API constants + private const val FADE_IN_ANIM_POSITION_SPRING_STIFFNESS: Float = 400f + + // During fade out animation we shift the bubble bar 1/80th of the screen width + private const val FADE_OUT_ANIM_POSITION_SHIFT: Float = 1 / 80f + + // During fade in animation we shift the bubble bar 1/60th of the screen width + private const val FADE_IN_ANIM_POSITION_SHIFT: Float = 1 / 60f + + private val View.screenWidth: Int + get() = resources.displayMetrics.widthPixels + + private val View.outShift: Float + get() = screenWidth * FADE_OUT_ANIM_POSITION_SHIFT + + private val View.inShiftX: Float + get() = screenWidth * FADE_IN_ANIM_POSITION_SHIFT + + /** + * Creates out animation for targetView that animates it finalTx and plays targetViewAlphaAnim + * to its final value. + */ + private fun createLocationOutAnimator( + finalTx: Float, + targetViewAlphaAnim: ObjectAnimator, + targetView: View, + ): Animator { + val positionAnim = + ObjectAnimator.ofFloat(targetView, VIEW_TRANSLATE_X, finalTx) + .setDuration(FADE_OUT_ANIM_POSITION_DURATION_MS) + positionAnim.interpolator = Interpolators.EMPHASIZED_ACCELERATE + + targetViewAlphaAnim.setDuration(FADE_OUT_ANIM_ALPHA_DURATION_MS) + targetViewAlphaAnim.startDelay = FADE_OUT_ANIM_ALPHA_DELAY_MS + + val animatorSet = AnimatorSet() + animatorSet.playTogether(positionAnim, targetViewAlphaAnim) + return animatorSet + } + + /** + * Creates in animation for targetView that animates it from startTx to finalTx and plays + * targetViewAlphaAnim to its final value. + */ + private fun createLocationInAnimator( + startTx: Float, + finalTx: Float, + targetViewAlphaAnim: ObjectAnimator, + targetView: View, + ): Animator { + targetViewAlphaAnim.setDuration(FADE_IN_ANIM_ALPHA_DURATION_MS) + val positionAnim: ValueAnimator = + SpringAnimationBuilder(targetView.context) + .setStartValue(startTx) + .setEndValue(finalTx) + .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY) + .setStiffness(FADE_IN_ANIM_POSITION_SPRING_STIFFNESS) + .build(targetView, VIEW_TRANSLATE_X) + val animatorSet = AnimatorSet() + animatorSet.playTogether(positionAnim, targetViewAlphaAnim) + return animatorSet + } + + /** Creates an animator for the bubble bar view in part. */ + @JvmStatic + fun getBubbleBarLocationInAnimator( + newLocation: BubbleBarLocation, + currentLocation: BubbleBarLocation, + distanceFromOtherSide: Float, + targetViewAlphaAnim: ObjectAnimator, + bubbleBarView: View, + ): Animator { + val shift: Float = bubbleBarView.outShift + + val onLeft = newLocation.isOnLeft(bubbleBarView.isLayoutRtl) + val startTx: Float + val finalTx = + if (newLocation == currentLocation) { + // Animated location matches layout location. + 0f + } else { + // We are animating in to a transient location, need to move the bar + // accordingly. + distanceFromOtherSide * (if (onLeft) -1 else 1) + } + startTx = + if (onLeft) { + // Bar will be shown on the left side. Start point is shifted right. + finalTx + shift + } else { + // Bar will be shown on the right side. Start point is shifted left. + finalTx - shift + } + return createLocationInAnimator(startTx, finalTx, targetViewAlphaAnim, bubbleBarView) + } + + /** Creates an animator for the bubble bar view out part. */ + @JvmStatic + fun getBubbleBarLocationOutAnimator( + bubbleBarView: View, + bubbleBarLocation: BubbleBarLocation, + targetViewAlphaAnim: ObjectAnimator, + ): Animator { + val onLeft = bubbleBarLocation.isOnLeft(bubbleBarView.isLayoutRtl) + val shift = bubbleBarView.outShift + val finalTx = bubbleBarView.translationX + (if (onLeft) -shift else shift) + return this.createLocationOutAnimator(finalTx, targetViewAlphaAnim, bubbleBarView) + } + + /** Creates a teleport animator for the navigation buttons view. */ + @JvmStatic + fun getTeleportAnimatorForNavButtons( + location: BubbleBarLocation, + navButtonsView: View, + navBarTargetTranslationX: Float, + ): Animator { + val outShift: Float = navButtonsView.outShift + val isNavBarOnRight: Boolean = location.isOnLeft(navButtonsView.isLayoutRtl) + val finalOutTx = + navButtonsView.translationX + (if (isNavBarOnRight) outShift else -outShift) + val fadeout: Animator = + createLocationOutAnimator( + finalOutTx, + ObjectAnimator.ofFloat(navButtonsView, LauncherAnimUtils.VIEW_ALPHA, 0f), + navButtonsView, + ) + val inShift: Float = navButtonsView.inShiftX + val inStartX = navBarTargetTranslationX + (if (isNavBarOnRight) -inShift else inShift) + val fadeIn: Animator = + createLocationInAnimator( + inStartX, + navBarTargetTranslationX, + ObjectAnimator.ofFloat(navButtonsView, LauncherAnimUtils.VIEW_ALPHA, 1f), + navButtonsView, + ) + val teleportAnimator = AnimatorSet() + teleportAnimator.play(fadeout).before(fadeIn) + return teleportAnimator + } +} diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java index a4a93f7d56..895535e5c7 100644 --- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java +++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java @@ -23,6 +23,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL; import static com.android.launcher3.LauncherAnimUtils.ROTATION_DRAWABLE_PERCENT; import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X; import static com.android.launcher3.Utilities.getDescendantCoordRelativeToAncestor; +import static com.android.launcher3.anim.AnimatorListeners.forEndCallback; import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UNIFICATION; import static com.android.launcher3.taskbar.LauncherTaskbarUIController.SYSUI_SURFACE_PROGRESS_INDEX; import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_A11Y; @@ -48,7 +49,9 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_Q import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SHORTCUT_HELPER_SHOWING; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING; +import static com.android.wm.shell.Flags.enableBubbleBarInPersistentTaskBar; +import android.animation.Animator; import android.animation.ArgbEvaluator; import android.animation.ObjectAnimator; import android.annotation.DrawableRes; @@ -175,6 +178,9 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT /** Color to use for navigation bar buttons, if they are on on a Taskbar surface background. */ private final int mOnBackgroundIconColor; + private @Nullable Animator mNavBarLocationAnimator; + private @Nullable BubbleBarLocation mBubbleBarTargetLocation; + private final AnimatedFloat mTaskbarNavButtonTranslationY = new AnimatedFloat( this::updateNavButtonTranslationY); private final AnimatedFloat mTaskbarNavButtonTranslationYForInAppDisplay = new AnimatedFloat( @@ -201,8 +207,6 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT private final Rect mFloatingRotationButtonBounds = new Rect(); - private @Nullable BubbleBarLocation mBubbleBarLocation; - // Initialized in init. private TaskbarControllers mControllers; private boolean mIsImeRenderingNavButtons; @@ -1176,16 +1180,30 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT /** Adjusts navigation buttons layout accordingly to the bubble bar position. */ @Override public void onBubbleBarLocationUpdated(BubbleBarLocation location) { - mBubbleBarLocation = location; + cancelExistingNavBarAnimation(); + mBubbleBarTargetLocation = location; mNavButtonContainer.setTranslationX(getNavBarTranslationX(location)); + mNavButtonContainer.setAlpha(1); } /** Animates navigation buttons accordingly to the bubble bar position. */ @Override public void onBubbleBarLocationAnimated(BubbleBarLocation location) { - // TODO(b/346381754) add the teleport animation similarly to the bubble bar - mBubbleBarLocation = location; - mNavButtonContainer.setTranslationX(getNavBarTranslationX(location)); + cancelExistingNavBarAnimation(); + mBubbleBarTargetLocation = location; + int finalX = getNavBarTranslationX(location); + Animator teleportAnimator = BarsLocationAnimatorHelper + .getTeleportAnimatorForNavButtons(location, mNavButtonContainer, finalX); + teleportAnimator.addListener(forEndCallback(() -> mNavBarLocationAnimator = null)); + mNavBarLocationAnimator = teleportAnimator; + mNavBarLocationAnimator.start(); + } + + private void cancelExistingNavBarAnimation() { + if (mNavBarLocationAnimator != null) { + mNavBarLocationAnimator.cancel(); + mNavBarLocationAnimator = null; + } } private int getNavBarTranslationX(BubbleBarLocation location) { @@ -1222,15 +1240,16 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT } /** Adjusts the navigation buttons layout position according to the bubble bar location. */ - public void onTaskbarLayoutChange() { - if (com.android.wm.shell.Flags.enableBubbleBarInPersistentTaskBar() + public void onTaskbarLayoutChanged() { + if (mControllers.taskbarViewController.getIconLayoutBounds().isEmpty()) return; + if (enableBubbleBarInPersistentTaskBar() && mControllers.bubbleControllers.isPresent()) { - if (mBubbleBarLocation == null) { + if (mBubbleBarTargetLocation == null) { // only set bubble bar location if it was not set before, e.g. at device boot - mBubbleBarLocation = mControllers.bubbleControllers.get() + mBubbleBarTargetLocation = mControllers.bubbleControllers.get() .bubbleBarViewController.getBubbleBarLocation(); } - onBubbleBarLocationUpdated(mBubbleBarLocation); + onBubbleBarLocationUpdated(mBubbleBarTargetLocation); } } diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java index 0fa9e60d06..b495e7d80e 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java @@ -84,7 +84,6 @@ import com.android.wm.shell.shared.bubbles.BubbleBarLocation; import java.io.PrintWriter; import java.util.Collections; import java.util.HashSet; -import java.util.List; import java.util.Set; import java.util.function.Predicate; @@ -160,7 +159,7 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar private final View.OnLayoutChangeListener mTaskbarViewLayoutChangeListener = (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { updateTaskbarIconTranslationXForPinning(); - mControllers.navbarButtonsViewController.onTaskbarLayoutChange(); + mControllers.navbarButtonsViewController.onTaskbarLayoutChanged(); }; // Animation to align icons with Launcher, created lazily. This allows the controller to be diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java index d454fd7b8e..7fed38113b 100644 --- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java +++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java @@ -15,13 +15,9 @@ */ package com.android.launcher3.taskbar.bubbles; -import static com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE; import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA; -import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X; import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.annotation.NonNull; @@ -42,10 +38,9 @@ import android.view.ViewGroup; import android.view.accessibility.AccessibilityNodeInfo; import android.widget.FrameLayout; -import androidx.dynamicanimation.animation.SpringForce; - import com.android.launcher3.R; -import com.android.launcher3.anim.SpringAnimationBuilder; +import com.android.launcher3.anim.AnimatorListeners; +import com.android.launcher3.taskbar.BarsLocationAnimatorHelper; import com.android.launcher3.taskbar.bubbles.animation.BubbleAnimator; import com.android.launcher3.util.DisplayController; import com.android.wm.shell.shared.bubbles.BubbleBarLocation; @@ -83,8 +78,9 @@ import java.util.function.Consumer; */ public class BubbleBarView extends FrameLayout { + public static final long FADE_OUT_ANIM_POSITION_DURATION_MS = 100L; + public static final long FADE_IN_ANIM_ALPHA_DURATION_MS = 100L; private static final String TAG = "BubbleBarView"; - // TODO: (b/273594744) calculate the amount of space we have and base the max on that // if it's smaller than 5. private static final int MAX_BUBBLES = 5; @@ -93,18 +89,6 @@ public class BubbleBarView extends FrameLayout { private static final int WIDTH_ANIMATION_DURATION_MS = 200; private static final int SCALE_ANIMATION_DURATION_MS = 200; - private static final long FADE_OUT_ANIM_ALPHA_DURATION_MS = 50L; - private static final long FADE_OUT_ANIM_ALPHA_DELAY_MS = 50L; - public static final long FADE_OUT_ANIM_POSITION_DURATION_MS = 100L; - // During fade out animation we shift the bubble bar 1/80th of the screen width - private static final float FADE_OUT_ANIM_POSITION_SHIFT = 1 / 80f; - - public static final long FADE_IN_ANIM_ALPHA_DURATION_MS = 100L; - // Use STIFFNESS_MEDIUMLOW which is not defined in the API constants - private static final float FADE_IN_ANIM_POSITION_SPRING_STIFFNESS = 400f; - // During fade in animation we shift the bubble bar 1/60th of the screen width - private static final float FADE_IN_ANIM_POSITION_SHIFT = 1 / 60f; - /** * Custom property to set alpha value for the bar view while a bubble is being dragged. * Skips applying alpha to the dragged bubble. @@ -578,77 +562,30 @@ public class BubbleBarView extends FrameLayout { // First animator hides the bar. // After it completes, bubble positions in the bar and arrow position is updated. // Second animator is started to show the bar. - mBubbleBarLocationAnimator = getLocationUpdateFadeOutAnimator(bubbleBarLocation); - mBubbleBarLocationAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - updateBubblesLayoutProperties(bubbleBarLocation); - mBubbleBarBackground.setAnchorLeft(bubbleBarLocation.isOnLeft(isLayoutRtl())); + ObjectAnimator alphaOutAnim = ObjectAnimator.ofFloat( + this, getLocationAnimAlphaProperty(), 0f); + mBubbleBarLocationAnimator = BarsLocationAnimatorHelper.getBubbleBarLocationOutAnimator( + this, + bubbleBarLocation, + alphaOutAnim); + mBubbleBarLocationAnimator.addListener(AnimatorListeners.forEndCallback(() -> { + updateBubblesLayoutProperties(bubbleBarLocation); + mBubbleBarBackground.setAnchorLeft(bubbleBarLocation.isOnLeft(isLayoutRtl())); + ObjectAnimator alphaInAnim = ObjectAnimator.ofFloat(BubbleBarView.this, + getLocationAnimAlphaProperty(), 1f); - // Animate it in - mBubbleBarLocationAnimator = getLocationUpdateFadeInAnimator(bubbleBarLocation); - mBubbleBarLocationAnimator.start(); - } - }); + // Animate it in + mBubbleBarLocationAnimator = BarsLocationAnimatorHelper.getBubbleBarLocationInAnimator( + bubbleBarLocation, + mBubbleBarLocation, + getDistanceFromOtherSide(), + alphaInAnim, + BubbleBarView.this); + mBubbleBarLocationAnimator.start(); + })); mBubbleBarLocationAnimator.start(); } - private Animator getLocationUpdateFadeOutAnimator(BubbleBarLocation newLocation) { - final float shift = - getResources().getDisplayMetrics().widthPixels * FADE_OUT_ANIM_POSITION_SHIFT; - final boolean onLeft = newLocation.isOnLeft(isLayoutRtl()); - final float tx = getTranslationX() + (onLeft ? -shift : shift); - - ObjectAnimator positionAnim = ObjectAnimator.ofFloat(this, VIEW_TRANSLATE_X, tx) - .setDuration(FADE_OUT_ANIM_POSITION_DURATION_MS); - positionAnim.setInterpolator(EMPHASIZED_ACCELERATE); - - ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(this, getLocationAnimAlphaProperty(), 0f) - .setDuration(FADE_OUT_ANIM_ALPHA_DURATION_MS); - alphaAnim.setStartDelay(FADE_OUT_ANIM_ALPHA_DELAY_MS); - - AnimatorSet animatorSet = new AnimatorSet(); - animatorSet.playTogether(positionAnim, alphaAnim); - return animatorSet; - } - - private Animator getLocationUpdateFadeInAnimator(BubbleBarLocation newLocation) { - final float shift = - getResources().getDisplayMetrics().widthPixels * FADE_IN_ANIM_POSITION_SHIFT; - - final boolean onLeft = newLocation.isOnLeft(isLayoutRtl()); - final float startTx; - final float finalTx; - if (newLocation == mBubbleBarLocation) { - // Animated location matches layout location. - finalTx = 0; - } else { - // We are animating in to a transient location, need to move the bar accordingly. - finalTx = getDistanceFromOtherSide() * (onLeft ? -1 : 1); - } - if (onLeft) { - // Bar will be shown on the left side. Start point is shifted right. - startTx = finalTx + shift; - } else { - // Bar will be shown on the right side. Start point is shifted left. - startTx = finalTx - shift; - } - - ValueAnimator positionAnim = new SpringAnimationBuilder(getContext()) - .setStartValue(startTx) - .setEndValue(finalTx) - .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY) - .setStiffness(FADE_IN_ANIM_POSITION_SPRING_STIFFNESS) - .build(this, VIEW_TRANSLATE_X); - - ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(this, getLocationAnimAlphaProperty(), 1f) - .setDuration(FADE_IN_ANIM_ALPHA_DURATION_MS); - - AnimatorSet animatorSet = new AnimatorSet(); - animatorSet.playTogether(positionAnim, alphaAnim); - return animatorSet; - } - /** * Get property that can be used to animate the alpha value for the bar. * When a bubble is being dragged, uses {@link #BUBBLE_DRAG_ALPHA}.