diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java index 5691ecf9fd..db225be4a9 100644 --- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java +++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java @@ -158,11 +158,11 @@ public class HotseatEduDialog extends AbstractSlideInView implements I } private void animateOpen() { - if (mIsOpen || mOpenCloseAnimator.isRunning()) { + if (mIsOpen || mOpenCloseAnimation.getAnimationPlayer().isRunning()) { return; } mIsOpen = true; - setUpDefaultOpenAnimator().start(); + setUpDefaultOpenAnimation().start(); } @Override diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java index 3fe7359573..9ee7b0e5a5 100644 --- a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java +++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java @@ -17,6 +17,7 @@ package com.android.launcher3.taskbar.allapps; import static com.android.app.animation.Interpolators.EMPHASIZED; +import android.animation.Animator; import android.content.Context; import android.graphics.Canvas; import android.graphics.Rect; @@ -58,7 +59,7 @@ public class TaskbarAllAppsSlideInView extends AbstractSlideInView mAllAppsCallbacks.onAllAppsTransitionEnd(true))); - mOpenCloseAnimator.setDuration(mAllAppsCallbacks.getOpenDuration()).start(); + animator.start(); } else { mTranslationShift = TRANSLATION_SHIFT_OPENED; mAllAppsCallbacks.onAllAppsTransitionEnd(true); diff --git a/src/com/android/launcher3/views/AbstractSlideInView.java b/src/com/android/launcher3/views/AbstractSlideInView.java index f69d299cf3..30e09718c2 100644 --- a/src/com/android/launcher3/views/AbstractSlideInView.java +++ b/src/com/android/launcher3/views/AbstractSlideInView.java @@ -17,6 +17,7 @@ package com.android.launcher3.views; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; +import static com.android.app.animation.Interpolators.LINEAR; import static com.android.app.animation.Interpolators.scrollInterpolatorForVelocity; import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY; import static com.android.launcher3.LauncherAnimUtils.SUCCESS_TRANSITION_PROGRESS; @@ -24,16 +25,14 @@ import static com.android.launcher3.LauncherAnimUtils.TABLET_BOTTOM_SHEET_SUCCES import static com.android.launcher3.allapps.AllAppsTransitionController.REVERT_SWIPE_ALL_APPS_TO_HOME_ANIMATION_DURATION_MS; import static com.android.launcher3.util.ScrollableLayoutManager.PREDICTIVE_BACK_MIN_SCALE; -import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; -import android.animation.PropertyValuesHolder; +import android.animation.ValueAnimator; import android.content.Context; import android.graphics.Canvas; import android.graphics.Outline; import android.graphics.drawable.Drawable; import android.os.Build; import android.util.AttributeSet; -import android.util.Property; +import android.util.FloatProperty; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; @@ -51,6 +50,8 @@ import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.Utilities; import com.android.launcher3.anim.AnimatedFloat; import com.android.launcher3.anim.AnimatorListeners; +import com.android.launcher3.anim.AnimatorPlaybackController; +import com.android.launcher3.anim.PendingAnimation; import com.android.launcher3.touch.BaseSwipeDetector; import com.android.launcher3.touch.SingleAxisSwipeDetector; @@ -66,8 +67,8 @@ import java.util.Optional; public abstract class AbstractSlideInView extends AbstractFloatingView implements SingleAxisSwipeDetector.Listener { - protected static final Property TRANSLATION_SHIFT = - new Property(Float.class, "translationShift") { + protected static final FloatProperty> TRANSLATION_SHIFT = + new FloatProperty<>("translationShift") { @Override public Float get(AbstractSlideInView view) { @@ -75,31 +76,54 @@ public abstract class AbstractSlideInView } @Override - public void set(AbstractSlideInView view, Float value) { + public void setValue(AbstractSlideInView view, float value) { view.setTranslationShift(value); } }; protected static final float TRANSLATION_SHIFT_CLOSED = 1f; protected static final float TRANSLATION_SHIFT_OPENED = 0f; private static final float VIEW_NO_SCALE = 1f; - private static final int NO_DURATION = -1; + private static final int DEFAULT_DURATION = 300; protected final T mActivityContext; protected final SingleAxisSwipeDetector mSwipeDetector; - protected @NonNull AnimatorSet mOpenCloseAnimator; - private final ObjectAnimator mTranslationShiftAnimator; + protected @NonNull AnimatorPlaybackController mOpenCloseAnimation; protected ViewGroup mContent; protected final View mColorScrim; + /** + * Interpolator for {@link #mOpenCloseAnimation} when we are closing due to dragging downwards. + */ private Interpolator mScrollInterpolator; private long mScrollDuration; + /** + * End progress for {@link #mOpenCloseAnimation} when we are closing due to dragging downloads. + *

+ * There are two cases that determine this value: + *

    + *
  1. + * If the drag interrupts the opening transition (i.e. {@link #mToTranslationShift} + * is {@link #TRANSLATION_SHIFT_OPENED}), we need to animate back to {@code 0} to + * reverse the animation that was paused at {@link #onDragStart(boolean, float)}. + *
  2. + *
  3. + * If the drag started after the view is fully opened (i.e. + * {@link #mToTranslationShift} is {@link #TRANSLATION_SHIFT_CLOSED}), the animation + * that was set up at {@link #onDragStart(boolean, float)} for closing the view + * should go forward to {@code 1}. + *
  4. + *
+ */ + private float mScrollEndProgress; // range [0, 1], 0=> completely open, 1=> completely closed protected float mTranslationShift = TRANSLATION_SHIFT_CLOSED; - /** {@link #mTranslationShift} at the invocation of {@link #onDragStart(boolean, float)}. */ - protected float mDragStartTranslationShift; + protected float mFromTranslationShift; + protected float mToTranslationShift; + /** {@link #mOpenCloseAnimation} progress at {@link #onDragStart(boolean, float)}. */ + private float mDragStartProgress; protected boolean mNoIntercept; protected @Nullable OnCloseListener mOnCloseBeginListener; @@ -128,52 +152,78 @@ public abstract class AbstractSlideInView mActivityContext = ActivityContext.lookupContext(context); mScrollInterpolator = Interpolators.SCROLL_CUBIC; - mScrollDuration = NO_DURATION; + mScrollDuration = DEFAULT_DURATION; mSwipeDetector = new SingleAxisSwipeDetector(context, this, SingleAxisSwipeDetector.VERTICAL); - mOpenCloseAnimator = new AnimatorSet(); - mTranslationShiftAnimator = ObjectAnimator.ofPropertyValuesHolder(this); + mOpenCloseAnimation = new PendingAnimation(0).createPlaybackController(); int scrimColor = getScrimColor(context); mColorScrim = scrimColor != -1 ? createColorScrim(context, scrimColor) : null; } /** - * Sets up a {@link #mOpenCloseAnimator} for opening with default parameters. + * Sets up a {@link #mOpenCloseAnimation} for opening with default parameters. * - * @see #setUpOpenCloseAnimator(float, Interpolator) + * @see #setUpOpenCloseAnimation(float, float, long) */ - protected final AnimatorSet setUpDefaultOpenAnimator() { - return setUpOpenCloseAnimator(TRANSLATION_SHIFT_OPENED, Interpolators.FAST_OUT_SLOW_IN); + protected final AnimatorPlaybackController setUpDefaultOpenAnimation() { + AnimatorPlaybackController animation = setUpOpenCloseAnimation( + TRANSLATION_SHIFT_CLOSED, TRANSLATION_SHIFT_OPENED, DEFAULT_DURATION); + animation.getAnimationPlayer().setInterpolator(Interpolators.FAST_OUT_SLOW_IN); + return animation; } /** - * Initializes a new {@link #mOpenCloseAnimator}. - *

- * Subclasses should override this method if they want to add more {@code Animator} instances - * to the set. + * Sets up a {@link #mOpenCloseAnimation} for opening with a given duration. * - * @param translationShift translation shift to animate to. - * @param translationShiftInterpolator interpolator for {@link #mTranslationShiftAnimator}. - * @return {@link #mOpenCloseAnimator} + * @see #setUpOpenCloseAnimation(float, float, long) */ - protected AnimatorSet setUpOpenCloseAnimator( - float translationShift, Interpolator translationShiftInterpolator) { - mOpenCloseAnimator = new AnimatorSet(); - mOpenCloseAnimator.addListener(AnimatorListeners.forEndCallback(() -> { + protected final AnimatorPlaybackController setUpOpenAnimation(long duration) { + return setUpOpenCloseAnimation( + TRANSLATION_SHIFT_CLOSED, TRANSLATION_SHIFT_OPENED, duration); + } + + private AnimatorPlaybackController setUpCloseAnimation(long duration) { + return setUpOpenCloseAnimation( + TRANSLATION_SHIFT_OPENED, TRANSLATION_SHIFT_CLOSED, duration); + } + + /** + * Initializes a new {@link #mOpenCloseAnimation}. + * + * @param fromTranslationShift translation shift to animate from. + * @param toTranslationShift translation shift to animate to. + * @param duration animation duration. + * @return {@link #mOpenCloseAnimation} + */ + private AnimatorPlaybackController setUpOpenCloseAnimation( + float fromTranslationShift, float toTranslationShift, long duration) { + mFromTranslationShift = fromTranslationShift; + mToTranslationShift = toTranslationShift; + + PendingAnimation animation = new PendingAnimation(duration); + animation.addEndListener(b -> { mSwipeDetector.finishedScrolling(); announceAccessibilityChanges(); - })); + }); - mTranslationShiftAnimator.setValues(PropertyValuesHolder.ofFloat( - TRANSLATION_SHIFT, translationShift)); - mTranslationShiftAnimator.setInterpolator(translationShiftInterpolator); - mOpenCloseAnimator.play(mTranslationShiftAnimator); + animation.addFloat( + this, TRANSLATION_SHIFT, fromTranslationShift, toTranslationShift, LINEAR); + onOpenCloseAnimationPending(animation); - return mOpenCloseAnimator; + mOpenCloseAnimation = animation.createPlaybackController(); + return mOpenCloseAnimation; } + /** + * Invoked when a {@link #mOpenCloseAnimation} is being set up. + *

+ * Subclasses can override this method to modify the animation before it's used to create a + * {@link AnimatorPlaybackController}. + */ + protected void onOpenCloseAnimationPending(PendingAnimation animation) {} + protected void attachToContainer() { if (mColorScrim != null) { getPopupContainer().addView(mColorScrim); @@ -316,29 +366,33 @@ public abstract class AbstractSlideInView } private boolean isOpeningAnimationRunning() { - return mIsOpen && mOpenCloseAnimator.isRunning(); + return mIsOpen && mOpenCloseAnimation.getAnimationPlayer().isRunning(); } /* SingleAxisSwipeDetector.Listener */ @Override public void onDragStart(boolean start, float startDisplacement) { - mOpenCloseAnimator.cancel(); - mDragStartTranslationShift = mTranslationShift; + if (mOpenCloseAnimation.getAnimationPlayer().isRunning()) { + mOpenCloseAnimation.pause(); + mDragStartProgress = mOpenCloseAnimation.getProgressFraction(); + } else { + setUpCloseAnimation(DEFAULT_DURATION); + mDragStartProgress = 0; + } } @Override public boolean onDrag(float displacement) { - setTranslationShift(Utilities.boundToRange( - mDragStartTranslationShift + displacement / getShiftRange(), - TRANSLATION_SHIFT_OPENED, - TRANSLATION_SHIFT_CLOSED)); + float progress = mDragStartProgress + + Math.signum(mToTranslationShift - mFromTranslationShift) + * (displacement / getShiftRange()); + mOpenCloseAnimation.setPlayFraction(Utilities.boundToRange(progress, 0, 1)); return true; } @Override public void onDragEnd(float velocity) { - mDragStartTranslationShift = 0; float successfulShiftThreshold = mActivityContext.getDeviceProfile().isTablet ? TABLET_BOTTOM_SHEET_SUCCESS_TRANSITION_PROGRESS : SUCCESS_TRANSITION_PROGRESS; if ((mSwipeDetector.isFling(velocity) && velocity > 0) @@ -346,10 +400,15 @@ public abstract class AbstractSlideInView mScrollInterpolator = scrollInterpolatorForVelocity(velocity); mScrollDuration = BaseSwipeDetector.calculateDuration( velocity, TRANSLATION_SHIFT_CLOSED - mTranslationShift); + mScrollEndProgress = mToTranslationShift == TRANSLATION_SHIFT_OPENED ? 0 : 1; close(true); } else { - setUpOpenCloseAnimator(TRANSLATION_SHIFT_OPENED, Interpolators.DECELERATE) - .setDuration(BaseSwipeDetector.calculateDuration(velocity, mTranslationShift)) + ValueAnimator animator = mOpenCloseAnimation.getAnimationPlayer(); + animator.setInterpolator(Interpolators.DECELERATE); + animator.setFloatValues( + mOpenCloseAnimation.getProgressFraction(), + mToTranslationShift == TRANSLATION_SHIFT_OPENED ? 1 : 0); + animator.setDuration(BaseSwipeDetector.calculateDuration(velocity, mTranslationShift)) .start(); } } @@ -371,24 +430,27 @@ public abstract class AbstractSlideInView Optional.ofNullable(mOnCloseBeginListener).ifPresent(OnCloseListener::onSlideInViewClosed); if (!animate) { - mOpenCloseAnimator.cancel(); + mOpenCloseAnimation.pause(); setTranslationShift(TRANSLATION_SHIFT_CLOSED); onCloseComplete(); return; } - final Interpolator interpolator; - final long duration; + final ValueAnimator animator; if (mSwipeDetector.isIdleState()) { - interpolator = getIdleInterpolator(); - duration = defaultDuration; + setUpCloseAnimation(defaultDuration); + animator = mOpenCloseAnimation.getAnimationPlayer(); + animator.setInterpolator(getIdleInterpolator()); } else { - interpolator = mScrollInterpolator; - duration = mScrollDuration > NO_DURATION ? mScrollDuration : defaultDuration; + animator = mOpenCloseAnimation.getAnimationPlayer(); + animator.setInterpolator(mScrollInterpolator); + animator.setDuration(mScrollDuration); + mOpenCloseAnimation.getAnimationPlayer().setFloatValues( + mOpenCloseAnimation.getProgressFraction(), mScrollEndProgress); } - setUpOpenCloseAnimator(TRANSLATION_SHIFT_CLOSED, interpolator) - .addListener(AnimatorListeners.forEndCallback(this::onCloseComplete)); - mOpenCloseAnimator.setDuration(duration).start(); + + animator.addListener(AnimatorListeners.forEndCallback(this::onCloseComplete)); + animator.start(); } protected Interpolator getIdleInterpolator() { diff --git a/src/com/android/launcher3/views/WidgetsEduView.java b/src/com/android/launcher3/views/WidgetsEduView.java index 92e048bbf5..e70b1cbd16 100644 --- a/src/com/android/launcher3/views/WidgetsEduView.java +++ b/src/com/android/launcher3/views/WidgetsEduView.java @@ -116,11 +116,11 @@ public class WidgetsEduView extends AbstractSlideInView implements Ins } private void animateOpen() { - if (mIsOpen || mOpenCloseAnimator.isRunning()) { + if (mIsOpen || mOpenCloseAnimation.getAnimationPlayer().isRunning()) { return; } mIsOpen = true; - setUpDefaultOpenAnimator().start(); + setUpDefaultOpenAnimation().start(); } /** Shows widget education dialog. */ diff --git a/src/com/android/launcher3/widget/AddItemWidgetsBottomSheet.java b/src/com/android/launcher3/widget/AddItemWidgetsBottomSheet.java index c6fc5fef85..80b1cdd83c 100644 --- a/src/com/android/launcher3/widget/AddItemWidgetsBottomSheet.java +++ b/src/com/android/launcher3/widget/AddItemWidgetsBottomSheet.java @@ -128,11 +128,11 @@ public class AddItemWidgetsBottomSheet extends AbstractSlideInView { - mOpenCloseAnimator - .setDuration(mActivityContext.getDeviceProfile().bottomSheetOpenDuration) + animator.setDuration(mActivityContext.getDeviceProfile().bottomSheetOpenDuration) .start(); mContent.animate().alpha(1).setDuration(FADE_IN_DURATION); });