summaryrefslogtreecommitdiffstats
path: root/core/java/android/animation
diff options
context:
space:
mode:
authorJohn Reck <jreck@google.com>2014-07-15 14:29:33 -0700
committerJohn Reck <jreck@google.com>2014-07-18 22:10:22 +0000
commitd3de42cae84fadfa1befd082a2cf1bf72f9ad82a (patch)
treee3bff6900caa9cc6546dc6e843f37414192fcc74 /core/java/android/animation
parentfac77c46fe03466cb4bd728da3dc49b40652964b (diff)
downloadframeworks_base-d3de42cae84fadfa1befd082a2cf1bf72f9ad82a.zip
frameworks_base-d3de42cae84fadfa1befd082a2cf1bf72f9ad82a.tar.gz
frameworks_base-d3de42cae84fadfa1befd082a2cf1bf72f9ad82a.tar.bz2
Add RT-enabled reveal animator
Bug: 16161431 Also re-writes RevealAnimator to avoid using any listeners internally, removing the logic around shadowing the update listeners. Change-Id: I6ed8126398eed971a87f20bccb7584c9acafbb6c
Diffstat (limited to 'core/java/android/animation')
-rw-r--r--core/java/android/animation/RevealAnimator.java231
-rw-r--r--core/java/android/animation/ValueAnimator.java43
2 files changed, 188 insertions, 86 deletions
diff --git a/core/java/android/animation/RevealAnimator.java b/core/java/android/animation/RevealAnimator.java
index 77a536a..a1cbd45 100644
--- a/core/java/android/animation/RevealAnimator.java
+++ b/core/java/android/animation/RevealAnimator.java
@@ -16,10 +16,9 @@
package android.animation;
+import android.view.RenderNodeAnimator;
import android.view.View;
-import java.util.ArrayList;
-
/**
* Reveals a View with an animated clipping circle.
* The clipping is implemented efficiently by talking to a private reveal API on View.
@@ -28,114 +27,178 @@ import java.util.ArrayList;
* @hide
*/
public class RevealAnimator extends ValueAnimator {
- private final static String LOGTAG = "RevealAnimator";
- private ValueAnimator.AnimatorListener mListener;
- private ValueAnimator.AnimatorUpdateListener mUpdateListener;
- private RevealCircle mReuseRevealCircle = new RevealCircle(0);
- private RevealAnimator(final View clipView, final int x, final int y,
- float startRadius, float endRadius, final boolean inverseClip) {
-
- setObjectValues(new RevealCircle(startRadius), new RevealCircle(endRadius));
- setEvaluator(new RevealCircleEvaluator(mReuseRevealCircle));
-
- mUpdateListener = new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- RevealCircle circle = (RevealCircle) animation.getAnimatedValue();
- float radius = circle.getRadius();
- clipView.setRevealClip(true, inverseClip, x, y, radius);
- }
- };
- mListener = new AnimatorListenerAdapter() {
- @Override
- public void onAnimationCancel(Animator animation) {
- clipView.setRevealClip(false, false, 0, 0, 0);
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- clipView.setRevealClip(false, false, 0, 0, 0);
- }
- };
- addUpdateListener(mUpdateListener);
- addListener(mListener);
- }
-
- public static RevealAnimator ofRevealCircle(View clipView, int x, int y,
+
+ private View mClipView;
+ private int mX, mY;
+ private boolean mInverseClip;
+ private float mStartRadius, mEndRadius;
+ private float mDelta;
+ private boolean mMayRunAsync;
+
+ // If this is null, we are running on the UI thread driven by the base
+ // ValueAnimator class. If this is not null, forward requests on to this
+ // Animator instead.
+ private RenderNodeAnimator mRtAnimator;
+
+ public RevealAnimator(View clipView, int x, int y,
float startRadius, float endRadius, boolean inverseClip) {
- RevealAnimator anim = new RevealAnimator(clipView, x, y,
- startRadius, endRadius, inverseClip);
- return anim;
+ mClipView = clipView;
+ mStartRadius = startRadius;
+ mEndRadius = endRadius;
+ mDelta = endRadius - startRadius;
+ mX = x;
+ mY = y;
+ mInverseClip = inverseClip;
+ super.setValues(PropertyValuesHolder.ofFloat("radius", startRadius, endRadius));
}
-
- /**
- * {@inheritDoc}
- */
@Override
- public void removeAllUpdateListeners() {
- super.removeAllUpdateListeners();
- addUpdateListener(mUpdateListener);
+ void animateValue(float fraction) {
+ super.animateValue(fraction);
+ fraction = getAnimatedFraction();
+ float radius = mStartRadius + (mDelta * fraction);
+ mClipView.setRevealClip(true, mInverseClip, mX, mY, radius);
}
- /**
- * {@inheritDoc}
- */
@Override
- public void removeAllListeners() {
- super.removeAllListeners();
- addListener(mListener);
+ protected void endAnimation(AnimationHandler handler) {
+ mClipView.setRevealClip(false, false, 0, 0, 0);
+ super.endAnimation(handler);
}
- /**
- * {@inheritDoc}
- */
@Override
- public ArrayList<AnimatorListener> getListeners() {
- ArrayList<AnimatorListener> allListeners =
- (ArrayList<AnimatorListener>) super.getListeners().clone();
- allListeners.remove(mListener);
- return allListeners;
+ public void setAllowRunningAsynchronously(boolean mayRunAsync) {
+ mMayRunAsync = mayRunAsync;
}
- private class RevealCircle {
- float mRadius;
+ private boolean canRunAsync() {
+ if (!mMayRunAsync) {
+ return false;
+ }
+ if (mUpdateListeners != null && mUpdateListeners.size() > 0) {
+ return false;
+ }
+ // TODO: Have RNA support this
+ if (getRepeatCount() != 0) {
+ return false;
+ }
+ return true;
+ }
- public RevealCircle(float radius) {
- mRadius = radius;
+ @Override
+ public void start() {
+ if (mRtAnimator != null) {
+ mRtAnimator.end();
+ mRtAnimator = null;
+ }
+ if (canRunAsync()) {
+ mRtAnimator = new RenderNodeAnimator(mX, mY, mInverseClip, mStartRadius, mEndRadius);
+ mRtAnimator.setDuration(getDuration());
+ mRtAnimator.setInterpolator(getInterpolator());
+ mRtAnimator.setTarget(mClipView);
+ // TODO: Listeners
+ mRtAnimator.start();
+ } else {
+ super.start();
}
+ }
- public void setRadius(float radius) {
- mRadius = radius;
+ @Override
+ public void cancel() {
+ if (mRtAnimator != null) {
+ mRtAnimator.cancel();
+ } else {
+ super.cancel();
}
+ }
- public float getRadius() {
- return mRadius;
+ @Override
+ public void end() {
+ if (mRtAnimator != null) {
+ mRtAnimator.end();
+ } else {
+ super.end();
}
}
- private class RevealCircleEvaluator implements TypeEvaluator<RevealCircle> {
+ @Override
+ public void resume() {
+ if (mRtAnimator != null) {
+ // TODO: Support? Reject?
+ } else {
+ super.resume();
+ }
+ }
- private RevealCircle mRevealCircle;
+ @Override
+ public void pause() {
+ if (mRtAnimator != null) {
+ // TODO: see resume()
+ } else {
+ super.pause();
+ }
+ }
- public RevealCircleEvaluator() {
+ @Override
+ public boolean isRunning() {
+ if (mRtAnimator != null) {
+ return mRtAnimator.isRunning();
+ } else {
+ return super.isRunning();
}
+ }
- public RevealCircleEvaluator(RevealCircle reuseCircle) {
- mRevealCircle = reuseCircle;
+ @Override
+ public boolean isStarted() {
+ if (mRtAnimator != null) {
+ return mRtAnimator.isStarted();
+ } else {
+ return super.isStarted();
}
+ }
- @Override
- public RevealCircle evaluate(float fraction, RevealCircle startValue,
- RevealCircle endValue) {
- float currentRadius = startValue.mRadius
- + ((endValue.mRadius - startValue.mRadius) * fraction);
- if (mRevealCircle == null) {
- return new RevealCircle(currentRadius);
- } else {
- mRevealCircle.setRadius(currentRadius);
- return mRevealCircle;
- }
+ @Override
+ public void reverse() {
+ if (mRtAnimator != null) {
+ // TODO support
+ } else {
+ super.reverse();
}
}
+
+ @Override
+ public ValueAnimator clone() {
+ RevealAnimator anim = (RevealAnimator) super.clone();
+ anim.mRtAnimator = null;
+ return anim;
+ }
+
+ // ----------------------------------------
+ // All the things we don't allow
+ // ----------------------------------------
+
+ @Override
+ public void setValues(PropertyValuesHolder... values) {
+ throw new IllegalStateException("Cannot change the values of RevealAnimator");
+ }
+
+ @Override
+ public void setFloatValues(float... values) {
+ throw new IllegalStateException("Cannot change the values of RevealAnimator");
+ }
+
+ @Override
+ public void setIntValues(int... values) {
+ throw new IllegalStateException("Cannot change the values of RevealAnimator");
+ }
+
+ @Override
+ public void setObjectValues(Object... values) {
+ throw new IllegalStateException("Cannot change the values of RevealAnimator");
+ }
+
+ @Override
+ public void setEvaluator(TypeEvaluator value) {
+ throw new IllegalStateException("Cannot change the evaluator of RevealAnimator");
+ }
}
diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java
index e3380a9..b13838a 100644
--- a/core/java/android/animation/ValueAnimator.java
+++ b/core/java/android/animation/ValueAnimator.java
@@ -206,7 +206,7 @@ public class ValueAnimator extends Animator {
/**
* The set of listeners to be sent events through the life of an animation.
*/
- private ArrayList<AnimatorUpdateListener> mUpdateListeners = null;
+ ArrayList<AnimatorUpdateListener> mUpdateListeners = null;
/**
* The property/value sets being animated.
@@ -1064,8 +1064,9 @@ public class ValueAnimator extends Animator {
/**
* Called internally to end an animation by removing it from the animations list. Must be
* called on the UI thread.
+ * @hide
*/
- private void endAnimation(AnimationHandler handler) {
+ protected void endAnimation(AnimationHandler handler) {
handler.mAnimations.remove(this);
handler.mPendingAnimations.remove(this);
handler.mDelayedAnims.remove(this);
@@ -1373,4 +1374,42 @@ public class ValueAnimator extends Animator {
}
return returnVal;
}
+
+ /**
+ * <p>Whether or not the ValueAnimator is allowed to run asynchronously off of
+ * the UI thread. This is a hint that informs the ValueAnimator that it is
+ * OK to run the animation off-thread, however ValueAnimator may decide
+ * that it must run the animation on the UI thread anyway. For example if there
+ * is an {@link AnimatorUpdateListener} the animation will run on the UI thread,
+ * regardless of the value of this hint.</p>
+ *
+ * <p>Regardless of whether or not the animation runs asynchronously, all
+ * listener callbacks will be called on the UI thread.</p>
+ *
+ * <p>To be able to use this hint the following must be true:</p>
+ * <ol>
+ * <li>{@link #getAnimatedFraction()} is not needed (it will return undefined values).</li>
+ * <li>The animator is immutable while {@link #isStarted()} is true. Requests
+ * to change values, duration, delay, etc... may be ignored.</li>
+ * <li>Lifecycle callback events may be asynchronous. Events such as
+ * {@link Animator.AnimatorListener#onAnimationEnd(Animator)} or
+ * {@link Animator.AnimatorListener#onAnimationRepeat(Animator)} may end up delayed
+ * as they must be posted back to the UI thread, and any actions performed
+ * by those callbacks (such as starting new animations) will not happen
+ * in the same frame.</li>
+ * <li>State change requests ({@link #cancel()}, {@link #end()}, {@link #reverse()}, etc...)
+ * may be asynchronous. It is guaranteed that all state changes that are
+ * performed on the UI thread in the same frame will be applied as a single
+ * atomic update, however that frame may be the current frame,
+ * the next frame, or some future frame. This will also impact the observed
+ * state of the Animator. For example, {@link #isStarted()} may still return true
+ * after a call to {@link #end()}. Using the lifecycle callbacks is preferred over
+ * queries to {@link #isStarted()}, {@link #isRunning()}, and {@link #isPaused()}
+ * for this reason.</li>
+ * </ol>
+ * @hide
+ */
+ public void setAllowRunningAsynchronously(boolean mayRunAsync) {
+ // It is up to subclasses to support this, if they can.
+ }
}