2 * Copyright (C) 2014 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 * in compliance with the License. You may obtain a copy of the License at
7 * http://www.apache.org/licenses/LICENSE-2.0
9 * Unless required by applicable law or agreed to in writing, software distributed under the License
10 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 * or implied. See the License for the specific language governing permissions and limitations under
15 package android.graphics.drawable;
17 import android.animation.Animator;
18 import android.animation.AnimatorInflater;
19 import android.animation.AnimatorListenerAdapter;
20 import android.animation.AnimatorSet;
21 import android.animation.Animator.AnimatorListener;
22 import android.animation.PropertyValuesHolder;
23 import android.animation.TimeInterpolator;
24 import android.animation.ValueAnimator;
25 import android.animation.ObjectAnimator;
26 import android.annotation.NonNull;
27 import android.annotation.Nullable;
28 import android.app.ActivityThread;
29 import android.app.Application;
30 import android.content.pm.ActivityInfo.Config;
31 import android.content.res.ColorStateList;
32 import android.content.res.Resources;
33 import android.content.res.Resources.Theme;
34 import android.content.res.TypedArray;
35 import android.graphics.Canvas;
36 import android.graphics.ColorFilter;
37 import android.graphics.Insets;
38 import android.graphics.Outline;
39 import android.graphics.PixelFormat;
40 import android.graphics.PorterDuff;
41 import android.graphics.Rect;
42 import android.os.Build;
43 import android.util.ArrayMap;
44 import android.util.AttributeSet;
45 import android.util.IntArray;
46 import android.util.Log;
47 import android.util.LongArray;
48 import android.util.PathParser;
49 import android.util.TimeUtils;
50 import android.view.Choreographer;
51 import android.view.DisplayListCanvas;
52 import android.view.RenderNode;
53 import android.view.RenderNodeAnimatorSetHelper;
54 import android.view.View;
56 import com.android.internal.R;
58 import com.android.internal.util.VirtualRefBasePtr;
59 import org.xmlpull.v1.XmlPullParser;
60 import org.xmlpull.v1.XmlPullParserException;
62 import java.io.IOException;
63 import java.lang.ref.WeakReference;
64 import java.util.ArrayList;
67 * This class uses {@link android.animation.ObjectAnimator} and
68 * {@link android.animation.AnimatorSet} to animate the properties of a
69 * {@link android.graphics.drawable.VectorDrawable} to create an animated drawable.
71 * AnimatedVectorDrawable are normally defined as 3 separate XML files.
74 * First is the XML file for {@link android.graphics.drawable.VectorDrawable}.
75 * Note that we allow the animation to happen on the group's attributes and path's
76 * attributes, which requires they are uniquely named in this XML file. Groups
77 * and paths without animations do not need names.
79 * <li>Here is a simple VectorDrawable in this vectordrawable.xml file.
81 * <vector xmlns:android="http://schemas.android.com/apk/res/android"
82 * android:height="64dp"
83 * android:width="64dp"
84 * android:viewportHeight="600"
85 * android:viewportWidth="600" >
87 * android:name="rotationGroup"
88 * android:pivotX="300.0"
89 * android:pivotY="300.0"
90 * android:rotation="45.0" >
92 * android:name="v"
93 * android:fillColor="#000000"
94 * android:pathData="M300,70 l 0,-70 70,70 0,0 -70,70z" />
99 * Second is the AnimatedVectorDrawable's XML file, which defines the target
100 * VectorDrawable, the target paths and groups to animate, the properties of the
101 * path and group to animate and the animations defined as the ObjectAnimators
104 * <li>Here is a simple AnimatedVectorDrawable defined in this avd.xml file.
105 * Note how we use the names to refer to the groups and paths in the vectordrawable.xml.
107 * <animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
108 * android:drawable="@drawable/vectordrawable" >
110 * android:name="rotationGroup"
111 * android:animation="@anim/rotation" />
113 * android:name="v"
114 * android:animation="@anim/path_morph" />
115 * </animated-vector>
118 * Last is the Animator XML file, which is the same as a normal ObjectAnimator
120 * To complete this example, here are the 2 animator files used in avd.xml:
121 * rotation.xml and path_morph.xml.
123 * <li>Here is the rotation.xml, which will rotate the target group for 360 degrees.
126 * android:duration="6000"
127 * android:propertyName="rotation"
128 * android:valueFrom="0"
129 * android:valueTo="360" />
131 * <li>Here is the path_morph.xml, which will morph the path from one shape to
132 * the other. Note that the paths must be compatible for morphing.
133 * In more details, the paths should have exact same length of commands , and
134 * exact same length of parameters for each commands.
135 * Note that the path strings are better stored in strings.xml for reusing.
137 * <set xmlns:android="http://schemas.android.com/apk/res/android">
139 * android:duration="3000"
140 * android:propertyName="pathData"
141 * android:valueFrom="M300,70 l 0,-70 70,70 0,0 -70,70z"
142 * android:valueTo="M300,70 l 0,-70 70,0 0,140 -70,0 z"
143 * android:valueType="pathType"/>
147 * @attr ref android.R.styleable#AnimatedVectorDrawable_drawable
148 * @attr ref android.R.styleable#AnimatedVectorDrawableTarget_name
149 * @attr ref android.R.styleable#AnimatedVectorDrawableTarget_animation
151 public class AnimatedVectorDrawable extends Drawable implements Animatable2 {
152 private static final String LOGTAG = "AnimatedVectorDrawable";
154 private static final String ANIMATED_VECTOR = "animated-vector";
155 private static final String TARGET = "target";
157 private static final boolean DBG_ANIMATION_VECTOR_DRAWABLE = false;
159 /** Local, mutable animator set. */
160 private VectorDrawableAnimator mAnimatorSet = new VectorDrawableAnimatorUI(this);
163 * The resources against which this drawable was created. Used to attempt
164 * to inflate animators if applyTheme() doesn't get called.
166 private Resources mRes;
168 private AnimatedVectorDrawableState mAnimatedVectorState;
170 /** The animator set that is parsed from the xml. */
171 private AnimatorSet mAnimatorSetFromXml = null;
173 private boolean mMutated;
175 /** Use a internal AnimatorListener to support callbacks during animation events. */
176 private ArrayList<Animatable2.AnimationCallback> mAnimationCallbacks = null;
177 private AnimatorListener mAnimatorListener = null;
179 public AnimatedVectorDrawable() {
183 private AnimatedVectorDrawable(AnimatedVectorDrawableState state, Resources res) {
184 mAnimatedVectorState = new AnimatedVectorDrawableState(state, mCallback, res);
189 public Drawable mutate() {
190 if (!mMutated && super.mutate() == this) {
191 mAnimatedVectorState = new AnimatedVectorDrawableState(
192 mAnimatedVectorState, mCallback, mRes);
201 public void clearMutated() {
202 super.clearMutated();
203 if (mAnimatedVectorState.mVectorDrawable != null) {
204 mAnimatedVectorState.mVectorDrawable.clearMutated();
210 * In order to avoid breaking old apps, we only throw exception on invalid VectorDrawable
211 * animations for apps targeting N and later. For older apps, we ignore (i.e. quietly skip)
214 * @return whether invalid animations for vector drawable should be ignored.
216 private static boolean shouldIgnoreInvalidAnimation() {
217 Application app = ActivityThread.currentApplication();
218 if (app == null || app.getApplicationInfo() == null) {
221 if (app.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) {
228 public ConstantState getConstantState() {
229 mAnimatedVectorState.mChangingConfigurations = getChangingConfigurations();
230 return mAnimatedVectorState;
234 public @Config int getChangingConfigurations() {
235 return super.getChangingConfigurations() | mAnimatedVectorState.getChangingConfigurations();
239 public void draw(Canvas canvas) {
240 mAnimatorSet.onDraw(canvas);
241 mAnimatedVectorState.mVectorDrawable.draw(canvas);
245 protected void onBoundsChange(Rect bounds) {
246 mAnimatedVectorState.mVectorDrawable.setBounds(bounds);
250 protected boolean onStateChange(int[] state) {
251 return mAnimatedVectorState.mVectorDrawable.setState(state);
255 protected boolean onLevelChange(int level) {
256 return mAnimatedVectorState.mVectorDrawable.setLevel(level);
260 public boolean onLayoutDirectionChanged(@View.ResolvedLayoutDir int layoutDirection) {
261 return mAnimatedVectorState.mVectorDrawable.setLayoutDirection(layoutDirection);
265 * AnimatedVectorDrawable is running on render thread now. Therefore, if the root alpha is being
266 * animated, then the root alpha value we get from this call could be out of sync with alpha
267 * value used in the render thread. Otherwise, the root alpha should be always the same value.
269 * @return the containing vector drawable's root alpha value.
272 public int getAlpha() {
273 return mAnimatedVectorState.mVectorDrawable.getAlpha();
277 public void setAlpha(int alpha) {
278 mAnimatedVectorState.mVectorDrawable.setAlpha(alpha);
282 public void setColorFilter(ColorFilter colorFilter) {
283 mAnimatedVectorState.mVectorDrawable.setColorFilter(colorFilter);
287 public ColorFilter getColorFilter() {
288 return mAnimatedVectorState.mVectorDrawable.getColorFilter();
292 public void setTintList(ColorStateList tint) {
293 mAnimatedVectorState.mVectorDrawable.setTintList(tint);
297 public void setHotspot(float x, float y) {
298 mAnimatedVectorState.mVectorDrawable.setHotspot(x, y);
302 public void setHotspotBounds(int left, int top, int right, int bottom) {
303 mAnimatedVectorState.mVectorDrawable.setHotspotBounds(left, top, right, bottom);
307 public void setTintMode(PorterDuff.Mode tintMode) {
308 mAnimatedVectorState.mVectorDrawable.setTintMode(tintMode);
312 public boolean setVisible(boolean visible, boolean restart) {
313 if (mAnimatorSet.isInfinite() && mAnimatorSet.isStarted()) {
315 // Resume the infinite animation when the drawable becomes visible again.
316 mAnimatorSet.resume();
318 // Pause the infinite animation once the drawable is no longer visible.
319 mAnimatorSet.pause();
322 mAnimatedVectorState.mVectorDrawable.setVisible(visible, restart);
323 return super.setVisible(visible, restart);
327 public boolean isStateful() {
328 return mAnimatedVectorState.mVectorDrawable.isStateful();
332 public int getOpacity() {
333 return PixelFormat.TRANSLUCENT;
337 public int getIntrinsicWidth() {
338 return mAnimatedVectorState.mVectorDrawable.getIntrinsicWidth();
342 public int getIntrinsicHeight() {
343 return mAnimatedVectorState.mVectorDrawable.getIntrinsicHeight();
347 public void getOutline(@NonNull Outline outline) {
348 mAnimatedVectorState.mVectorDrawable.getOutline(outline);
353 public Insets getOpticalInsets() {
354 return mAnimatedVectorState.mVectorDrawable.getOpticalInsets();
358 public void inflate(Resources res, XmlPullParser parser, AttributeSet attrs, Theme theme)
359 throws XmlPullParserException, IOException {
360 final AnimatedVectorDrawableState state = mAnimatedVectorState;
362 int eventType = parser.getEventType();
363 float pathErrorScale = 1;
364 while (eventType != XmlPullParser.END_DOCUMENT) {
365 if (eventType == XmlPullParser.START_TAG) {
366 final String tagName = parser.getName();
367 if (ANIMATED_VECTOR.equals(tagName)) {
368 final TypedArray a = obtainAttributes(res, theme, attrs,
369 R.styleable.AnimatedVectorDrawable);
370 int drawableRes = a.getResourceId(
371 R.styleable.AnimatedVectorDrawable_drawable, 0);
372 if (drawableRes != 0) {
373 VectorDrawable vectorDrawable = (VectorDrawable) res.getDrawable(
374 drawableRes, theme).mutate();
375 vectorDrawable.setAllowCaching(false);
376 vectorDrawable.setCallback(mCallback);
377 pathErrorScale = vectorDrawable.getPixelSize();
378 if (state.mVectorDrawable != null) {
379 state.mVectorDrawable.setCallback(null);
381 state.mVectorDrawable = vectorDrawable;
384 } else if (TARGET.equals(tagName)) {
385 final TypedArray a = obtainAttributes(res, theme, attrs,
386 R.styleable.AnimatedVectorDrawableTarget);
387 final String target = a.getString(
388 R.styleable.AnimatedVectorDrawableTarget_name);
389 final int animResId = a.getResourceId(
390 R.styleable.AnimatedVectorDrawableTarget_animation, 0);
391 if (animResId != 0) {
393 final Animator objectAnimator = AnimatorInflater.loadAnimator(
394 res, theme, animResId, pathErrorScale);
395 state.addTargetAnimator(target, objectAnimator);
397 // The animation may be theme-dependent. As a
398 // workaround until Animator has full support for
399 // applyTheme(), postpone loading the animator
400 // until we have a theme in applyTheme().
401 state.addPendingAnimator(animResId, pathErrorScale, target);
409 eventType = parser.next();
412 // If we don't have any pending animations, we don't need to hold a
413 // reference to the resources.
414 mRes = state.mPendingAnims == null ? null : res;
418 * Force to animate on UI thread.
421 public void forceAnimationOnUI() {
422 if (mAnimatorSet instanceof VectorDrawableAnimatorRT) {
423 VectorDrawableAnimatorRT animator = (VectorDrawableAnimatorRT) mAnimatorSet;
424 if (animator.isRunning()) {
425 throw new UnsupportedOperationException("Cannot force Animated Vector Drawable to" +
426 " run on UI thread when the animation has started on RenderThread.");
428 mAnimatorSet = new VectorDrawableAnimatorUI(this);
429 if (mAnimatorSetFromXml != null) {
430 mAnimatorSet.init(mAnimatorSetFromXml);
436 public boolean canApplyTheme() {
437 return (mAnimatedVectorState != null && mAnimatedVectorState.canApplyTheme())
438 || super.canApplyTheme();
442 public void applyTheme(Theme t) {
445 final VectorDrawable vectorDrawable = mAnimatedVectorState.mVectorDrawable;
446 if (vectorDrawable != null && vectorDrawable.canApplyTheme()) {
447 vectorDrawable.applyTheme(t);
451 mAnimatedVectorState.inflatePendingAnimators(t.getResources(), t);
454 // If we don't have any pending animations, we don't need to hold a
455 // reference to the resources.
456 if (mAnimatedVectorState.mPendingAnims == null) {
461 private static class AnimatedVectorDrawableState extends ConstantState {
462 @Config int mChangingConfigurations;
463 VectorDrawable mVectorDrawable;
465 /** Animators that require a theme before inflation. */
466 ArrayList<PendingAnimator> mPendingAnims;
468 /** Fully inflated animators awaiting cloning into an AnimatorSet. */
469 ArrayList<Animator> mAnimators;
471 /** Map of animators to their target object names */
472 ArrayMap<Animator, String> mTargetNameMap;
474 public AnimatedVectorDrawableState(AnimatedVectorDrawableState copy,
475 Callback owner, Resources res) {
477 mChangingConfigurations = copy.mChangingConfigurations;
479 if (copy.mVectorDrawable != null) {
480 final ConstantState cs = copy.mVectorDrawable.getConstantState();
482 mVectorDrawable = (VectorDrawable) cs.newDrawable(res);
484 mVectorDrawable = (VectorDrawable) cs.newDrawable();
486 mVectorDrawable = (VectorDrawable) mVectorDrawable.mutate();
487 mVectorDrawable.setCallback(owner);
488 mVectorDrawable.setLayoutDirection(copy.mVectorDrawable.getLayoutDirection());
489 mVectorDrawable.setBounds(copy.mVectorDrawable.getBounds());
490 mVectorDrawable.setAllowCaching(false);
493 if (copy.mAnimators != null) {
494 mAnimators = new ArrayList<>(copy.mAnimators);
497 if (copy.mTargetNameMap != null) {
498 mTargetNameMap = new ArrayMap<>(copy.mTargetNameMap);
501 if (copy.mPendingAnims != null) {
502 mPendingAnims = new ArrayList<>(copy.mPendingAnims);
505 mVectorDrawable = new VectorDrawable();
510 public boolean canApplyTheme() {
511 return (mVectorDrawable != null && mVectorDrawable.canApplyTheme())
512 || mPendingAnims != null || super.canApplyTheme();
516 public Drawable newDrawable() {
517 return new AnimatedVectorDrawable(this, null);
521 public Drawable newDrawable(Resources res) {
522 return new AnimatedVectorDrawable(this, res);
526 public @Config int getChangingConfigurations() {
527 return mChangingConfigurations;
530 public void addPendingAnimator(int resId, float pathErrorScale, String target) {
531 if (mPendingAnims == null) {
532 mPendingAnims = new ArrayList<>(1);
534 mPendingAnims.add(new PendingAnimator(resId, pathErrorScale, target));
537 public void addTargetAnimator(String targetName, Animator animator) {
538 if (mAnimators == null) {
539 mAnimators = new ArrayList<>(1);
540 mTargetNameMap = new ArrayMap<>(1);
542 mAnimators.add(animator);
543 mTargetNameMap.put(animator, targetName);
545 if (DBG_ANIMATION_VECTOR_DRAWABLE) {
546 Log.v(LOGTAG, "add animator for target " + targetName + " " + animator);
551 * Prepares a local set of mutable animators based on the constant
554 * If there are any pending uninflated animators, attempts to inflate
555 * them immediately against the provided resources object.
557 * @param animatorSet the animator set to which the animators should
559 * @param res the resources against which to inflate any pending
560 * animators, or {@code null} if not available
562 public void prepareLocalAnimators(@NonNull AnimatorSet animatorSet,
563 @Nullable Resources res) {
564 // Check for uninflated animators. We can remove this after we add
565 // support for Animator.applyTheme(). See comments in inflate().
566 if (mPendingAnims != null) {
567 // Attempt to load animators without applying a theme.
569 inflatePendingAnimators(res, null);
571 Log.e(LOGTAG, "Failed to load animators. Either the AnimatedVectorDrawable"
572 + " must be created using a Resources object or applyTheme() must be"
573 + " called with a non-null Theme object.");
576 mPendingAnims = null;
579 // Perform a deep copy of the constant state's animators.
580 final int count = mAnimators == null ? 0 : mAnimators.size();
582 final Animator firstAnim = prepareLocalAnimator(0);
583 final AnimatorSet.Builder builder = animatorSet.play(firstAnim);
584 for (int i = 1; i < count; ++i) {
585 final Animator nextAnim = prepareLocalAnimator(i);
586 builder.with(nextAnim);
592 * Prepares a local animator for the given index within the constant
593 * state's list of animators.
595 * @param index the index of the animator within the constant state
597 private Animator prepareLocalAnimator(int index) {
598 final Animator animator = mAnimators.get(index);
599 final Animator localAnimator = animator.clone();
600 final String targetName = mTargetNameMap.get(animator);
601 final Object target = mVectorDrawable.getTargetByName(targetName);
602 localAnimator.setTarget(target);
603 return localAnimator;
607 * Inflates pending animators, if any, against a theme. Clears the list of
610 * @param t the theme against which to inflate the animators
612 public void inflatePendingAnimators(@NonNull Resources res, @Nullable Theme t) {
613 final ArrayList<PendingAnimator> pendingAnims = mPendingAnims;
614 if (pendingAnims != null) {
615 mPendingAnims = null;
617 for (int i = 0, count = pendingAnims.size(); i < count; i++) {
618 final PendingAnimator pendingAnimator = pendingAnims.get(i);
619 final Animator objectAnimator = pendingAnimator.newInstance(res, t);
620 addTargetAnimator(pendingAnimator.target, objectAnimator);
626 * Basically a constant state for Animators until we actually implement
627 * constant states for Animators.
629 private static class PendingAnimator {
630 public final int animResId;
631 public final float pathErrorScale;
632 public final String target;
634 public PendingAnimator(int animResId, float pathErrorScale, String target) {
635 this.animResId = animResId;
636 this.pathErrorScale = pathErrorScale;
637 this.target = target;
640 public Animator newInstance(Resources res, Theme theme) {
641 return AnimatorInflater.loadAnimator(res, theme, animResId, pathErrorScale);
647 public boolean isRunning() {
648 return mAnimatorSet.isRunning();
652 * Resets the AnimatedVectorDrawable to the start state as specified in the animators.
654 public void reset() {
656 if (DBG_ANIMATION_VECTOR_DRAWABLE) {
657 Log.w(LOGTAG, "calling reset on AVD: " +
658 ((VectorDrawable.VectorDrawableState) ((AnimatedVectorDrawableState)
659 getConstantState()).mVectorDrawable.getConstantState()).mRootName
662 mAnimatorSet.reset();
666 public void start() {
668 if (DBG_ANIMATION_VECTOR_DRAWABLE) {
669 Log.w(LOGTAG, "calling start on AVD: " +
670 ((VectorDrawable.VectorDrawableState) ((AnimatedVectorDrawableState)
671 getConstantState()).mVectorDrawable.getConstantState()).mRootName
674 mAnimatorSet.start();
678 private void ensureAnimatorSet() {
679 if (mAnimatorSetFromXml == null) {
680 // TODO: Skip the AnimatorSet creation and init the VectorDrawableAnimator directly
681 // with a list of LocalAnimators.
682 mAnimatorSetFromXml = new AnimatorSet();
683 mAnimatedVectorState.prepareLocalAnimators(mAnimatorSetFromXml, mRes);
684 mAnimatorSet.init(mAnimatorSetFromXml);
691 if (DBG_ANIMATION_VECTOR_DRAWABLE) {
692 Log.w(LOGTAG, "calling stop on AVD: " +
693 ((VectorDrawable.VectorDrawableState) ((AnimatedVectorDrawableState)
694 getConstantState()).mVectorDrawable.getConstantState())
695 .mRootName + ", at: " + this);
701 * Reverses ongoing animations or starts pending animations in reverse.
703 * NOTE: Only works if all animations support reverse. Otherwise, this will
707 public void reverse() {
710 // Only reverse when all the animators can be reversed.
712 Log.w(LOGTAG, "AnimatedVectorDrawable can't reverse()");
716 mAnimatorSet.reverse();
722 public boolean canReverse() {
723 return mAnimatorSet.canReverse();
726 private final Callback mCallback = new Callback() {
728 public void invalidateDrawable(@NonNull Drawable who) {
733 public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) {
734 scheduleSelf(what, when);
738 public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) {
739 unscheduleSelf(what);
744 public void registerAnimationCallback(@NonNull AnimationCallback callback) {
745 if (callback == null) {
749 // Add listener accordingly.
750 if (mAnimationCallbacks == null) {
751 mAnimationCallbacks = new ArrayList<>();
754 mAnimationCallbacks.add(callback);
756 if (mAnimatorListener == null) {
757 // Create a animator listener and trigger the callback events when listener is
759 mAnimatorListener = new AnimatorListenerAdapter() {
761 public void onAnimationStart(Animator animation) {
762 ArrayList<AnimationCallback> tmpCallbacks = new ArrayList<>(mAnimationCallbacks);
763 int size = tmpCallbacks.size();
764 for (int i = 0; i < size; i ++) {
765 tmpCallbacks.get(i).onAnimationStart(AnimatedVectorDrawable.this);
770 public void onAnimationEnd(Animator animation) {
771 ArrayList<AnimationCallback> tmpCallbacks = new ArrayList<>(mAnimationCallbacks);
772 int size = tmpCallbacks.size();
773 for (int i = 0; i < size; i ++) {
774 tmpCallbacks.get(i).onAnimationEnd(AnimatedVectorDrawable.this);
779 mAnimatorSet.setListener(mAnimatorListener);
782 // A helper function to clean up the animator listener in the mAnimatorSet.
783 private void removeAnimatorSetListener() {
784 if (mAnimatorListener != null) {
785 mAnimatorSet.removeListener(mAnimatorListener);
786 mAnimatorListener = null;
791 public boolean unregisterAnimationCallback(@NonNull AnimationCallback callback) {
792 if (mAnimationCallbacks == null || callback == null) {
793 // Nothing to be removed.
796 boolean removed = mAnimationCallbacks.remove(callback);
798 // When the last call back unregistered, remove the listener accordingly.
799 if (mAnimationCallbacks.size() == 0) {
800 removeAnimatorSetListener();
806 public void clearAnimationCallbacks() {
807 removeAnimatorSetListener();
808 if (mAnimationCallbacks == null) {
812 mAnimationCallbacks.clear();
815 private interface VectorDrawableAnimator {
816 void init(@NonNull AnimatorSet set);
821 boolean canReverse();
822 void setListener(AnimatorListener listener);
823 void removeListener(AnimatorListener listener);
824 void onDraw(Canvas canvas);
827 boolean isInfinite();
832 private static class VectorDrawableAnimatorUI implements VectorDrawableAnimator {
833 // mSet is only initialized in init(). So we need to check whether it is null before any
835 private AnimatorSet mSet = null;
836 private final Drawable mDrawable;
837 // Caching the listener in the case when listener operation is called before the mSet is
839 private ArrayList<AnimatorListener> mListenerArray = null;
840 private boolean mIsInfinite = false;
842 VectorDrawableAnimatorUI(@NonNull AnimatedVectorDrawable drawable) {
843 mDrawable = drawable;
847 public void init(@NonNull AnimatorSet set) {
849 // Already initialized
850 throw new UnsupportedOperationException("VectorDrawableAnimator cannot be " +
853 // Keep a deep copy of the set, such that set can be still be constantly representing
854 // the static content from XML file.
856 mIsInfinite = mSet.getTotalDuration() == Animator.DURATION_INFINITE;
858 // If there are listeners added before calling init(), now they should be setup.
859 if (mListenerArray != null && !mListenerArray.isEmpty()) {
860 for (int i = 0; i < mListenerArray.size(); i++) {
861 mSet.addListener(mListenerArray.get(i));
863 mListenerArray.clear();
864 mListenerArray = null;
868 // Although start(), reset() and reverse() should call init() already, it is better to
869 // protect these functions from NPE in any situation.
871 public void start() {
872 if (mSet == null || mSet.isStarted()) {
876 invalidateOwningView();
888 public void reset() {
897 public void reverse() {
902 invalidateOwningView();
906 public boolean canReverse() {
907 return mSet != null && mSet.canReverse();
911 public void setListener(AnimatorListener listener) {
913 if (mListenerArray == null) {
914 mListenerArray = new ArrayList<AnimatorListener>();
916 mListenerArray.add(listener);
918 mSet.addListener(listener);
923 public void removeListener(AnimatorListener listener) {
925 if (mListenerArray == null) {
928 mListenerArray.remove(listener);
930 mSet.removeListener(listener);
935 public void onDraw(Canvas canvas) {
936 if (mSet != null && mSet.isStarted()) {
937 invalidateOwningView();
942 public boolean isStarted() {
943 return mSet != null && mSet.isStarted();
947 public boolean isRunning() {
948 return mSet != null && mSet.isRunning();
952 public boolean isInfinite() {
957 public void pause() {
965 public void resume() {
972 private void invalidateOwningView() {
973 mDrawable.invalidateSelf();
980 public static class VectorDrawableAnimatorRT implements VectorDrawableAnimator {
981 private static final int START_ANIMATION = 1;
982 private static final int REVERSE_ANIMATION = 2;
983 private static final int RESET_ANIMATION = 3;
984 private static final int END_ANIMATION = 4;
985 private AnimatorListener mListener = null;
986 private final LongArray mStartDelays = new LongArray();
987 private PropertyValuesHolder.PropertyValues mTmpValues =
988 new PropertyValuesHolder.PropertyValues();
989 private long mSetPtr = 0;
990 private boolean mContainsSequentialAnimators = false;
991 private boolean mStarted = false;
992 private boolean mInitialized = false;
993 private boolean mIsReversible = false;
994 private boolean mIsInfinite = false;
995 // This needs to be set before parsing starts.
996 private boolean mShouldIgnoreInvalidAnim;
997 // TODO: Consider using NativeAllocationRegistery to track native allocation
998 private final VirtualRefBasePtr mSetRefBasePtr;
999 private WeakReference<RenderNode> mLastSeenTarget = null;
1000 private int mLastListenerId = 0;
1001 private final IntArray mPendingAnimationActions = new IntArray();
1002 private final Drawable mDrawable;
1004 VectorDrawableAnimatorRT(AnimatedVectorDrawable drawable) {
1005 mDrawable = drawable;
1006 mSetPtr = nCreateAnimatorSet();
1007 // Increment ref count on native AnimatorSet, so it doesn't get released before Java
1008 // side is done using it.
1009 mSetRefBasePtr = new VirtualRefBasePtr(mSetPtr);
1013 public void init(@NonNull AnimatorSet set) {
1015 // Already initialized
1016 throw new UnsupportedOperationException("VectorDrawableAnimator cannot be " +
1019 mShouldIgnoreInvalidAnim = shouldIgnoreInvalidAnimation();
1020 parseAnimatorSet(set, 0);
1021 mInitialized = true;
1022 mIsInfinite = set.getTotalDuration() == Animator.DURATION_INFINITE;
1024 // Check reversible.
1025 mIsReversible = true;
1026 if (mContainsSequentialAnimators) {
1027 mIsReversible = false;
1029 // Check if there's any start delay set on child
1030 for (int i = 0; i < mStartDelays.size(); i++) {
1031 if (mStartDelays.get(i) > 0) {
1032 mIsReversible = false;
1039 private void parseAnimatorSet(AnimatorSet set, long startTime) {
1040 ArrayList<Animator> animators = set.getChildAnimations();
1042 boolean playTogether = set.shouldPlayTogether();
1043 // Convert AnimatorSet to VectorDrawableAnimatorRT
1044 for (int i = 0; i < animators.size(); i++) {
1045 Animator animator = animators.get(i);
1046 // Here we only support ObjectAnimator
1047 if (animator instanceof AnimatorSet) {
1048 parseAnimatorSet((AnimatorSet) animator, startTime);
1049 } else if (animator instanceof ObjectAnimator) {
1050 createRTAnimator((ObjectAnimator) animator, startTime);
1051 } // ignore ValueAnimators and others because they don't directly modify VD
1052 // therefore will be useless to AVD.
1054 if (!playTogether) {
1055 // Assume not play together means play sequentially
1056 startTime += animator.getTotalDuration();
1057 mContainsSequentialAnimators = true;
1062 // TODO: This method reads animation data from already parsed Animators. We need to move
1063 // this step further up the chain in the parser to avoid the detour.
1064 private void createRTAnimator(ObjectAnimator animator, long startTime) {
1065 PropertyValuesHolder[] values = animator.getValues();
1066 Object target = animator.getTarget();
1067 if (target instanceof VectorDrawable.VGroup) {
1068 createRTAnimatorForGroup(values, animator, (VectorDrawable.VGroup) target,
1070 } else if (target instanceof VectorDrawable.VPath) {
1071 for (int i = 0; i < values.length; i++) {
1072 values[i].getPropertyValues(mTmpValues);
1073 if (mTmpValues.endValue instanceof PathParser.PathData &&
1074 mTmpValues.propertyName.equals("pathData")) {
1075 createRTAnimatorForPath(animator, (VectorDrawable.VPath) target,
1077 } else if (target instanceof VectorDrawable.VFullPath) {
1078 createRTAnimatorForFullPath(animator, (VectorDrawable.VFullPath) target,
1080 } else if (!mShouldIgnoreInvalidAnim) {
1081 throw new IllegalArgumentException("ClipPath only supports PathData " +
1086 } else if (target instanceof VectorDrawable.VectorDrawableState) {
1087 createRTAnimatorForRootGroup(values, animator,
1088 (VectorDrawable.VectorDrawableState) target, startTime);
1089 } else if (!mShouldIgnoreInvalidAnim) {
1090 // Should never get here
1091 throw new UnsupportedOperationException("Target should be either VGroup, VPath, " +
1092 "or ConstantState, " + target == null ? "Null target" : target.getClass() +
1093 " is not supported");
1097 private void createRTAnimatorForGroup(PropertyValuesHolder[] values,
1098 ObjectAnimator animator, VectorDrawable.VGroup target,
1101 long nativePtr = target.getNativePtr();
1103 for (int i = 0; i < values.length; i++) {
1104 // TODO: We need to support the rare case in AVD where no start value is provided
1105 values[i].getPropertyValues(mTmpValues);
1106 propertyId = VectorDrawable.VGroup.getPropertyIndex(mTmpValues.propertyName);
1107 if (mTmpValues.type != Float.class && mTmpValues.type != float.class) {
1108 if (DBG_ANIMATION_VECTOR_DRAWABLE) {
1109 Log.e(LOGTAG, "Unsupported type: " +
1110 mTmpValues.type + ". Only float value is supported for Groups.");
1114 if (propertyId < 0) {
1115 if (DBG_ANIMATION_VECTOR_DRAWABLE) {
1116 Log.e(LOGTAG, "Unsupported property: " +
1117 mTmpValues.propertyName + " for Vector Drawable Group");
1121 long propertyPtr = nCreateGroupPropertyHolder(nativePtr, propertyId,
1122 (Float) mTmpValues.startValue, (Float) mTmpValues.endValue);
1123 if (mTmpValues.dataSource != null) {
1124 float[] dataPoints = createDataPoints(mTmpValues.dataSource, animator
1126 nSetPropertyHolderData(propertyPtr, dataPoints, dataPoints.length);
1128 createNativeChildAnimator(propertyPtr, startTime, animator);
1131 private void createRTAnimatorForPath( ObjectAnimator animator, VectorDrawable.VPath target,
1134 long nativePtr = target.getNativePtr();
1135 long startPathDataPtr = ((PathParser.PathData) mTmpValues.startValue)
1137 long endPathDataPtr = ((PathParser.PathData) mTmpValues.endValue)
1139 long propertyPtr = nCreatePathDataPropertyHolder(nativePtr, startPathDataPtr,
1141 createNativeChildAnimator(propertyPtr, startTime, animator);
1144 private void createRTAnimatorForFullPath(ObjectAnimator animator,
1145 VectorDrawable.VFullPath target, long startTime) {
1147 int propertyId = target.getPropertyIndex(mTmpValues.propertyName);
1149 long nativePtr = target.getNativePtr();
1150 if (mTmpValues.type == Float.class || mTmpValues.type == float.class) {
1151 if (propertyId < 0) {
1152 if (mShouldIgnoreInvalidAnim) {
1155 throw new IllegalArgumentException("Property: " + mTmpValues.propertyName
1156 + " is not supported for FullPath");
1159 propertyPtr = nCreatePathPropertyHolder(nativePtr, propertyId,
1160 (Float) mTmpValues.startValue, (Float) mTmpValues.endValue);
1162 } else if (mTmpValues.type == Integer.class || mTmpValues.type == int.class) {
1163 propertyPtr = nCreatePathColorPropertyHolder(nativePtr, propertyId,
1164 (Integer) mTmpValues.startValue, (Integer) mTmpValues.endValue);
1166 if (mShouldIgnoreInvalidAnim) {
1169 throw new UnsupportedOperationException("Unsupported type: " +
1170 mTmpValues.type + ". Only float, int or PathData value is " +
1171 "supported for Paths.");
1174 if (mTmpValues.dataSource != null) {
1175 float[] dataPoints = createDataPoints(mTmpValues.dataSource, animator
1177 nSetPropertyHolderData(propertyPtr, dataPoints, dataPoints.length);
1179 createNativeChildAnimator(propertyPtr, startTime, animator);
1182 private void createRTAnimatorForRootGroup(PropertyValuesHolder[] values,
1183 ObjectAnimator animator, VectorDrawable.VectorDrawableState target,
1185 long nativePtr = target.getNativeRenderer();
1186 if (!animator.getPropertyName().equals("alpha")) {
1187 if (mShouldIgnoreInvalidAnim) {
1190 throw new UnsupportedOperationException("Only alpha is supported for root "
1194 Float startValue = null;
1195 Float endValue = null;
1196 for (int i = 0; i < values.length; i++) {
1197 values[i].getPropertyValues(mTmpValues);
1198 if (mTmpValues.propertyName.equals("alpha")) {
1199 startValue = (Float) mTmpValues.startValue;
1200 endValue = (Float) mTmpValues.endValue;
1204 if (startValue == null && endValue == null) {
1205 if (mShouldIgnoreInvalidAnim) {
1208 throw new UnsupportedOperationException("No alpha values are specified");
1211 long propertyPtr = nCreateRootAlphaPropertyHolder(nativePtr, startValue, endValue);
1212 createNativeChildAnimator(propertyPtr, startTime, animator);
1215 // These are the data points that define the value of the animating properties.
1216 // e.g. translateX and translateY can animate along a Path, at any fraction in [0, 1]
1217 // a point on the path corresponds to the values of translateX and translateY.
1218 // TODO: (Optimization) We should pass the path down in native and chop it into segments
1220 private static float[] createDataPoints(
1221 PropertyValuesHolder.PropertyValues.DataSource dataSource, long duration) {
1222 long frameIntervalNanos = Choreographer.getInstance().getFrameIntervalNanos();
1223 int animIntervalMs = (int) (frameIntervalNanos / TimeUtils.NANOS_PER_MS);
1224 int numAnimFrames = (int) Math.ceil(((double) duration) / animIntervalMs);
1225 float values[] = new float[numAnimFrames];
1226 float lastFrame = numAnimFrames - 1;
1227 for (int i = 0; i < numAnimFrames; i++) {
1228 float fraction = i / lastFrame;
1229 values[i] = (Float) dataSource.getValueAtFraction(fraction);
1234 private void createNativeChildAnimator(long propertyPtr, long extraDelay,
1235 ObjectAnimator animator) {
1236 long duration = animator.getDuration();
1237 int repeatCount = animator.getRepeatCount();
1238 long startDelay = extraDelay + animator.getStartDelay();
1239 TimeInterpolator interpolator = animator.getInterpolator();
1240 long nativeInterpolator =
1241 RenderNodeAnimatorSetHelper.createNativeInterpolator(interpolator, duration);
1243 startDelay *= ValueAnimator.getDurationScale();
1244 duration *= ValueAnimator.getDurationScale();
1246 mStartDelays.add(startDelay);
1247 nAddAnimator(mSetPtr, propertyPtr, nativeInterpolator, startDelay, duration,
1252 * Holds a weak reference to the target that was last seen (through the DisplayListCanvas
1253 * in the last draw call), so that when animator set needs to start, we can add the animator
1254 * to the last seen RenderNode target and start right away.
1256 protected void recordLastSeenTarget(DisplayListCanvas canvas) {
1257 mLastSeenTarget = new WeakReference<RenderNode>(
1258 RenderNodeAnimatorSetHelper.getTarget(canvas));
1259 if (mPendingAnimationActions.size() > 0 && useLastSeenTarget()) {
1260 if (DBG_ANIMATION_VECTOR_DRAWABLE) {
1261 Log.d(LOGTAG, "Target is set in the next frame");
1263 for (int i = 0; i < mPendingAnimationActions.size(); i++) {
1264 handlePendingAction(mPendingAnimationActions.get(i));
1266 mPendingAnimationActions.clear();
1270 private void handlePendingAction(int pendingAnimationAction) {
1271 if (pendingAnimationAction == START_ANIMATION) {
1273 } else if (pendingAnimationAction == REVERSE_ANIMATION) {
1275 } else if (pendingAnimationAction == RESET_ANIMATION) {
1277 } else if (pendingAnimationAction == END_ANIMATION) {
1280 throw new UnsupportedOperationException("Animation action " +
1281 pendingAnimationAction + "is not supported");
1285 private boolean useLastSeenTarget() {
1286 if (mLastSeenTarget != null) {
1287 final RenderNode target = mLastSeenTarget.get();
1288 if (target != null && target.isAttached()) {
1289 target.addAnimator(this);
1296 private void invalidateOwningView() {
1297 mDrawable.invalidateSelf();
1300 private void addPendingAction(int pendingAnimationAction) {
1301 invalidateOwningView();
1302 mPendingAnimationActions.add(pendingAnimationAction);
1306 public void start() {
1307 if (!mInitialized) {
1311 if (useLastSeenTarget()) {
1312 if (DBG_ANIMATION_VECTOR_DRAWABLE) {
1313 Log.d(LOGTAG, "Target is set. Starting VDAnimatorSet from java");
1317 addPendingAction(START_ANIMATION);
1324 if (!mInitialized) {
1328 if (useLastSeenTarget()) {
1331 addPendingAction(END_ANIMATION);
1336 public void reset() {
1337 if (!mInitialized) {
1341 if (useLastSeenTarget()) {
1344 addPendingAction(RESET_ANIMATION);
1348 // Current (imperfect) Java AnimatorSet cannot be reversed when the set contains sequential
1349 // animators or when the animator set has a start delay
1351 public void reverse() {
1352 if (!mIsReversible || !mInitialized) {
1355 if (useLastSeenTarget()) {
1356 if (DBG_ANIMATION_VECTOR_DRAWABLE) {
1357 Log.d(LOGTAG, "Target is set. Reversing VDAnimatorSet from java");
1361 addPendingAction(REVERSE_ANIMATION);
1365 // This should only be called after animator has been added to the RenderNode target.
1366 private void startAnimation() {
1367 if (DBG_ANIMATION_VECTOR_DRAWABLE) {
1368 Log.w(LOGTAG, "starting animation on VD: " +
1369 ((VectorDrawable.VectorDrawableState) ((AnimatedVectorDrawableState)
1370 mDrawable.getConstantState()).mVectorDrawable.getConstantState())
1374 nStart(mSetPtr, this, ++mLastListenerId);
1375 invalidateOwningView();
1376 if (mListener != null) {
1377 mListener.onAnimationStart(null);
1381 // This should only be called after animator has been added to the RenderNode target.
1382 private void endAnimation() {
1383 if (DBG_ANIMATION_VECTOR_DRAWABLE) {
1384 Log.w(LOGTAG, "ending animation on VD: " +
1385 ((VectorDrawable.VectorDrawableState) ((AnimatedVectorDrawableState)
1386 mDrawable.getConstantState()).mVectorDrawable.getConstantState())
1390 invalidateOwningView();
1393 // This should only be called after animator has been added to the RenderNode target.
1394 private void resetAnimation() {
1396 invalidateOwningView();
1399 // This should only be called after animator has been added to the RenderNode target.
1400 private void reverseAnimation() {
1402 nReverse(mSetPtr, this, ++mLastListenerId);
1403 invalidateOwningView();
1404 if (mListener != null) {
1405 mListener.onAnimationStart(null);
1409 public long getAnimatorNativePtr() {
1414 public boolean canReverse() {
1415 return mIsReversible;
1419 public boolean isStarted() {
1424 public boolean isRunning() {
1425 if (!mInitialized) {
1432 public void setListener(AnimatorListener listener) {
1433 mListener = listener;
1437 public void removeListener(AnimatorListener listener) {
1442 public void onDraw(Canvas canvas) {
1443 if (canvas.isHardwareAccelerated()) {
1444 recordLastSeenTarget((DisplayListCanvas) canvas);
1449 public boolean isInfinite() {
1454 public void pause() {
1455 // TODO: Implement pause for Animator On RT.
1459 public void resume() {
1460 // TODO: Implement resume for Animator On RT.
1463 private void onAnimationEnd(int listenerId) {
1464 if (listenerId != mLastListenerId) {
1467 if (DBG_ANIMATION_VECTOR_DRAWABLE) {
1468 Log.d(LOGTAG, "on finished called from native");
1471 // Invalidate in the end of the animation to make sure the data in
1472 // RT thread is synced back to UI thread.
1473 invalidateOwningView();
1474 if (mListener != null) {
1475 mListener.onAnimationEnd(null);
1479 // onFinished: should be called from native
1480 private static void callOnFinished(VectorDrawableAnimatorRT set, int id) {
1481 set.onAnimationEnd(id);
1485 private static native long nCreateAnimatorSet();
1486 private static native void nAddAnimator(long setPtr, long propertyValuesHolder,
1487 long nativeInterpolator, long startDelay, long duration, int repeatCount);
1489 private static native long nCreateGroupPropertyHolder(long nativePtr, int propertyId,
1490 float startValue, float endValue);
1492 private static native long nCreatePathDataPropertyHolder(long nativePtr, long startValuePtr,
1494 private static native long nCreatePathColorPropertyHolder(long nativePtr, int propertyId,
1495 int startValue, int endValue);
1496 private static native long nCreatePathPropertyHolder(long nativePtr, int propertyId,
1497 float startValue, float endValue);
1498 private static native long nCreateRootAlphaPropertyHolder(long nativePtr, float startValue,
1500 private static native void nSetPropertyHolderData(long nativePtr, float[] data, int length);
1501 private static native void nStart(long animatorSetPtr, VectorDrawableAnimatorRT set, int id);
1502 private static native void nReverse(long animatorSetPtr, VectorDrawableAnimatorRT set, int id);
1503 private static native void nEnd(long animatorSetPtr);
1504 private static native void nReset(long animatorSetPtr);