Refactor the design and improve the animations of Accessibility Floating Menu(11/n).
Goals: Show the dock tooltip to indicate users how to use the feature of moving the menu to be tucked. Bug: 227715451 Test: atest MenuAnimationControllerTest Change-Id: Idd0d85f7d333701a7545e99a20164243e0ce12ec
This commit is contained in:
@@ -25,6 +25,9 @@ import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.animation.Animation;
|
||||
import android.view.animation.OvershootInterpolator;
|
||||
import android.view.animation.TranslateAnimation;
|
||||
|
||||
import androidx.dynamicanimation.animation.DynamicAnimation;
|
||||
import androidx.dynamicanimation.animation.FlingAnimation;
|
||||
@@ -33,6 +36,7 @@ import androidx.dynamicanimation.animation.SpringAnimation;
|
||||
import androidx.dynamicanimation.animation.SpringForce;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.util.Preconditions;
|
||||
|
||||
import java.util.HashMap;
|
||||
@@ -55,7 +59,11 @@ class MenuAnimationController {
|
||||
private static final float SPRING_AFTER_FLING_DAMPING_RATIO = 0.85f;
|
||||
private static final float SPRING_STIFFNESS = 700f;
|
||||
private static final float ESCAPE_VELOCITY = 750f;
|
||||
// Make tucked animation by using translation X relative to the view itself.
|
||||
private static final float ANIMATION_TO_X_VALUE = 0.5f;
|
||||
|
||||
private static final int ANIMATION_START_OFFSET_MS = 600;
|
||||
private static final int ANIMATION_DURATION_MS = 600;
|
||||
private static final int FADE_OUT_DURATION_MS = 1000;
|
||||
private static final int FADE_EFFECT_DURATION_MS = 3000;
|
||||
|
||||
@@ -64,10 +72,12 @@ class MenuAnimationController {
|
||||
private final Handler mHandler;
|
||||
private boolean mIsFadeEffectEnabled;
|
||||
private DismissAnimationController.DismissCallback mDismissCallback;
|
||||
private Runnable mSpringAnimationsEndAction;
|
||||
|
||||
// Cache the animations state of {@link DynamicAnimation.TRANSLATION_X} and {@link
|
||||
// DynamicAnimation.TRANSLATION_Y} to be well controlled by the touch handler
|
||||
private final HashMap<DynamicAnimation.ViewProperty, DynamicAnimation> mPositionAnimations =
|
||||
@VisibleForTesting
|
||||
final HashMap<DynamicAnimation.ViewProperty, DynamicAnimation> mPositionAnimations =
|
||||
new HashMap<>();
|
||||
|
||||
MenuAnimationController(MenuView menuView) {
|
||||
@@ -102,6 +112,13 @@ class MenuAnimationController {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the action to be called when the all dynamic animations are completed.
|
||||
*/
|
||||
void setSpringAnimationsEndAction(Runnable runnable) {
|
||||
mSpringAnimationsEndAction = runnable;
|
||||
}
|
||||
|
||||
void setDismissCallback(
|
||||
DismissAnimationController.DismissCallback dismissCallback) {
|
||||
mDismissCallback = dismissCallback;
|
||||
@@ -192,7 +209,7 @@ class MenuAnimationController {
|
||||
? bounds.right
|
||||
: bounds.bottom;
|
||||
|
||||
final FlingAnimation flingAnimation = new FlingAnimation(mMenuView, menuPositionProperty);
|
||||
final FlingAnimation flingAnimation = createFlingAnimation(mMenuView, menuPositionProperty);
|
||||
flingAnimation.setFriction(friction)
|
||||
.setStartVelocity(velocity)
|
||||
.setMinValue(Math.min(currentValue, min))
|
||||
@@ -217,7 +234,14 @@ class MenuAnimationController {
|
||||
flingAnimation.start();
|
||||
}
|
||||
|
||||
private void springMenuWith(DynamicAnimation.ViewProperty property, SpringForce spring,
|
||||
@VisibleForTesting
|
||||
FlingAnimation createFlingAnimation(MenuView menuView,
|
||||
MenuPositionProperty menuPositionProperty) {
|
||||
return new FlingAnimation(menuView, menuPositionProperty);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void springMenuWith(DynamicAnimation.ViewProperty property, SpringForce spring,
|
||||
float velocity, float finalPosition) {
|
||||
final MenuPositionProperty menuPositionProperty = new MenuPositionProperty(property);
|
||||
final SpringAnimation springAnimation =
|
||||
@@ -228,8 +252,13 @@ class MenuAnimationController {
|
||||
return;
|
||||
}
|
||||
|
||||
onSpringAnimationEnd(new PointF(mMenuView.getTranslationX(),
|
||||
mMenuView.getTranslationY()));
|
||||
final boolean areAnimationsRunning =
|
||||
mPositionAnimations.values().stream().anyMatch(
|
||||
DynamicAnimation::isRunning);
|
||||
if (!areAnimationsRunning) {
|
||||
onSpringAnimationsEnd(new PointF(mMenuView.getTranslationX(),
|
||||
mMenuView.getTranslationY()));
|
||||
}
|
||||
})
|
||||
.setStartVelocity(velocity);
|
||||
|
||||
@@ -332,11 +361,15 @@ class MenuAnimationController {
|
||||
.start();
|
||||
}
|
||||
|
||||
private void onSpringAnimationEnd(PointF position) {
|
||||
private void onSpringAnimationsEnd(PointF position) {
|
||||
mMenuView.onBoundsInParentChanged((int) position.x, (int) position.y);
|
||||
constrainPositionAndUpdate(position);
|
||||
|
||||
fadeOutIfEnabled();
|
||||
|
||||
if (mSpringAnimationsEndAction != null) {
|
||||
mSpringAnimationsEndAction.run();
|
||||
}
|
||||
}
|
||||
|
||||
private void constrainPositionAndUpdate(PointF position) {
|
||||
@@ -387,6 +420,26 @@ class MenuAnimationController {
|
||||
mHandler.removeCallbacksAndMessages(/* token= */ null);
|
||||
}
|
||||
|
||||
void startTuckedAnimationPreview() {
|
||||
fadeInNowIfEnabled();
|
||||
|
||||
final float toXValue = isOnLeftSide()
|
||||
? -ANIMATION_TO_X_VALUE
|
||||
: ANIMATION_TO_X_VALUE;
|
||||
final TranslateAnimation animation =
|
||||
new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0,
|
||||
Animation.RELATIVE_TO_SELF, toXValue,
|
||||
Animation.RELATIVE_TO_SELF, 0,
|
||||
Animation.RELATIVE_TO_SELF, 0);
|
||||
animation.setDuration(ANIMATION_DURATION_MS);
|
||||
animation.setRepeatMode(Animation.REVERSE);
|
||||
animation.setInterpolator(new OvershootInterpolator());
|
||||
animation.setRepeatCount(Animation.INFINITE);
|
||||
animation.setStartOffset(ANIMATION_START_OFFSET_MS);
|
||||
|
||||
mMenuView.startAnimation(animation);
|
||||
}
|
||||
|
||||
private Handler createUiHandler() {
|
||||
return new Handler(requireNonNull(Looper.myLooper(), "looper must not be null"));
|
||||
}
|
||||
|
||||
@@ -56,6 +56,7 @@ class MenuInfoRepository {
|
||||
@FloatRange(from = 0.0, to = 1.0)
|
||||
private static final float DEFAULT_MENU_POSITION_Y_PERCENT = 0.77f;
|
||||
private static final boolean DEFAULT_MOVE_TO_TUCKED_VALUE = false;
|
||||
private static final boolean DEFAULT_HAS_SEEN_DOCK_TOOLTIP_VALUE = false;
|
||||
private static final int DEFAULT_MIGRATION_TOOLTIP_VALUE_PROMPT = MigrationPrompt.DISABLED;
|
||||
|
||||
private final Context mContext;
|
||||
@@ -114,6 +115,12 @@ class MenuInfoRepository {
|
||||
DEFAULT_MOVE_TO_TUCKED_VALUE));
|
||||
}
|
||||
|
||||
void loadDockTooltipVisibility(OnInfoReady<Boolean> callback) {
|
||||
callback.onReady(Prefs.getBoolean(mContext,
|
||||
Prefs.Key.HAS_SEEN_ACCESSIBILITY_FLOATING_MENU_DOCK_TOOLTIP,
|
||||
DEFAULT_HAS_SEEN_DOCK_TOOLTIP_VALUE));
|
||||
}
|
||||
|
||||
void loadMigrationTooltipVisibility(OnInfoReady<Boolean> callback) {
|
||||
callback.onReady(Settings.Secure.getIntForUser(mContext.getContentResolver(),
|
||||
ACCESSIBILITY_FLOATING_MENU_MIGRATION_TOOLTIP_PROMPT,
|
||||
@@ -153,6 +160,11 @@ class MenuInfoRepository {
|
||||
percentagePosition.toString());
|
||||
}
|
||||
|
||||
void updateDockTooltipVisibility(boolean hasSeen) {
|
||||
Prefs.putBoolean(mContext, Prefs.Key.HAS_SEEN_ACCESSIBILITY_FLOATING_MENU_DOCK_TOOLTIP,
|
||||
hasSeen);
|
||||
}
|
||||
|
||||
void updateMigrationTooltipVisibility(boolean visible) {
|
||||
Settings.Secure.putIntForUser(mContext.getContentResolver(),
|
||||
ACCESSIBILITY_FLOATING_MENU_MIGRATION_TOOLTIP_PROMPT,
|
||||
|
||||
@@ -89,10 +89,13 @@ class MenuViewLayer extends FrameLayout implements
|
||||
private final IAccessibilityFloatingMenu mFloatingMenu;
|
||||
private final DismissAnimationController mDismissAnimationController;
|
||||
private final MenuViewModel mMenuViewModel;
|
||||
private final Observer<Boolean> mDockTooltipObserver =
|
||||
this::onDockTooltipVisibilityChanged;
|
||||
private final Observer<Boolean> mMigrationTooltipObserver =
|
||||
this::onMigrationTooltipVisibilityChanged;
|
||||
private final Rect mImeInsetsRect = new Rect();
|
||||
private boolean mIsMigrationTooltipShowing;
|
||||
private boolean mShouldShowDockTooltip;
|
||||
private Optional<MenuEduTooltipView> mEduTooltipView = Optional.empty();
|
||||
|
||||
@IntDef({
|
||||
@@ -111,10 +114,12 @@ class MenuViewLayer extends FrameLayout implements
|
||||
|
||||
@StringDef({
|
||||
TooltipType.MIGRATION,
|
||||
TooltipType.DOCK,
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@interface TooltipType {
|
||||
String MIGRATION = "migration";
|
||||
String DOCK = "dock";
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
@@ -154,6 +159,7 @@ class MenuViewLayer extends FrameLayout implements
|
||||
mMenuView = new MenuView(context, mMenuViewModel, mMenuViewAppearance);
|
||||
mMenuAnimationController = mMenuView.getMenuAnimationController();
|
||||
mMenuAnimationController.setDismissCallback(this::hideMenuAndShowMessage);
|
||||
mMenuAnimationController.setSpringAnimationsEndAction(this::onSpringAnimationsEndAction);
|
||||
mDismissView = new DismissView(context);
|
||||
mDismissAnimationController = new DismissAnimationController(mDismissView, mMenuView);
|
||||
mDismissAnimationController.setMagnetListener(new MagnetizedObject.MagnetListener() {
|
||||
@@ -236,6 +242,7 @@ class MenuViewLayer extends FrameLayout implements
|
||||
setOnClickListener(this);
|
||||
setOnApplyWindowInsetsListener((view, insets) -> onWindowInsetsApplied(insets));
|
||||
getViewTreeObserver().addOnComputeInternalInsetsListener(this);
|
||||
mMenuViewModel.getDockTooltipVisibilityData().observeForever(mDockTooltipObserver);
|
||||
mMenuViewModel.getMigrationTooltipVisibilityData().observeForever(
|
||||
mMigrationTooltipObserver);
|
||||
mMessageView.setUndoListener(view -> undo());
|
||||
@@ -250,6 +257,7 @@ class MenuViewLayer extends FrameLayout implements
|
||||
setOnClickListener(null);
|
||||
setOnApplyWindowInsetsListener(null);
|
||||
getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
|
||||
mMenuViewModel.getDockTooltipVisibilityData().removeObserver(mDockTooltipObserver);
|
||||
mMenuViewModel.getMigrationTooltipVisibilityData().removeObserver(
|
||||
mMigrationTooltipObserver);
|
||||
mHandler.removeCallbacksAndMessages(/* token= */ null);
|
||||
@@ -306,6 +314,21 @@ class MenuViewLayer extends FrameLayout implements
|
||||
}
|
||||
}
|
||||
|
||||
private void onDockTooltipVisibilityChanged(boolean hasSeenTooltip) {
|
||||
mShouldShowDockTooltip = !hasSeenTooltip;
|
||||
}
|
||||
|
||||
private void onSpringAnimationsEndAction() {
|
||||
if (mShouldShowDockTooltip) {
|
||||
mEduTooltipView = Optional.of(new MenuEduTooltipView(mContext, mMenuViewAppearance));
|
||||
mEduTooltipView.ifPresent(view -> addTooltipView(view,
|
||||
getContext().getText(R.string.accessibility_floating_button_docking_tooltip),
|
||||
TooltipType.DOCK));
|
||||
|
||||
mMenuAnimationController.startTuckedAnimationPreview();
|
||||
}
|
||||
}
|
||||
|
||||
private CharSequence getMigrationMessage() {
|
||||
final Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_DETAILS_SETTINGS);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
@@ -341,6 +364,12 @@ class MenuViewLayer extends FrameLayout implements
|
||||
mIsMigrationTooltipShowing = false;
|
||||
}
|
||||
|
||||
if (tooltipView.getTag().equals(TooltipType.DOCK)) {
|
||||
mMenuViewModel.updateDockTooltipVisibility(/* hasSeen= */ true);
|
||||
mMenuView.clearAnimation();
|
||||
mShouldShowDockTooltip = false;
|
||||
}
|
||||
|
||||
removeView(tooltipView);
|
||||
|
||||
mMenuListViewTouchHandler.setOnActionDownEndListener(null);
|
||||
|
||||
@@ -36,6 +36,7 @@ class MenuViewModel implements MenuInfoRepository.OnSettingsContentsChanged {
|
||||
private final MutableLiveData<MenuFadeEffectInfo> mFadeEffectInfoData =
|
||||
new MutableLiveData<>();
|
||||
private final MutableLiveData<Boolean> mMoveToTuckedData = new MutableLiveData<>();
|
||||
private final MutableLiveData<Boolean> mDockTooltipData = new MutableLiveData<>();
|
||||
private final MutableLiveData<Boolean> mMigrationTooltipData = new MutableLiveData<>();
|
||||
private final MutableLiveData<Position> mPercentagePositionData = new MutableLiveData<>();
|
||||
private final MenuInfoRepository mInfoRepository;
|
||||
@@ -67,6 +68,10 @@ class MenuViewModel implements MenuInfoRepository.OnSettingsContentsChanged {
|
||||
mInfoRepository.updateMenuSavingPosition(percentagePosition);
|
||||
}
|
||||
|
||||
void updateDockTooltipVisibility(boolean hasSeen) {
|
||||
mInfoRepository.updateDockTooltipVisibility(hasSeen);
|
||||
}
|
||||
|
||||
void updateMigrationTooltipVisibility(boolean visible) {
|
||||
mInfoRepository.updateMigrationTooltipVisibility(visible);
|
||||
}
|
||||
@@ -76,6 +81,11 @@ class MenuViewModel implements MenuInfoRepository.OnSettingsContentsChanged {
|
||||
return mMoveToTuckedData;
|
||||
}
|
||||
|
||||
LiveData<Boolean> getDockTooltipVisibilityData() {
|
||||
mInfoRepository.loadDockTooltipVisibility(mDockTooltipData::setValue);
|
||||
return mDockTooltipData;
|
||||
}
|
||||
|
||||
LiveData<Boolean> getMigrationTooltipVisibilityData() {
|
||||
mInfoRepository.loadMigrationTooltipVisibility(mMigrationTooltipData::setValue);
|
||||
return mMigrationTooltipData;
|
||||
|
||||
@@ -20,8 +20,10 @@ import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.Mockito.any;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyZeroInteractions;
|
||||
|
||||
import android.graphics.PointF;
|
||||
import android.testing.AndroidTestingRunner;
|
||||
@@ -30,6 +32,10 @@ import android.view.View;
|
||||
import android.view.ViewPropertyAnimator;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import androidx.dynamicanimation.animation.DynamicAnimation;
|
||||
import androidx.dynamicanimation.animation.FlingAnimation;
|
||||
import androidx.dynamicanimation.animation.SpringAnimation;
|
||||
import androidx.dynamicanimation.animation.SpringForce;
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.systemui.Prefs;
|
||||
@@ -39,6 +45,9 @@ import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/** Tests for {@link MenuAnimationController}. */
|
||||
@RunWith(AndroidTestingRunner.class)
|
||||
@@ -47,9 +56,10 @@ import org.junit.runner.RunWith;
|
||||
public class MenuAnimationControllerTest extends SysuiTestCase {
|
||||
|
||||
private boolean mLastIsMoveToTucked;
|
||||
private ArgumentCaptor<DynamicAnimation.OnAnimationEndListener> mEndListenerCaptor;
|
||||
private ViewPropertyAnimator mViewPropertyAnimator;
|
||||
private MenuView mMenuView;
|
||||
private MenuAnimationController mMenuAnimationController;
|
||||
private TestMenuAnimationController mMenuAnimationController;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
@@ -62,15 +72,17 @@ public class MenuAnimationControllerTest extends SysuiTestCase {
|
||||
mViewPropertyAnimator = spy(mMenuView.animate());
|
||||
doReturn(mViewPropertyAnimator).when(mMenuView).animate();
|
||||
|
||||
mMenuAnimationController = new MenuAnimationController(mMenuView);
|
||||
mMenuAnimationController = new TestMenuAnimationController(mMenuView);
|
||||
mLastIsMoveToTucked = Prefs.getBoolean(mContext,
|
||||
Prefs.Key.HAS_ACCESSIBILITY_FLOATING_MENU_TUCKED, /* defaultValue= */ false);
|
||||
mEndListenerCaptor = ArgumentCaptor.forClass(DynamicAnimation.OnAnimationEndListener.class);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
Prefs.putBoolean(mContext, Prefs.Key.HAS_ACCESSIBILITY_FLOATING_MENU_TUCKED,
|
||||
mLastIsMoveToTucked);
|
||||
mEndListenerCaptor.getAllValues().clear();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -122,4 +134,115 @@ public class MenuAnimationControllerTest extends SysuiTestCase {
|
||||
|
||||
assertThat(isMoveToTucked).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void startTuckedAnimationPreview_hasAnimation() {
|
||||
mMenuView.clearAnimation();
|
||||
|
||||
mMenuAnimationController.startTuckedAnimationPreview();
|
||||
|
||||
assertThat(mMenuView.getAnimation()).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void startSpringAnimationsAndEndOneAnimation_notTriggerEndAction() {
|
||||
final Runnable onSpringAnimationsEndCallback = mock(Runnable.class);
|
||||
mMenuAnimationController.setSpringAnimationsEndAction(onSpringAnimationsEndCallback);
|
||||
|
||||
setupAndRunSpringAnimations();
|
||||
final Optional<DynamicAnimation> anyAnimation =
|
||||
mMenuAnimationController.mPositionAnimations.values().stream().findAny();
|
||||
anyAnimation.ifPresent(this::skipAnimationToEnd);
|
||||
|
||||
verifyZeroInteractions(onSpringAnimationsEndCallback);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void startAndEndSpringAnimations_triggerEndAction() {
|
||||
final Runnable onSpringAnimationsEndCallback = mock(Runnable.class);
|
||||
mMenuAnimationController.setSpringAnimationsEndAction(onSpringAnimationsEndCallback);
|
||||
|
||||
setupAndRunSpringAnimations();
|
||||
mMenuAnimationController.mPositionAnimations.values().forEach(this::skipAnimationToEnd);
|
||||
|
||||
verify(onSpringAnimationsEndCallback).run();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void flingThenSpringAnimationsAreEnded_triggerEndAction() {
|
||||
final Runnable onSpringAnimationsEndCallback = mock(Runnable.class);
|
||||
mMenuAnimationController.setSpringAnimationsEndAction(onSpringAnimationsEndCallback);
|
||||
|
||||
mMenuAnimationController.flingMenuThenSpringToEdge(/* x= */ 0, /* velocityX= */
|
||||
100, /* velocityY= */ 100);
|
||||
mMenuAnimationController.mPositionAnimations.values()
|
||||
.forEach(animation -> verify((FlingAnimation) animation).addEndListener(
|
||||
mEndListenerCaptor.capture()));
|
||||
mEndListenerCaptor.getAllValues()
|
||||
.forEach(listener -> listener.onAnimationEnd(mock(DynamicAnimation.class),
|
||||
/* canceled */ false, /* endValue */ 0, /* endVelocity */ 0));
|
||||
mMenuAnimationController.mPositionAnimations.values().forEach(this::skipAnimationToEnd);
|
||||
|
||||
verify(onSpringAnimationsEndCallback).run();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void existFlingIsRunningAndTheOtherAreEnd_notTriggerEndAction() {
|
||||
final Runnable onSpringAnimationsEndCallback = mock(Runnable.class);
|
||||
mMenuAnimationController.setSpringAnimationsEndAction(onSpringAnimationsEndCallback);
|
||||
|
||||
mMenuAnimationController.flingMenuThenSpringToEdge(/* x= */ 0, /* velocityX= */
|
||||
200, /* velocityY= */ 200);
|
||||
mMenuAnimationController.mPositionAnimations.values()
|
||||
.forEach(animation -> verify((FlingAnimation) animation).addEndListener(
|
||||
mEndListenerCaptor.capture()));
|
||||
final Optional<DynamicAnimation.OnAnimationEndListener> anyAnimation =
|
||||
mEndListenerCaptor.getAllValues().stream().findAny();
|
||||
anyAnimation.ifPresent(
|
||||
listener -> listener.onAnimationEnd(mock(DynamicAnimation.class), /* canceled */
|
||||
false, /* endValue */ 0, /* endVelocity */ 0));
|
||||
mMenuAnimationController.mPositionAnimations.values()
|
||||
.stream()
|
||||
.filter(animation -> animation instanceof SpringAnimation)
|
||||
.forEach(this::skipAnimationToEnd);
|
||||
|
||||
verifyZeroInteractions(onSpringAnimationsEndCallback);
|
||||
}
|
||||
|
||||
private void setupAndRunSpringAnimations() {
|
||||
final float stiffness = 700f;
|
||||
final float dampingRatio = 0.85f;
|
||||
final float velocity = 100f;
|
||||
final float finalPosition = 300f;
|
||||
|
||||
mMenuAnimationController.springMenuWith(DynamicAnimation.TRANSLATION_X, new SpringForce()
|
||||
.setStiffness(stiffness)
|
||||
.setDampingRatio(dampingRatio), velocity, finalPosition);
|
||||
mMenuAnimationController.springMenuWith(DynamicAnimation.TRANSLATION_Y, new SpringForce()
|
||||
.setStiffness(stiffness)
|
||||
.setDampingRatio(dampingRatio), velocity, finalPosition);
|
||||
}
|
||||
|
||||
private void skipAnimationToEnd(DynamicAnimation animation) {
|
||||
final SpringAnimation springAnimation = ((SpringAnimation) animation);
|
||||
// The doAnimationFrame function is used for skipping animation to the end.
|
||||
springAnimation.doAnimationFrame(100);
|
||||
springAnimation.skipToEnd();
|
||||
springAnimation.doAnimationFrame(200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper class for testing.
|
||||
*/
|
||||
private static class TestMenuAnimationController extends MenuAnimationController {
|
||||
TestMenuAnimationController(MenuView menuView) {
|
||||
super(menuView);
|
||||
}
|
||||
|
||||
@Override
|
||||
FlingAnimation createFlingAnimation(MenuView menuView,
|
||||
MenuPositionProperty menuPositionProperty) {
|
||||
return spy(super.createFlingAnimation(menuView, menuPositionProperty));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user