Added spring animations for tucking/untucking Floating Action Button
This change builds off of modifications from ag/24314702 to better suit animated tucking/untucking.
MenuViewLayer applies a clipBounds covering most of the screen excluding system/navigation bars
once the FAB begins a tucking animation. It does not remove the clip bounds until an untuck animation concludes.
Flag: ACONFIG com.android.systemui.Flags.floating_menu_animated_tuck ENABLED
Bug: 297556899
Test: adb sync system_ext && adb shell stop && adb shell start
Change-Id: Ic18e39486e24cd9080620174f9a3ecb298ebc515
diff --git a/packages/SystemUI/aconfig/accessibility.aconfig b/packages/SystemUI/aconfig/accessibility.aconfig
index 08ecf09b..25ac486 100644
--- a/packages/SystemUI/aconfig/accessibility.aconfig
+++ b/packages/SystemUI/aconfig/accessibility.aconfig
@@ -3,10 +3,10 @@
# NOTE: Keep alphabetized to help limit merge conflicts from multiple simultaneous editors.
flag {
- name: "floating_menu_overlaps_nav_bars_flag"
+ name: "floating_menu_animated_tuck"
namespace: "accessibility"
- description: "Adjusts bounds to allow the floating menu to render on top of navigation bars."
- bug: "283768342"
+ description: "Sets up animations for tucking/untucking and adjusts clipbounds."
+ bug: "24592044"
}
flag {
@@ -14,4 +14,11 @@
namespace: "accessibility"
description: "Adds an animation for when the FAB is displaced by an IME becoming visible."
bug: "281150010"
+}
+
+flag {
+ name: "floating_menu_overlaps_nav_bars_flag"
+ namespace: "accessibility"
+ description: "Adjusts bounds to allow the floating menu to render on top of navigation bars."
+ bug: "283768342"
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
index cd8bef1..ceddee8 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
@@ -341,7 +341,16 @@
void moveToEdgeAndHide() {
mMenuView.updateMenuMoveToTucked(/* isMoveToTucked= */ true);
final PointF position = mMenuView.getMenuPosition();
- moveToPosition(getTuckedMenuPosition());
+ final PointF tuckedPosition = getTuckedMenuPosition();
+ if (Flags.floatingMenuAnimatedTuck()) {
+ flingThenSpringMenuWith(DynamicAnimation.TRANSLATION_X,
+ Math.signum(tuckedPosition.x - position.x) * ESCAPE_VELOCITY,
+ FLING_FRICTION_SCALAR,
+ createDefaultSpringForce(),
+ tuckedPosition.x);
+ } else {
+ moveToPosition(tuckedPosition);
+ }
// Keep the touch region let users could click extra space to pop up the menu view
// from the screen edge
@@ -353,7 +362,24 @@
void moveOutEdgeAndShow() {
mMenuView.updateMenuMoveToTucked(/* isMoveToTucked= */ false);
- mMenuView.onPositionChanged();
+ if (Flags.floatingMenuAnimatedTuck()) {
+ PointF position = mMenuView.getMenuPosition();
+ springMenuWith(DynamicAnimation.TRANSLATION_X,
+ createDefaultSpringForce(),
+ 0,
+ position.x,
+ true
+ );
+ springMenuWith(DynamicAnimation.TRANSLATION_Y,
+ createDefaultSpringForce(),
+ 0,
+ position.y,
+ true
+ );
+ } else {
+ mMenuView.onPositionChanged();
+ }
+
mMenuView.onEdgeChangedIfNeeded();
}
@@ -489,6 +515,12 @@
return new Handler(requireNonNull(Looper.myLooper(), "looper must not be null"));
}
+ private static SpringForce createDefaultSpringForce() {
+ return new SpringForce()
+ .setStiffness(SPRING_STIFFNESS)
+ .setDampingRatio(SPRING_AFTER_FLING_DAMPING_RATIO);
+ }
+
static class MenuPositionProperty
extends FloatPropertyCompat<MenuView> {
private final DynamicAnimation.ViewProperty mProperty;
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
index ea5a56c..92c7259 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
@@ -71,6 +71,7 @@
private final MenuAnimationController mMenuAnimationController;
private OnTargetFeaturesChangeListener mFeaturesChangeListener;
+ private OnMoveToTuckedListener mMoveToTuckedListener;
MenuView(Context context, MenuViewModel menuViewModel, MenuViewAppearance menuViewAppearance) {
super(context);
@@ -138,6 +139,10 @@
mFeaturesChangeListener = listener;
}
+ void setMoveToTuckedListener(OnMoveToTuckedListener listener) {
+ mMoveToTuckedListener = listener;
+ }
+
void addOnItemTouchListenerToList(RecyclerView.OnItemTouchListener listener) {
mTargetFeaturesView.addOnItemTouchListener(listener);
}
@@ -307,8 +312,11 @@
void updateMenuMoveToTucked(boolean isMoveToTucked) {
mIsMoveToTucked = isMoveToTucked;
mMenuViewModel.updateMenuMoveToTucked(isMoveToTucked);
+ if (mMoveToTuckedListener != null) {
+ mMoveToTuckedListener.onMoveToTuckedChanged(isMoveToTucked);
+ }
- if (Flags.floatingMenuOverlapsNavBarsFlag()) {
+ if (Flags.floatingMenuOverlapsNavBarsFlag() && !Flags.floatingMenuAnimatedTuck()) {
if (isMoveToTucked) {
final float halfWidth = getMenuWidth() / 2.0f;
final boolean isOnLeftSide = mMenuAnimationController.isOnLeftSide();
@@ -428,4 +436,11 @@
*/
void onChange(List<AccessibilityTarget> newTargetFeatures);
}
+
+ /**
+ * Interface containing a callback for when MoveToTucked changes.
+ */
+ interface OnMoveToTuckedListener {
+ void onMoveToTuckedChanged(boolean moveToTucked);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java
index 89ce065..4865fce 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java
@@ -281,7 +281,7 @@
: new float[]{radius, radius, 0.0f, 0.0f, 0.0f, 0.0f, radius, radius};
}
- private Rect getWindowAvailableBounds() {
+ public Rect getWindowAvailableBounds() {
final WindowMetrics windowMetrics = mWindowManager.getCurrentWindowMetrics();
final WindowInsets windowInsets = windowMetrics.getWindowInsets();
final Insets insets = windowInsets.getInsetsIgnoringVisibility(
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
index fbca022..ff3a9e3 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
@@ -79,7 +79,8 @@
*/
@SuppressLint("ViewConstructor")
class MenuViewLayer extends FrameLayout implements
- ViewTreeObserver.OnComputeInternalInsetsListener, View.OnClickListener, ComponentCallbacks {
+ ViewTreeObserver.OnComputeInternalInsetsListener, View.OnClickListener, ComponentCallbacks,
+ MenuView.OnMoveToTuckedListener {
private static final int SHOW_MESSAGE_DELAY_MS = 3000;
private final WindowManager mWindowManager;
@@ -211,6 +212,7 @@
mMenuListViewTouchHandler = new MenuListViewTouchHandler(mMenuAnimationController,
mDismissAnimationController);
mMenuView.addOnItemTouchListenerToList(mMenuListViewTouchHandler);
+ mMenuView.setMoveToTuckedListener(this);
mMessageView = new MenuMessageView(context);
@@ -232,6 +234,10 @@
addView(mMenuView, LayerIndex.MENU_VIEW);
addView(mDismissView, LayerIndex.DISMISS_VIEW);
addView(mMessageView, LayerIndex.MESSAGE_VIEW);
+
+ if (Flags.floatingMenuAnimatedTuck()) {
+ setClipChildren(true);
+ }
}
@Override
@@ -354,6 +360,24 @@
mShouldShowDockTooltip = !hasSeenTooltip;
}
+ public void onMoveToTuckedChanged(boolean moveToTuck) {
+ if (Flags.floatingMenuOverlapsNavBarsFlag()) {
+ if (moveToTuck) {
+ final Rect bounds = mMenuViewAppearance.getWindowAvailableBounds();
+ final int[] location = getLocationOnScreen();
+ bounds.offset(
+ location[0],
+ location[1]
+ );
+
+ setClipBounds(bounds);
+ }
+ // Instead of clearing clip bounds when moveToTuck is false,
+ // wait until the spring animation finishes.
+ }
+ // Function is a no-operation if flag is disabled.
+ }
+
private void onSpringAnimationsEndAction() {
if (mShouldShowDockTooltip) {
mEduTooltipView = Optional.of(new MenuEduTooltipView(mContext, mMenuViewAppearance));
@@ -364,6 +388,11 @@
mMenuAnimationController.startTuckedAnimationPreview();
}
+ if (Flags.floatingMenuAnimatedTuck()) {
+ if (!mMenuView.isMoveToTucked()) {
+ setClipBounds(null);
+ }
+ }
if (Flags.floatingMenuImeDisplacementAnimation()) {
mMenuView.onArrivalAtPosition();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
index 2e75480..834dccb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
@@ -26,6 +26,9 @@
import static org.mockito.Mockito.verifyZeroInteractions;
import android.graphics.PointF;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.View;
@@ -39,6 +42,7 @@
import androidx.dynamicanimation.animation.SpringForce;
import androidx.test.filters.SmallTest;
+import com.android.systemui.Flags;
import com.android.systemui.Prefs;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.util.settings.SecureSettings;
@@ -70,6 +74,10 @@
@Rule
public MockitoRule mockito = MockitoJUnit.rule();
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
@Mock
private AccessibilityManager mAccessibilityManager;
@@ -223,6 +231,24 @@
verifyZeroInteractions(onSpringAnimationsEndCallback);
}
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_FLOATING_MENU_ANIMATED_TUCK)
+ public void tuck_animates() {
+ mMenuAnimationController.cancelAnimations();
+ mMenuAnimationController.moveToEdgeAndHide();
+ assertThat(mMenuAnimationController.getAnimation(
+ DynamicAnimation.TRANSLATION_X).isRunning()).isTrue();
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_FLOATING_MENU_ANIMATED_TUCK)
+ public void untuck_animates() {
+ mMenuAnimationController.cancelAnimations();
+ mMenuAnimationController.moveOutEdgeAndShow();
+ assertThat(mMenuAnimationController.getAnimation(
+ DynamicAnimation.TRANSLATION_X).isRunning()).isTrue();
+ }
+
private void setupAndRunSpringAnimations() {
final float stiffness = 700f;
final float dampingRatio = 0.85f;