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

Commit c88b5c67 authored by Riley Jones's avatar Riley Jones Committed by Android (Google) Code Review
Browse files

Merge "Implement smooth FAB animations for displacement due to IME visibility changes" into main

parents 3871106c 84156fc1
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -485,6 +485,8 @@ android_library {
        "motion_tool_lib",
        "androidx.core_core-animation-testing-nodeps",
        "androidx.compose.ui_ui",
        "flag-junit",
        "platform-test-annotations",
    ],
}

+7 −0
Original line number Diff line number Diff line
@@ -8,3 +8,10 @@ flag {
    description: "Adjusts bounds to allow the floating menu to render on top of navigation bars."
    bug: "283768342"
}

flag {
    name: "floating_menu_ime_displacement_animation"
    namespace: "accessibility"
    description: "Adds an animation for when the FAB is displaced by an IME becoming visible."
    bug: "281150010"
}
 No newline at end of file
+76 −24
Original line number Diff line number Diff line
@@ -38,6 +38,7 @@ import androidx.recyclerview.widget.RecyclerView;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
import com.android.systemui.Flags;

import java.util.HashMap;

@@ -91,17 +92,49 @@ class MenuAnimationController {
    }

    void moveToPosition(PointF position) {
        moveToPositionX(position.x);
        moveToPositionY(position.y);
        moveToPosition(position, /* animateMovement = */ false);
    }

    /* Moves position without updating underlying percentage position. Can be animated. */
    void moveToPosition(PointF position, boolean animateMovement) {
        if (Flags.floatingMenuImeDisplacementAnimation()) {
            moveToPositionX(position.x, animateMovement);
            moveToPositionY(position.y, animateMovement);
        } else {
            moveToPositionX(position.x, /* animateMovement = */ false);
            moveToPositionY(position.y, /* animateMovement = */ false);
        }
    }

    void moveToPositionX(float positionX) {
        moveToPositionX(positionX, /* animateMovement = */ false);
    }

    void moveToPositionX(float positionX, boolean animateMovement) {
        if (animateMovement && Flags.floatingMenuImeDisplacementAnimation()) {
            springMenuWith(DynamicAnimation.TRANSLATION_X,
                    createSpringForce(),
                    /* velocity = */ 0,
                    positionX, /* writeToPosition = */ false);
        } else {
            DynamicAnimation.TRANSLATION_X.setValue(mMenuView, positionX);
        }
    }

    void moveToPositionY(float positionY) {
        moveToPositionY(positionY, /* animateMovement = */ false);
    }

    private void moveToPositionY(float positionY) {
    void moveToPositionY(float positionY, boolean animateMovement) {
        if (animateMovement && Flags.floatingMenuImeDisplacementAnimation()) {
            springMenuWith(DynamicAnimation.TRANSLATION_Y,
                    createSpringForce(),
                    /* velocity = */ 0,
                    positionY, /* writeToPosition = */ false);
        } else {
            DynamicAnimation.TRANSLATION_Y.setValue(mMenuView, positionY);
        }
    }

    void moveToPositionYIfNeeded(float positionY) {
        // If the list view was out of screen bounds, it would allow users to nest scroll inside
@@ -151,7 +184,7 @@ class MenuAnimationController {
    void moveAndPersistPosition(PointF position) {
        moveToPosition(position);
        mMenuView.onBoundsInParentChanged((int) position.x, (int) position.y);
        constrainPositionAndUpdate(position);
        constrainPositionAndUpdate(position, /* writeToPosition = */ true);
    }

    void removeMenu() {
@@ -180,17 +213,13 @@ class MenuAnimationController {
        flingThenSpringMenuWith(DynamicAnimation.TRANSLATION_X,
                startXVelocity,
                FLING_FRICTION_SCALAR,
                new SpringForce()
                        .setStiffness(SPRING_STIFFNESS)
                        .setDampingRatio(SPRING_AFTER_FLING_DAMPING_RATIO),
                createSpringForce(),
                finalPositionX);

        flingThenSpringMenuWith(DynamicAnimation.TRANSLATION_Y,
                velocityY,
                FLING_FRICTION_SCALAR,
                new SpringForce()
                        .setStiffness(SPRING_STIFFNESS)
                        .setDampingRatio(SPRING_AFTER_FLING_DAMPING_RATIO),
                createSpringForce(),
                /* finalPosition= */ null);
    }

@@ -226,7 +255,8 @@ class MenuAnimationController {
                    final float endPosition = finalPosition != null
                            ? finalPosition
                            : Math.max(min, Math.min(max, endValue));
                    springMenuWith(property, spring, endVelocity, endPosition);
                    springMenuWith(property, spring, endVelocity, endPosition,
                            /* writeToPosition = */ true);
                });

        cancelAnimation(property);
@@ -242,7 +272,7 @@ class MenuAnimationController {

    @VisibleForTesting
    void springMenuWith(DynamicAnimation.ViewProperty property, SpringForce spring,
            float velocity, float finalPosition) {
            float velocity, float finalPosition, boolean writeToPosition) {
        final MenuPositionProperty menuPositionProperty = new MenuPositionProperty(property);
        final SpringAnimation springAnimation =
                new SpringAnimation(mMenuView, menuPositionProperty)
@@ -257,7 +287,7 @@ class MenuAnimationController {
                                            DynamicAnimation::isRunning);
                            if (!areAnimationsRunning) {
                                onSpringAnimationsEnd(new PointF(mMenuView.getTranslationX(),
                                        mMenuView.getTranslationY()));
                                        mMenuView.getTranslationY()), writeToPosition);
                            }
                        })
                        .setStartVelocity(velocity);
@@ -281,7 +311,8 @@ class MenuAnimationController {
        if (currentXTranslation < draggableBounds.left
                || currentXTranslation > draggableBounds.right) {
            constrainPositionAndUpdate(
                    new PointF(mMenuView.getTranslationX(), mMenuView.getTranslationY()));
                    new PointF(mMenuView.getTranslationX(), mMenuView.getTranslationY()),
                    /* writeToPosition = */ true);
            moveToEdgeAndHide();
            return true;
        }
@@ -298,15 +329,19 @@ class MenuAnimationController {
        return mMenuView.isMoveToTucked();
    }

    void moveToEdgeAndHide() {
        mMenuView.updateMenuMoveToTucked(/* isMoveToTucked= */ true);

    PointF getTuckedMenuPosition() {
        final PointF position = mMenuView.getMenuPosition();
        final float menuHalfWidth = mMenuView.getMenuWidth() / 2.0f;
        final float endX = isOnLeftSide()
                ? position.x - menuHalfWidth
                : position.x + menuHalfWidth;
        moveToPosition(new PointF(endX, position.y));
        return new PointF(endX, position.y);
    }

    void moveToEdgeAndHide() {
        mMenuView.updateMenuMoveToTucked(/* isMoveToTucked= */ true);
        final PointF position = mMenuView.getMenuPosition();
        moveToPosition(getTuckedMenuPosition());

        // Keep the touch region let users could click extra space to pop up the menu view
        // from the screen edge
@@ -335,6 +370,11 @@ class MenuAnimationController {
        mPositionAnimations.get(property).cancel();
    }

    @VisibleForTesting
    DynamicAnimation getAnimation(DynamicAnimation.ViewProperty property) {
        return mPositionAnimations.getOrDefault(property, null);
    }

    void onDraggingStart() {
        mMenuView.onDraggingStart();
    }
@@ -361,9 +401,9 @@ class MenuAnimationController {
                .start();
    }

    private void onSpringAnimationsEnd(PointF position) {
    private void onSpringAnimationsEnd(PointF position, boolean writeToPosition) {
        mMenuView.onBoundsInParentChanged((int) position.x, (int) position.y);
        constrainPositionAndUpdate(position);
        constrainPositionAndUpdate(position, writeToPosition);

        fadeOutIfEnabled();

@@ -372,7 +412,7 @@ class MenuAnimationController {
        }
    }

    private void constrainPositionAndUpdate(PointF position) {
    private void constrainPositionAndUpdate(PointF position, boolean writeToPosition) {
        final Rect draggableBounds = mMenuView.getMenuDraggableBoundsExcludeIme();
        // Have the space gap margin between the top bound and the menu view, so actually the
        // position y range needs to cut the margin.
@@ -384,8 +424,13 @@ class MenuAnimationController {
        final float percentageY = position.y < 0 || draggableBounds.height() == 0
                ? MIN_PERCENT
                : Math.min(MAX_PERCENT, position.y / draggableBounds.height());

        if (Flags.floatingMenuImeDisplacementAnimation() && !writeToPosition) {
            mMenuView.onEdgeChangedIfNeeded();
        } else {
            mMenuView.persistPositionAndUpdateEdge(new Position(percentageX, percentageY));
        }
    }

    void updateOpacityWith(boolean isFadeEffectEnabled, float newOpacityValue) {
        mIsFadeEffectEnabled = isFadeEffectEnabled;
@@ -463,4 +508,11 @@ class MenuAnimationController {
            mProperty.setValue(menuView, value);
        }
    }

    @VisibleForTesting
    static SpringForce createSpringForce() {
        return new SpringForce()
                .setStiffness(SPRING_STIFFNESS)
                .setDampingRatio(SPRING_AFTER_FLING_DAMPING_RATIO);
    }
}
+25 −3
Original line number Diff line number Diff line
@@ -54,7 +54,6 @@ class MenuView extends FrameLayout implements
    private final List<AccessibilityTarget> mTargetFeatures = new ArrayList<>();
    private final AccessibilityTargetAdapter mAdapter;
    private final MenuViewModel mMenuViewModel;
    private final MenuAnimationController mMenuAnimationController;
    private final Rect mBoundsInParent = new Rect();
    private final RecyclerView mTargetFeaturesView;
    private final ViewTreeObserver.OnDrawListener mSystemGestureExcludeUpdater =
@@ -70,6 +69,7 @@ class MenuView extends FrameLayout implements

    private boolean mIsMoveToTucked;

    private final MenuAnimationController mMenuAnimationController;
    private OnTargetFeaturesChangeListener mFeaturesChangeListener;

    MenuView(Context context, MenuViewModel menuViewModel, MenuViewAppearance menuViewAppearance) {
@@ -197,8 +197,30 @@ class MenuView extends FrameLayout implements
    }

    void onPositionChanged() {
        final PointF position = mMenuViewAppearance.getMenuPosition();
        onPositionChanged(/* animateMovement = */ false);
    }

    void onPositionChanged(boolean animateMovement) {
        final PointF position;
        if (isMoveToTucked()) {
            position = mMenuAnimationController.getTuckedMenuPosition();
        } else {
            position = getMenuPosition();
        }

        // We can skip animating if FAB is not visible
        if (Flags.floatingMenuImeDisplacementAnimation()
                && animateMovement && getVisibility() == VISIBLE) {
            mMenuAnimationController.moveToPosition(position, /* animateMovement = */ true);
            // onArrivalAtPosition() is called at the end of the animation.
        } else {
            mMenuAnimationController.moveToPosition(position);
            onArrivalAtPosition(); // no animation, so we call this immediately.
        }
    }

    void onArrivalAtPosition() {
        final PointF position = getMenuPosition();
        onBoundsInParentChanged((int) position.x, (int) position.y);

        if (isMoveToTucked()) {
+6 −1
Original line number Diff line number Diff line
@@ -59,6 +59,7 @@ import androidx.lifecycle.Observer;
import com.android.internal.accessibility.dialog.AccessibilityTarget;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
import com.android.systemui.Flags;
import com.android.systemui.res.R;
import com.android.systemui.util.settings.SecureSettings;
import com.android.wm.shell.bubbles.DismissViewUtils;
@@ -331,7 +332,7 @@ class MenuViewLayer extends FrameLayout implements
            mMenuViewAppearance.onImeVisibilityChanged(windowInsets.isVisible(ime()), imeTop);

            mMenuView.onEdgeChanged();
            mMenuView.onPositionChanged();
            mMenuView.onPositionChanged(/* animateMovement = */ true);

            mImeInsetsRect.set(imeInsetsRect);
        }
@@ -362,6 +363,10 @@ class MenuViewLayer extends FrameLayout implements

            mMenuAnimationController.startTuckedAnimationPreview();
        }

        if (Flags.floatingMenuImeDisplacementAnimation()) {
            mMenuView.onArrivalAtPosition();
        }
    }

    private CharSequence getMigrationMessage() {
Loading