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:
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user