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
This commit is contained in:
Peter Liang
2022-09-08 18:32:55 +08:00
parent 4f62018aa2
commit 4cda5ccc73
5 changed files with 132 additions and 4 deletions

View File

@@ -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.

View File

@@ -79,9 +79,11 @@ class MenuListViewTouchHandler implements RecyclerView.OnItemTouchListener {
final float endX = mMenuTranslationDown.x + dx;
mIsDragging = false;
mVelocityTracker.computeCurrentVelocity(VELOCITY_UNIT_SECONDS);
mMenuAnimationController.flingMenuThenSpringToEdge(endX,
mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity());
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;

View File

@@ -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);

View File

@@ -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();

View File

@@ -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();