Adding a compat implementation for playback control on AnimatorSet
Change-Id: I9f01fc319341cf2499fffb59521d32c2c81eba45
diff --git a/src/com/android/launcher3/PinchToOverviewListener.java b/src/com/android/launcher3/PinchToOverviewListener.java
index fc75fe3..d1a2538 100644
--- a/src/com/android/launcher3/PinchToOverviewListener.java
+++ b/src/com/android/launcher3/PinchToOverviewListener.java
@@ -16,30 +16,22 @@
package com.android.launcher3;
+import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
import android.animation.Animator;
-import android.animation.Animator.AnimatorListener;
import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
-import android.annotation.TargetApi;
-import android.os.Build;
-import android.util.Range;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.ScaleGestureDetector.OnScaleGestureListener;
+import com.android.launcher3.compat.AnimatorSetCompat;
import com.android.launcher3.util.TouchController;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
/**
* Detects pinches and animates the Workspace to/from overview mode.
*/
-@TargetApi(Build.VERSION_CODES.O)
public class PinchToOverviewListener
implements TouchController, OnScaleGestureListener, Runnable {
@@ -55,9 +47,8 @@
private Workspace mWorkspace = null;
private boolean mPinchStarted = false;
- private AnimatorSet mCurrentAnimation;
+ private AnimatorSetCompat mCurrentAnimation;
private float mCurrentScale;
- private Range<Integer> mDurationRange;
private boolean mShouldGoToFinalState;
private LauncherState mToState;
@@ -109,14 +100,13 @@
}
mToState = mLauncher.isInState(OVERVIEW) ? NORMAL : OVERVIEW;
- mCurrentAnimation = mLauncher.getStateManager()
- .createAnimationToNewWorkspace(mToState, this);
+ mCurrentAnimation = AnimatorSetCompat.wrap(mLauncher.getStateManager()
+ .createAnimationToNewWorkspace(mToState, this), OVERVIEW_TRANSITION_MS);
mPinchStarted = true;
mCurrentScale = 1;
- mDurationRange = Range.create(0, LauncherAnimUtils.OVERVIEW_TRANSITION_MS);
mShouldGoToFinalState = false;
- dispatchOnStart(mCurrentAnimation);
+ mCurrentAnimation.dispatchOnStart();
return true;
}
@@ -160,26 +150,7 @@
}
// Move the transition animation to that duration.
- long playPosition = mDurationRange.clamp(
- (int) ((1 - animationFraction) * mDurationRange.getUpper()));
- mCurrentAnimation.setCurrentPlayTime(playPosition);
-
+ mCurrentAnimation.setPlayFraction(1 - animationFraction);
return true;
}
-
- private void dispatchOnStart(Animator animator) {
- for (AnimatorListener l : nonNullList(animator.getListeners())) {
- l.onAnimationStart(animator);
- }
-
- if (animator instanceof AnimatorSet) {
- for (Animator anim : nonNullList(((AnimatorSet) animator).getChildAnimations())) {
- dispatchOnStart(anim);
- }
- }
- }
-
- private static <T> List<T> nonNullList(ArrayList<T> list) {
- return list == null ? Collections.<T>emptyList() : list;
- }
}
\ No newline at end of file
diff --git a/src/com/android/launcher3/compat/AnimatorSetCompat.java b/src/com/android/launcher3/compat/AnimatorSetCompat.java
new file mode 100644
index 0000000..497dd14
--- /dev/null
+++ b/src/com/android/launcher3/compat/AnimatorSetCompat.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.compat;
+
+import android.animation.Animator;
+import android.animation.Animator.AnimatorListener;
+import android.animation.AnimatorSet;
+import android.animation.ValueAnimator;
+import android.annotation.TargetApi;
+import android.os.Build;
+import android.view.animation.LinearInterpolator;
+
+import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.AnimationSuccessListener;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Compat implementation for various new APIs in {@link AnimatorSet}
+ *
+ * Note: The compat implementation does not support start delays on child animations or
+ * sequential playbacks.
+ */
+public abstract class AnimatorSetCompat implements ValueAnimator.AnimatorUpdateListener {
+
+ public static AnimatorSetCompat wrap(AnimatorSet anim, int duration) {
+ if (Utilities.ATLEAST_OREO) {
+ return new AnimatorSetCompatVO(anim, duration);
+ } else {
+ return new AnimatorSetCompatVL(anim, duration);
+ }
+ }
+
+ private final ValueAnimator mAnimationPlayer;
+ private final long mDuration;
+
+ protected final AnimatorSet mAnim;
+
+ protected float mCurrentFraction;
+
+ protected AnimatorSetCompat(AnimatorSet anim, int duration) {
+ mAnim = anim;
+ mDuration = duration;
+
+ mAnimationPlayer = ValueAnimator.ofFloat(0, 1);
+ mAnimationPlayer.setInterpolator(new LinearInterpolator());
+ mAnimationPlayer.addUpdateListener(this);
+ }
+
+ /**
+ * Starts playing the animation forward from current position.
+ */
+ public void start() {
+ mAnimationPlayer.setFloatValues(mCurrentFraction, 1);
+ mAnimationPlayer.setDuration(clampDuration(1 - mCurrentFraction));
+ mAnimationPlayer.addListener(new OnAnimationEndDispatcher());
+ mAnimationPlayer.start();
+ }
+
+ /**
+ * Starts playing the animation backwards from current position
+ */
+ public void reverse() {
+ mAnimationPlayer.setFloatValues(mCurrentFraction, 0);
+ mAnimationPlayer.setDuration(clampDuration(mCurrentFraction));
+ mAnimationPlayer.addListener(new OnAnimationEndDispatcher());
+ mAnimationPlayer.start();
+ }
+
+ /**
+ * Sets the current animation position and updates all the child animators accordingly.
+ */
+ public abstract void setPlayFraction(float fraction);
+
+ /**
+ * @see Animator#addListener(AnimatorListener)
+ */
+ public void addListener(Animator.AnimatorListener listener) {
+ mAnimationPlayer.addListener(listener);
+ }
+
+ @Override
+ public void onAnimationUpdate(ValueAnimator valueAnimator) {
+ setPlayFraction((float) valueAnimator.getAnimatedValue());
+ }
+
+ protected long clampDuration(float fraction) {
+ float playPos = mDuration * fraction;
+ if (playPos <= 0) {
+ return 0;
+ } else {
+ return Math.min((long) playPos, mDuration);
+ }
+ }
+
+ public void dispatchOnStart() {
+ dispatchOnStartRecursively(mAnim);
+ }
+
+ private void dispatchOnStartRecursively(Animator animator) {
+ for (AnimatorListener l : nonNullList(animator.getListeners())) {
+ l.onAnimationStart(animator);
+ }
+
+ if (animator instanceof AnimatorSet) {
+ for (Animator anim : nonNullList(((AnimatorSet) animator).getChildAnimations())) {
+ dispatchOnStartRecursively(anim);
+ }
+ }
+ }
+
+ public static class AnimatorSetCompatVL extends AnimatorSetCompat {
+
+ private final ValueAnimator[] mChildAnimations;
+
+ private AnimatorSetCompatVL(AnimatorSet anim, int duration) {
+ super(anim, duration);
+
+ // Build animation list
+ ArrayList<ValueAnimator> childAnims = new ArrayList<>();
+ getAnimationsRecur(mAnim, childAnims);
+ mChildAnimations = childAnims.toArray(new ValueAnimator[childAnims.size()]);
+ }
+
+ private void getAnimationsRecur(AnimatorSet anim, ArrayList<ValueAnimator> out) {
+ long forceDuration = anim.getDuration();
+ for (Animator child : anim.getChildAnimations()) {
+ if (forceDuration > 0) {
+ child.setDuration(forceDuration);
+ }
+ if (child instanceof ValueAnimator) {
+ out.add((ValueAnimator) child);
+ } else if (child instanceof AnimatorSet) {
+ getAnimationsRecur((AnimatorSet) child, out);
+ } else {
+ throw new RuntimeException("Unknown animation type " + child);
+ }
+ }
+ }
+
+ @Override
+ public void setPlayFraction(float fraction) {
+ mCurrentFraction = fraction;
+ long playPos = clampDuration(fraction);
+ for (ValueAnimator anim : mChildAnimations) {
+ anim.setCurrentPlayTime(Math.min(playPos, anim.getDuration()));
+ }
+ }
+
+ }
+
+ @TargetApi(Build.VERSION_CODES.O)
+ private static class AnimatorSetCompatVO extends AnimatorSetCompat {
+
+ private AnimatorSetCompatVO(AnimatorSet anim, int duration) {
+ super(anim, duration);
+ }
+
+ @Override
+ public void setPlayFraction(float fraction) {
+ mCurrentFraction = fraction;
+ mAnim.setCurrentPlayTime(clampDuration(fraction));
+ }
+ }
+
+ private class OnAnimationEndDispatcher extends AnimationSuccessListener {
+
+ @Override
+ public void onAnimationSuccess(Animator animator) {
+ dispatchOnEndRecursively(mAnim);
+ }
+
+ private void dispatchOnEndRecursively(Animator animator) {
+ for (AnimatorListener l : nonNullList(animator.getListeners())) {
+ l.onAnimationEnd(animator);
+ }
+
+ if (animator instanceof AnimatorSet) {
+ for (Animator anim : nonNullList(((AnimatorSet) animator).getChildAnimations())) {
+ dispatchOnEndRecursively(anim);
+ }
+ }
+ }
+ }
+
+ private static <T> List<T> nonNullList(ArrayList<T> list) {
+ return list == null ? Collections.<T>emptyList() : list;
+ }
+}
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index f2bad6b..bc5aafc 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -143,7 +143,7 @@
public void onAccessibilityStateChanged(boolean isAccessibilityEnabled) {
mPinchListener = FeatureFlags.LAUNCHER3_DISABLE_PINCH_TO_OVERVIEW || isAccessibilityEnabled
- || !Utilities.ATLEAST_OREO ? null : new PinchToOverviewListener(mLauncher);
+ ? null : new PinchToOverviewListener(mLauncher);
}
public boolean isEventOverHotseat(MotionEvent ev) {