Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 3c436cb9 authored by Alan Viverette's avatar Alan Viverette Committed by Android (Google) Code Review
Browse files

Merge "Postpone AnimatedVectorDrawable animator inflation until applyTheme()" into mnc-dev

parents a5705a4a d8d9a75f
Loading
Loading
Loading
Loading
+208 −65
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ import android.animation.AnimatorInflater;
import android.animation.AnimatorSet;
import android.animation.Animator.AnimatorListener;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
@@ -128,15 +129,27 @@ import java.util.List;
 * @attr ref android.R.styleable#AnimatedVectorDrawableTarget_animation
 */
public class AnimatedVectorDrawable extends Drawable implements Animatable {
    private static final String LOGTAG = AnimatedVectorDrawable.class.getSimpleName();
    private static final String LOGTAG = "AnimatedVectorDrawable";

    private static final String ANIMATED_VECTOR = "animated-vector";
    private static final String TARGET = "target";

    private static final boolean DBG_ANIMATION_VECTOR_DRAWABLE = false;

    /** Local, mutable animator set. */
    private final AnimatorSet mAnimatorSet = new AnimatorSet();

    /**
     * The resources against which this drawable was created. Used to attempt
     * to inflate animators if applyTheme() doesn't get called.
     */
    private Resources mRes;

    private AnimatedVectorDrawableState mAnimatedVectorState;

    /** Whether the animator set has been prepared. */
    private boolean mHasAnimatorSet;

    private boolean mMutated;

    public AnimatedVectorDrawable() {
@@ -145,6 +158,7 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable {

    private AnimatedVectorDrawable(AnimatedVectorDrawableState state, Resources res) {
        mAnimatedVectorState = new AnimatedVectorDrawableState(state, mCallback, res);
        mRes = res;
    }

    @Override
@@ -162,7 +176,9 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable {
     */
    public void clearMutated() {
        super.clearMutated();
        if (mAnimatedVectorState.mVectorDrawable != null) {
            mAnimatedVectorState.mVectorDrawable.clearMutated();
        }
        mMutated = false;
    }

@@ -274,6 +290,7 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable {
    @Override
    public void inflate(Resources res, XmlPullParser parser, AttributeSet attrs, Theme theme)
            throws XmlPullParserException, IOException {
        final AnimatedVectorDrawableState state = mAnimatedVectorState;

        int eventType = parser.getEventType();
        float pathErrorScale = 1;
@@ -291,10 +308,10 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable {
                        vectorDrawable.setAllowCaching(false);
                        vectorDrawable.setCallback(mCallback);
                        pathErrorScale = vectorDrawable.getPixelSize();
                        if (mAnimatedVectorState.mVectorDrawable != null) {
                            mAnimatedVectorState.mVectorDrawable.setCallback(null);
                        if (state.mVectorDrawable != null) {
                            state.mVectorDrawable.setCallback(null);
                        }
                        mAnimatedVectorState.mVectorDrawable = vectorDrawable;
                        state.mVectorDrawable = vectorDrawable;
                    }
                    a.recycle();
                } else if (TARGET.equals(tagName)) {
@@ -302,13 +319,21 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable {
                            R.styleable.AnimatedVectorDrawableTarget);
                    final String target = a.getString(
                            R.styleable.AnimatedVectorDrawableTarget_name);

                    int id = a.getResourceId(
                    final int animResId = a.getResourceId(
                            R.styleable.AnimatedVectorDrawableTarget_animation, 0);
                    if (id != 0) {
                        Animator objectAnimator = AnimatorInflater.loadAnimator(res, theme, id,
                                pathErrorScale);
                        setupAnimatorsForTarget(target, objectAnimator);
                    if (animResId != 0) {
                        if (theme != null) {
                            final Animator objectAnimator = AnimatorInflater.loadAnimator(
                                    res, theme, animResId, pathErrorScale);
                            state.addTargetAnimator(target, objectAnimator);
                        } else {
                            // The animation may be theme-dependent. As a
                            // workaround until Animator has full support for
                            // applyTheme(), postpone loading the animator
                            // until we have a theme in applyTheme().
                            state.addPendingAnimator(animResId, pathErrorScale, target);

                        }
                    }
                    a.recycle();
                }
@@ -316,15 +341,10 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable {

            eventType = parser.next();
        }
        setupAnimatorSet();
    }

    private void setupAnimatorSet() {
        if (mAnimatedVectorState.mTempAnimators != null) {
            mAnimatedVectorState.mAnimatorSet.playTogether(mAnimatedVectorState.mTempAnimators);
            mAnimatedVectorState.mTempAnimators.clear();
            mAnimatedVectorState.mTempAnimators = null;
        }
        // If we don't have any pending animations, we don't need to hold a
        // reference to the resources.
        mRes = state.mPendingAnims == null ? null : res;
    }

    @Override
@@ -341,6 +361,16 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable {
        if (vectorDrawable != null && vectorDrawable.canApplyTheme()) {
            vectorDrawable.applyTheme(t);
        }

        if (t != null) {
            mAnimatedVectorState.inflatePendingAnimators(t.getResources(), t);
        }

        // If we don't have any pending animations, we don't need to hold a
        // reference to the resources.
        if (mAnimatedVectorState.mPendingAnims == null) {
            mRes = null;
        }
    }

    /**
@@ -350,7 +380,7 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable {
     * @param listener the listener to be added to the current set of listeners for this animation.
     */
    public void addListener(AnimatorListener listener) {
        mAnimatedVectorState.mAnimatorSet.addListener(listener);
        mAnimatorSet.addListener(listener);
    }

    /**
@@ -360,7 +390,7 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable {
     *                 animation.
     */
    public void removeListener(AnimatorListener listener) {
        mAnimatedVectorState.mAnimatorSet.removeListener(listener);
        mAnimatorSet.removeListener(listener);
    }

    /**
@@ -370,23 +400,27 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable {
     * @return List<AnimatorListener> The set of listeners.
     */
    public List<AnimatorListener> getListeners() {
        return mAnimatedVectorState.mAnimatorSet.getListeners();
        return mAnimatorSet.getListeners();
    }

    private static class AnimatedVectorDrawableState extends ConstantState {
        int mChangingConfigurations;
        VectorDrawable mVectorDrawable;
        // Always have a valid animatorSet to handle all the listeners call.
        AnimatorSet mAnimatorSet = new AnimatorSet();
        // When parsing the XML, we build individual animator and store in this array. At the end,
        // we add this array into the mAnimatorSet.
        private ArrayList<Animator> mTempAnimators;

        /** Animators that require a theme before inflation. */
        ArrayList<PendingAnimator> mPendingAnims;

        /** Fully inflated animators awaiting cloning into an AnimatorSet. */
        ArrayList<Animator> mAnimators;

        /** Map of animators to their target object names */
        ArrayMap<Animator, String> mTargetNameMap;

        public AnimatedVectorDrawableState(AnimatedVectorDrawableState copy,
                Callback owner, Resources res) {
            if (copy != null) {
                mChangingConfigurations = copy.mChangingConfigurations;

                if (copy.mVectorDrawable != null) {
                    final ConstantState cs = copy.mVectorDrawable.getConstantState();
                    if (res != null) {
@@ -400,24 +434,17 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable {
                    mVectorDrawable.setBounds(copy.mVectorDrawable.getBounds());
                    mVectorDrawable.setAllowCaching(false);
                }
                if (copy.mAnimatorSet != null) {
                    final int numAnimators = copy.mTargetNameMap.size();
                    // Deep copy a animator set, and then setup the target map again.
                    mAnimatorSet = copy.mAnimatorSet.clone();
                    mTargetNameMap = new ArrayMap<Animator, String>(numAnimators);
                    // Since the new AnimatorSet is cloned from the old one, the order must be the
                    // same inside the array.
                    ArrayList<Animator> oldAnim = copy.mAnimatorSet.getChildAnimations();
                    ArrayList<Animator> newAnim = mAnimatorSet.getChildAnimations();

                    for (int i = 0; i < numAnimators; ++i) {
                        // Target name must be the same for new and old
                        String targetName = copy.mTargetNameMap.get(oldAnim.get(i));
                if (copy.mAnimators != null) {
                    mAnimators = new ArrayList<>(copy.mAnimators);
                }

                        Object newTargetObject = mVectorDrawable.getTargetByName(targetName);
                        newAnim.get(i).setTarget(newTargetObject);
                        mTargetNameMap.put(newAnim.get(i), targetName);
                if (copy.mTargetNameMap != null) {
                    mTargetNameMap = new ArrayMap<>(copy.mTargetNameMap);
                }

                if (copy.mPendingAnims != null) {
                    mPendingAnims = new ArrayList<>(copy.mPendingAnims);
                }
            } else {
                mVectorDrawable = new VectorDrawable();
@@ -427,7 +454,7 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable {
        @Override
        public boolean canApplyTheme() {
            return (mVectorDrawable != null && mVectorDrawable.canApplyTheme())
                    || super.canApplyTheme();
                    || mPendingAnims != null || super.canApplyTheme();
        }

        @Override
@@ -444,44 +471,157 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable {
        public int getChangingConfigurations() {
            return mChangingConfigurations;
        }

        public void addPendingAnimator(int resId, float pathErrorScale, String target) {
            if (mPendingAnims == null) {
                mPendingAnims = new ArrayList<>(1);
            }
            mPendingAnims.add(new PendingAnimator(resId, pathErrorScale, target));
        }

    private void setupAnimatorsForTarget(String name, Animator animator) {
        Object target = mAnimatedVectorState.mVectorDrawable.getTargetByName(name);
        animator.setTarget(target);
        if (mAnimatedVectorState.mTempAnimators == null) {
            mAnimatedVectorState.mTempAnimators = new ArrayList<Animator>();
            mAnimatedVectorState.mTargetNameMap = new ArrayMap<Animator, String>();
        public void addTargetAnimator(String targetName, Animator animator) {
            if (mAnimators == null) {
                mAnimators = new ArrayList<>(1);
                mTargetNameMap = new ArrayMap<>(1);
            }
        mAnimatedVectorState.mTempAnimators.add(animator);
        mAnimatedVectorState.mTargetNameMap.put(animator, name);
            mAnimators.add(animator);
            mTargetNameMap.put(animator, targetName);

            if (DBG_ANIMATION_VECTOR_DRAWABLE) {
            Log.v(LOGTAG, "add animator  for target " + name + " " + animator);
                Log.v(LOGTAG, "add animator  for target " + targetName + " " + animator);
            }
        }

        /**
         * Prepares a local set of mutable animators based on the constant
         * state.
         * <p>
         * If there are any pending uninflated animators, attempts to inflate
         * them immediately against the provided resources object.
         *
         * @param animatorSet the animator set to which the animators should
         *                    be added
         * @param res the resources against which to inflate any pending
         *            animators, or {@code null} if not available
         */
        public void prepareLocalAnimators(@NonNull AnimatorSet animatorSet,
                @Nullable Resources res) {
            // Check for uninflated animators. We can remove this after we add
            // support for Animator.applyTheme(). See comments in inflate().
            if (mPendingAnims != null) {
                // Attempt to load animators without applying a theme.
                if (res != null) {
                    inflatePendingAnimators(res, null);
                } else {
                    Log.e(LOGTAG, "Failed to load animators. Either the AnimatedVectorDrawable"
                            + " must be created using a Resources object or applyTheme() must be"
                            + " called with a non-null Theme object.");
                }

                mPendingAnims = null;
            }

            // Perform a deep copy of the constant state's animators.
            final int count = mAnimators == null ? 0 : mAnimators.size();
            if (count > 0) {
                final Animator firstAnim = prepareLocalAnimator(0);
                final AnimatorSet.Builder builder = animatorSet.play(firstAnim);
                for (int i = 1; i < count; ++i) {
                    final Animator nextAnim = prepareLocalAnimator(i);
                    builder.with(nextAnim);
                }
            }
        }

        /**
         * Prepares a local animator for the given index within the constant
         * state's list of animators.
         *
         * @param index the index of the animator within the constant state
         */
        private Animator prepareLocalAnimator(int index) {
            final Animator animator = mAnimators.get(index);
            final Animator localAnimator = animator.clone();
            final String targetName = mTargetNameMap.get(animator);
            final Object target = mVectorDrawable.getTargetByName(targetName);
            localAnimator.setTarget(target);
            return localAnimator;
        }

        /**
         * Inflates pending animators, if any, against a theme. Clears the list of
         * pending animators.
         *
         * @param t the theme against which to inflate the animators
         */
        public void inflatePendingAnimators(@NonNull Resources res, @Nullable Theme t) {
            final ArrayList<PendingAnimator> pendingAnims = mPendingAnims;
            if (pendingAnims != null) {
                mPendingAnims = null;

                for (int i = 0, count = pendingAnims.size(); i < count; i++) {
                    final PendingAnimator pendingAnimator = pendingAnims.get(i);
                    final Animator objectAnimator = pendingAnimator.newInstance(res, t);
                    addTargetAnimator(pendingAnimator.target, objectAnimator);
                }
            }
        }

        /**
         * Basically a constant state for Animators until we actually implement
         * constant states for Animators.
         */
        private static class PendingAnimator {
            public final int animResId;
            public final float pathErrorScale;
            public final String target;

            public PendingAnimator(int animResId, float pathErrorScale, String target) {
                this.animResId = animResId;
                this.pathErrorScale = pathErrorScale;
                this.target = target;
            }

            public Animator newInstance(Resources res, Theme theme) {
                return AnimatorInflater.loadAnimator(res, theme, animResId, pathErrorScale);
            }
        }
    }

    @Override
    public boolean isRunning() {
        return mAnimatedVectorState.mAnimatorSet.isRunning();
        return mAnimatorSet.isRunning();
    }

    private boolean isStarted() {
        return mAnimatedVectorState.mAnimatorSet.isStarted();
        return mAnimatorSet.isStarted();
    }

    @Override
    public void start() {
        ensureAnimatorSet();

        // If any one of the animator has not ended, do nothing.
        if (isStarted()) {
            return;
        }
        mAnimatedVectorState.mAnimatorSet.start();

        mAnimatorSet.start();
        invalidateSelf();
    }

    @NonNull
    private void ensureAnimatorSet() {
        if (!mHasAnimatorSet) {
            mAnimatedVectorState.prepareLocalAnimators(mAnimatorSet, mRes);
            mHasAnimatorSet = true;
            mRes = null;
        }
    }

    @Override
    public void stop() {
        mAnimatedVectorState.mAnimatorSet.end();
        mAnimatorSet.end();
    }

    /**
@@ -492,20 +632,23 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable {
     * @hide
     */
    public void reverse() {
        // Only reverse when all the animators can be reverse. Otherwise, partially
        // reverse is confusing.
        ensureAnimatorSet();

        // Only reverse when all the animators can be reversed.
        if (!canReverse()) {
            Log.w(LOGTAG, "AnimatedVectorDrawable can't reverse()");
            return;
        }
        mAnimatedVectorState.mAnimatorSet.reverse();

        mAnimatorSet.reverse();
        invalidateSelf();
    }

    /**
     * @hide
     */
    public boolean canReverse() {
        return mAnimatedVectorState.mAnimatorSet.canReverse();
        return mAnimatorSet.canReverse();
    }

    private final Callback mCallback = new Callback() {