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

Commit 4cda5ccc authored by Peter Liang's avatar Peter Liang
Browse files

Refactor the design and improve the animations of Accessibility Floating Menu(7/n).

Actions of this change:
1) Support the feature of hiding half the menu into the screen edge.

Bug: 227715451
Test: atest MenuTouchHandlerTest
Change-Id: I2dbf7c4a65e4cb0fdec723f381143ad83af1a272
parent 4f62018a
Loading
Loading
Loading
Loading
+67 −0
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package com.android.systemui.accessibility.floatingmenu;

import static android.util.MathUtils.constrain;

import static java.util.Objects.requireNonNull;

import android.animation.ValueAnimator;
@@ -57,6 +59,7 @@ class MenuAnimationController {
    private final MenuView mMenuView;
    private final ValueAnimator mFadeOutAnimator;
    private final Handler mHandler;
    private boolean mIsMovedToEdge;
    private boolean mIsFadeEffectEnabled;

    // Cache the animations state of {@link DynamicAnimation.TRANSLATION_X} and {@link
@@ -96,6 +99,12 @@ class MenuAnimationController {
        }
    }

    void moveAndPersistPosition(PointF position) {
        moveToPosition(position);
        mMenuView.onBoundsInParentChanged((int) position.x, (int) position.y);
        constrainPositionAndUpdate(position);
    }

    void flingMenuThenSpringToEdge(float x, float velocityX, float velocityY) {
        final boolean shouldMenuFlingLeft = isOnLeftSide()
                ? velocityX < ESCAPE_VELOCITY
@@ -190,10 +199,63 @@ class MenuAnimationController {
        springAnimation.animateToFinalPosition(finalPosition);
    }

    /**
     * Determines whether to hide the menu to the edge of the screen with the given current
     * translation x of the menu view. It should be used when receiving the action up touch event.
     *
     * @param currentXTranslation the current translation x of the menu view.
     * @return true if the menu would be hidden to the edge, otherwise false.
     */
    boolean maybeMoveToEdgeAndHide(float currentXTranslation) {
        final Rect draggableBounds = mMenuView.getMenuDraggableBounds();

        // If the translation x is zero, it should be at the left of the bound.
        if (currentXTranslation < draggableBounds.left
                || currentXTranslation > draggableBounds.right) {
            moveToEdgeAndHide();
            return true;
        }

        fadeOutIfEnabled();
        return false;
    }

    private boolean isOnLeftSide() {
        return mMenuView.getTranslationX() < mMenuView.getMenuDraggableBounds().centerX();
    }

    boolean isMovedToEdge() {
        return mIsMovedToEdge;
    }

    void moveToEdgeAndHide() {
        mIsMovedToEdge = true;

        final Rect draggableBounds = mMenuView.getMenuDraggableBounds();
        final float endY = constrain(mMenuView.getTranslationY(), draggableBounds.top,
                draggableBounds.bottom);
        final float menuHalfWidth = mMenuView.getWidth() / 2.0f;
        final float endX = isOnLeftSide()
                ? draggableBounds.left - menuHalfWidth
                : draggableBounds.right + menuHalfWidth;
        moveAndPersistPosition(new PointF(endX, endY));

        // Keep the touch region let users could click extra space to pop up the menu view
        // from the screen edge
        mMenuView.onBoundsInParentChanged(isOnLeftSide()
                ? draggableBounds.left
                : draggableBounds.right, (int) mMenuView.getTranslationY());

        fadeOutIfEnabled();
    }

    void moveOutEdgeAndShow() {
        mIsMovedToEdge = false;

        mMenuView.onPositionChanged();
        mMenuView.onEdgeChangedIfNeeded();
    }

    void cancelAnimations() {
        cancelAnimation(DynamicAnimation.TRANSLATION_X);
        cancelAnimation(DynamicAnimation.TRANSLATION_Y);
@@ -213,7 +275,12 @@ class MenuAnimationController {

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

        fadeOutIfEnabled();
    }

    private void constrainPositionAndUpdate(PointF position) {
        final Rect draggableBounds = mMenuView.getMenuDraggableBounds();
        // Have the space gap margin between the top bound and the menu view, so actually the
        // position y range needs to cut the margin.
+5 −3
Original line number Diff line number Diff line
@@ -79,9 +79,11 @@ class MenuListViewTouchHandler implements RecyclerView.OnItemTouchListener {
                    final float endX = mMenuTranslationDown.x + dx;
                    mIsDragging = false;

                    if (!mMenuAnimationController.maybeMoveToEdgeAndHide(endX)) {
                        mVelocityTracker.computeCurrentVelocity(VELOCITY_UNIT_SECONDS);
                        mMenuAnimationController.flingMenuThenSpringToEdge(endX,
                                mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity());
                    }

                    // Avoid triggering the listener of the item.
                    return true;
+28 −1
Original line number Diff line number Diff line
@@ -148,7 +148,7 @@ class MenuView extends FrameLayout implements
        onPositionChanged();
    }

    private void onPositionChanged() {
    void onPositionChanged() {
        final PointF position = mMenuViewAppearance.getMenuPosition();
        mMenuAnimationController.moveToPosition(position);
        onBoundsInParentChanged((int) position.x, (int) position.y);
@@ -204,6 +204,33 @@ class MenuView extends FrameLayout implements
        onEdgeChangedIfNeeded();
    }

    /**
     * Uses the touch events from the parent view to identify if users clicked the extra
     * space of the menu view. If yes, will use the percentage position and update the
     * translations of the menu view to meet the effect of moving out from the edge. It’s only
     * used when the menu view is hidden to the screen edge.
     *
     * @param x the current x of the touch event from the parent {@link MenuViewLayer} of the
     * {@link MenuView}.
     * @param y the current y of the touch event from the parent {@link MenuViewLayer} of the
     * {@link MenuView}.
     * @return true if consume the touch event, otherwise false.
     */
    boolean maybeMoveOutEdgeAndShow(int x, int y) {
        // Utilizes the touch region of the parent view to implement that users could tap extra
        // the space region to show the menu from the edge.
        if (!mMenuAnimationController.isMovedToEdge() || !mBoundsInParent.contains(x, y)) {
            return false;
        }

        mMenuAnimationController.fadeInNowIfEnabled();

        mMenuAnimationController.moveOutEdgeAndShow();

        mMenuAnimationController.fadeOutIfEnabled();
        return true;
    }

    void show() {
        mMenuViewModel.getPercentagePositionData().observeForever(mPercentagePositionObserver);
        mMenuViewModel.getFadeEffectInfoData().observeForever(mFadeEffectInfoObserver);
+10 −0
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package com.android.systemui.accessibility.floatingmenu;
import android.annotation.IntDef;
import android.annotation.SuppressLint;
import android.content.Context;
import android.view.MotionEvent;
import android.view.WindowManager;
import android.widget.FrameLayout;

@@ -53,6 +54,15 @@ class MenuViewLayer extends FrameLayout {
        addView(mMenuView, LayerIndex.MENU_VIEW);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        if (mMenuView.maybeMoveOutEdgeAndShow((int) event.getX(), (int) event.getY())) {
            return true;
        }

        return super.onInterceptTouchEvent(event);
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
+22 −0
Original line number Diff line number Diff line
@@ -130,6 +130,28 @@ public class MenuListViewTouchHandlerTest extends SysuiTestCase {
                anyFloat());
    }

    @Test
    public void dragMenuOutOfBoundsAndDrop_moveToLeftEdge_shouldMoveToEdgeAndHide() {
        final int offset = -100;
        final MotionEvent stubDownEvent =
                mMotionEventHelper.obtainMotionEvent(/* downTime= */ 0, /* eventTime= */ 1,
                        MotionEvent.ACTION_DOWN, mStubMenuView.getTranslationX(),
                        mStubMenuView.getTranslationY());
        final MotionEvent stubMoveEvent =
                mMotionEventHelper.obtainMotionEvent(/* downTime= */ 0, /* eventTime= */ 3,
                        MotionEvent.ACTION_MOVE, mStubMenuView.getTranslationX() + offset,
                        mStubMenuView.getTranslationY() + offset);
        final MotionEvent stubUpEvent =
                mMotionEventHelper.obtainMotionEvent(/* downTime= */ 0, /* eventTime= */ 5,
                        MotionEvent.ACTION_UP, mStubMenuView.getTranslationX() + offset,
                        mStubMenuView.getTranslationY() + offset);
        mTouchHandler.onInterceptTouchEvent(mStubListView, stubDownEvent);
        mTouchHandler.onInterceptTouchEvent(mStubListView, stubMoveEvent);
        mTouchHandler.onInterceptTouchEvent(mStubListView, stubUpEvent);

        verify(mMenuAnimationController).moveToEdgeAndHide();
    }

    @After
    public void tearDown() {
        mMotionEventHelper.recycleEvents();