public class

AnimatedVectorDrawableCompat

extends androidx.vectordrawable.graphics.drawable.VectorDrawableCommon

implements Animatable2Compat

 java.lang.Object

↳Drawable

↳androidx.vectordrawable.graphics.drawable.VectorDrawableCommon

↳androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat

Gradle dependencies

compile group: 'androidx.vectordrawable', name: 'vectordrawable-animated', version: '1.2.0'

  • groupId: androidx.vectordrawable
  • artifactId: vectordrawable-animated
  • version: 1.2.0

Artifact androidx.vectordrawable:vectordrawable-animated:1.2.0 it located at Google repository (https://maven.google.com/)

Androidx artifact mapping:

androidx.vectordrawable:vectordrawable-animated com.android.support:animated-vector-drawable

Androidx class mapping:

androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat android.support.graphics.drawable.AnimatedVectorDrawableCompat

Overview

For API 24 and above, this class is delegating to the framework's . For older API version, this class uses and to animate the properties of a VectorDrawableCompat to create an animated drawable.

AnimatedVectorDrawableCompat are defined in the same XML format as .

Here are all the animatable attributes in VectorDrawableCompat:

Element Name Animatable attribute name
<vector> alpha
<group> rotation
pivotX
pivotY
scaleX
scaleY
translateX
translateY
<path> fillColor
pathData
strokeColor
strokeWidth
strokeAlpha
fillAlpha
trimPathStart
trimPathEnd
trimPathOffset

You can always create a AnimatedVectorDrawableCompat object and use it as a Drawable by the Java API. In order to refer to AnimatedVectorDrawableCompat inside a XML file, you can use app:srcCompat attribute in AppCompat library's ImageButton or ImageView.

Note that the animation in AnimatedVectorDrawableCompat now can support the following features:

  • Path Morphing (PathType evaluator). This is used for morphing one path into another.
  • Path Interpolation. This is used to defined a flexible interpolator (represented as a path) instead of the system defined ones like LinearInterpolator.
  • Animating 2 values in one ObjectAnimator according to one path's X value and Y value. One usage is moving one object in both X and Y dimensions along an path.

Summary

Methods
public voidapplyTheme(Theme t)

public booleancanApplyTheme()

public voidclearAnimationCallbacks()

public static voidclearAnimationCallbacks(Drawable dr)

Utility function to clear animation callbacks from Drawable, when the drawable is created from XML and referred in Java code, e.g: ImageView.getDrawable().

public static AnimatedVectorDrawableCompatcreate(Context context, int resId)

Create a AnimatedVectorDrawableCompat object.

public static AnimatedVectorDrawableCompatcreateFromXmlInner(Context context, Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)

Create a AnimatedVectorDrawableCompat from inside an XML document using an optional .

public voiddraw(Canvas canvas)

public intgetAlpha()

public intgetChangingConfigurations()

public ColorFiltergetColorFilter()

public ConstantStategetConstantState()

Note that we don't support constant state when SDK < 24.

public intgetIntrinsicHeight()

public intgetIntrinsicWidth()

public intgetOpacity()

public voidinflate(Resources res, XmlPullParser parser, AttributeSet attrs)

public voidinflate(Resources res, XmlPullParser parser, AttributeSet attrs, Theme theme)

public booleanisAutoMirrored()

public booleanisRunning()

public booleanisStateful()

public Drawablemutate()

mutate() will be effective only if the getConstantState() is returning non-null.

protected voidonBoundsChange(Rect bounds)

protected booleanonLevelChange(int level)

protected booleanonStateChange(int[] state[])

public voidregisterAnimationCallback(Animatable2Compat.AnimationCallback callback)

public static voidregisterAnimationCallback(Drawable dr, Animatable2Compat.AnimationCallback callback)

Utility function to register callback to Drawable, when the drawable is created from XML and referred in Java code, e.g: ImageView.getDrawable().

public voidsetAlpha(int alpha)

public voidsetAutoMirrored(boolean mirrored)

public voidsetColorFilter(ColorFilter colorFilter)

public voidsetTint(int tint)

public voidsetTintList(ColorStateList tint)

public voidsetTintMode(PorterDuff.Mode tintMode)

public booleansetVisible(boolean visible, boolean restart)

public voidstart()

public voidstop()

public booleanunregisterAnimationCallback(Animatable2Compat.AnimationCallback callback)

public static booleanunregisterAnimationCallback(Drawable dr, Animatable2Compat.AnimationCallback callback)

Utility function to unregister animation callback from Drawable, when the drawable is created from XML and referred in Java code, e.g: ImageView.getDrawable().

from androidx.vectordrawable.graphics.drawable.VectorDrawableCommonapplyTheme, clearColorFilter, getCurrent, getMinimumHeight, getMinimumWidth, getPadding, getState, getTransparentRegion, jumpToCurrentState, setChangingConfigurations, setColorFilter, setFilterBitmap, setHotspot, setHotspotBounds, setState
from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Methods

public Drawable mutate()

mutate() will be effective only if the getConstantState() is returning non-null. Otherwise, it just return the current object without modification.

public static AnimatedVectorDrawableCompat create(Context context, int resId)

Create a AnimatedVectorDrawableCompat object.

Parameters:

context: the context for creating the animators.
resId: the resource ID for AnimatedVectorDrawableCompat object.

Returns:

a new AnimatedVectorDrawableCompat or null if parsing error is found.

public static AnimatedVectorDrawableCompat createFromXmlInner(Context context, Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)

Create a AnimatedVectorDrawableCompat from inside an XML document using an optional . Called on a parser positioned at a tag in an XML document, tries to create a Drawable from that tag. Returns null if the tag is not a valid drawable.

public ConstantState getConstantState()

Note that we don't support constant state when SDK < 24. Make sure you check the return value before using it.

public int getChangingConfigurations()

public void draw(Canvas canvas)

protected void onBoundsChange(Rect bounds)

protected boolean onStateChange(int[] state[])

protected boolean onLevelChange(int level)

public int getAlpha()

public void setAlpha(int alpha)

public void setColorFilter(ColorFilter colorFilter)

public ColorFilter getColorFilter()

public void setTint(int tint)

public void setTintList(ColorStateList tint)

public void setTintMode(PorterDuff.Mode tintMode)

public boolean setVisible(boolean visible, boolean restart)

public boolean isStateful()

public int getOpacity()

public int getIntrinsicWidth()

public int getIntrinsicHeight()

public boolean isAutoMirrored()

public void setAutoMirrored(boolean mirrored)

public void inflate(Resources res, XmlPullParser parser, AttributeSet attrs, Theme theme)

public void inflate(Resources res, XmlPullParser parser, AttributeSet attrs)

public void applyTheme(Theme t)

public boolean canApplyTheme()

public boolean isRunning()

public void start()

public void stop()

public void registerAnimationCallback(Animatable2Compat.AnimationCallback callback)

public boolean unregisterAnimationCallback(Animatable2Compat.AnimationCallback callback)

public void clearAnimationCallbacks()

public static void registerAnimationCallback(Drawable dr, Animatable2Compat.AnimationCallback callback)

Utility function to register callback to Drawable, when the drawable is created from XML and referred in Java code, e.g: ImageView.getDrawable(). From API 24 on, the drawable is treated as an AnimatedVectorDrawable. Otherwise, it is treated as AnimatedVectorDrawableCompat.

public static boolean unregisterAnimationCallback(Drawable dr, Animatable2Compat.AnimationCallback callback)

Utility function to unregister animation callback from Drawable, when the drawable is created from XML and referred in Java code, e.g: ImageView.getDrawable(). From API 24 on, the drawable is treated as an AnimatedVectorDrawable. Otherwise, it is treated as AnimatedVectorDrawableCompat.

public static void clearAnimationCallbacks(Drawable dr)

Utility function to clear animation callbacks from Drawable, when the drawable is created from XML and referred in Java code, e.g: ImageView.getDrawable(). From API 24 on, the drawable is treated as an AnimatedVectorDrawable. Otherwise, it is treated as AnimatedVectorDrawableCompat.

Source

/*
 * Copyright (C) 2015 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 androidx.vectordrawable.graphics.drawable;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ArgbEvaluator;
import android.animation.ObjectAnimator;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Animatable2;
import android.graphics.drawable.AnimatedVectorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Xml;

import androidx.annotation.DoNotInline;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.collection.ArrayMap;
import androidx.core.content.res.ResourcesCompat;
import androidx.core.content.res.TypedArrayUtils;
import androidx.core.graphics.drawable.DrawableCompat;
import androidx.core.util.ObjectsCompat;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * For API 24 and above, this class is delegating to the framework's {@link
 * AnimatedVectorDrawable}.
 * For older API version, this class uses {@link ObjectAnimator} and
 * {@link AnimatorSet} to animate the properties of a
 * {@link VectorDrawableCompat} to create an animated drawable.
 * <p/>
 * AnimatedVectorDrawableCompat are defined in the same XML format as
 * {@link AnimatedVectorDrawable}.
 * <p/>
 * Here are all the animatable attributes in {@link VectorDrawableCompat}:
 * <table border="2" align="center" cellpadding="5">
 *     <thead>
 *         <tr>
 *             <th>Element Name</th>
 *             <th>Animatable attribute name</th>
 *         </tr>
 *     </thead>
 *     <tr>
 *         <td>&lt;vector&gt;</td>
 *         <td>alpha</td>
 *     </tr>
 *     <tr>
 *         <td rowspan="7">&lt;group&gt;</td>
 *         <td>rotation</td>
 *     </tr>
 *     <tr>
 *         <td>pivotX</td>
 *     </tr>
 *     <tr>
 *         <td>pivotY</td>
 *     </tr>
 *     <tr>
 *         <td>scaleX</td>
 *     </tr>
 *     <tr>
 *         <td>scaleY</td>
 *     </tr>
 *     <tr>
 *         <td>translateX</td>
 *     </tr>
 *     <tr>
 *         <td>translateY</td>
 *     </tr>
 *     <tr>
 *         <td rowspan="8">&lt;path&gt;</td>
 *         <td>fillColor</td>
 *     </tr>
 *     <tr>
 *         <td>pathData</td>
 *     </tr>
 *     <tr>
 *         <td>strokeColor</td>
 *     </tr>
 *     <tr>
 *         <td>strokeWidth</td>
 *     </tr>
 *     <tr>
 *         <td>strokeAlpha</td>
 *     </tr>
 *     <tr>
 *         <td>fillAlpha</td>
 *     </tr>
 *     <tr>
 *         <td>trimPathStart</td>
 *     </tr>
 *     <tr>
 *         <td>trimPathEnd</td>
 *     </tr>
 *     <tr>
 *         <td>trimPathOffset</td>
 *     </tr>
 * </table>
 * <p/>
 * You can always create a AnimatedVectorDrawableCompat object and use it as a Drawable by the Java
 * API. In order to refer to AnimatedVectorDrawableCompat inside a XML file, you can use
 * app:srcCompat attribute in AppCompat library's ImageButton or ImageView.
 * <p/>
 * Note that the animation in AnimatedVectorDrawableCompat now can support the following features:
 * <ul>
 * <li>Path Morphing (PathType evaluator). This is used for morphing one path into another.</li>
 * <li>Path Interpolation. This is used to defined a flexible interpolator (represented as a path)
 * instead of the system defined ones like LinearInterpolator.</li>
 * <li>Animating 2 values in one ObjectAnimator according to one path's X value and Y value. One
 * usage is moving one object in both X and Y dimensions along an path.</li>
 * </ul>
 */

public class AnimatedVectorDrawableCompat extends VectorDrawableCommon
        implements Animatable2Compat {
    private static final String LOGTAG = "AnimatedVDCompat";

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

    private static final boolean DBG_ANIMATION_VECTOR_DRAWABLE = false;

    @NonNull
    private final AnimatedVectorDrawableCompatState mAnimatedVectorState;

    private final Context mContext;

    private ArgbEvaluator mArgbEvaluator = null;

    AnimatedVectorDrawableDelegateState mCachedConstantStateDelegate;

    // Use internal listener to support AVDC's callback.
    private Animator.AnimatorListener mAnimatorListener = null;

    // Use an array to keep track of multiple call back associated with one drawable.
    ArrayList<AnimationCallback> mAnimationCallbacks = null;


    AnimatedVectorDrawableCompat() {
        this(null, null, null);
    }

    private AnimatedVectorDrawableCompat(@Nullable Context context) {
        this(context, null, null);
    }

    private AnimatedVectorDrawableCompat(@Nullable Context context,
            @Nullable AnimatedVectorDrawableCompatState state,
            @Nullable Resources res) {
        mContext = context;
        if (state != null) {
            mAnimatedVectorState = state;
        } else {
            mAnimatedVectorState = new AnimatedVectorDrawableCompatState(context, null, mCallback,
                    res);
        }
    }

    /**
     * mutate() will be effective only if the getConstantState() is returning non-null.
     * Otherwise, it just return the current object without modification.
     */
    @NonNull
    @Override
    public Drawable mutate() {
        if (mDelegateDrawable != null) {
            mDelegateDrawable.mutate();
        }
        // For older platforms that there is no delegated drawable, we just return this without
        // any modification here, and the getConstantState() will return null in this case.
        return this;
    }


    /**
     * Create a AnimatedVectorDrawableCompat object.
     *
     * @param context the context for creating the animators.
     * @param resId   the resource ID for AnimatedVectorDrawableCompat object.
     * @return a new AnimatedVectorDrawableCompat or null if parsing error is found.
     */
    @Nullable
    public static AnimatedVectorDrawableCompat create(@NonNull Context context,
            @DrawableRes int resId) {
        if (Build.VERSION.SDK_INT >= 24) {
            final AnimatedVectorDrawableCompat drawable = new AnimatedVectorDrawableCompat(context);
            final Drawable delegate = ResourcesCompat.getDrawable(context.getResources(), resId,
                    context.getTheme());
            ObjectsCompat.requireNonNull(drawable, "Failed to load drawable");
            //noinspection ConstantConditions
            delegate.setCallback(drawable.mCallback);
            drawable.mCachedConstantStateDelegate = new AnimatedVectorDrawableDelegateState(
                    delegate.getConstantState());
            drawable.mDelegateDrawable = delegate;
            return drawable;
        }
        Resources resources = context.getResources();
        try {
            //noinspection AndroidLintResourceType - Parse drawable as XML.
            final XmlPullParser parser = resources.getXml(resId);
            final AttributeSet attrs = Xml.asAttributeSet(parser);
            int type;
            //noinspection StatementWithEmptyBody
            while ((type = parser.next()) != XmlPullParser.START_TAG
                    && type != XmlPullParser.END_DOCUMENT) {
                // Empty loop
            }
            if (type != XmlPullParser.START_TAG) {
                throw new XmlPullParserException("No start tag found");
            }
            return createFromXmlInner(context, context.getResources(), parser, attrs,
                    context.getTheme());
        } catch (XmlPullParserException e) {
            Log.e(LOGTAG, "parser error", e);
        } catch (IOException e) {
            Log.e(LOGTAG, "parser error", e);
        }
        return null;
    }

    /**
     * Create a AnimatedVectorDrawableCompat from inside an XML document using an optional
     * {@link Theme}. Called on a parser positioned at a tag in an XML
     * document, tries to create a Drawable from that tag. Returns {@code null}
     * if the tag is not a valid drawable.
     */
    @NonNull
    public static AnimatedVectorDrawableCompat createFromXmlInner(@NonNull Context context,
            @NonNull Resources r, @NonNull XmlPullParser parser, @NonNull AttributeSet attrs,
            @Nullable Theme theme) throws XmlPullParserException, IOException {
        final AnimatedVectorDrawableCompat drawable = new AnimatedVectorDrawableCompat(context);
        drawable.inflate(r, parser, attrs, theme);
        return drawable;
    }

    /**
     * {@inheritDoc}
     * <strong>Note</strong> that we don't support constant state when SDK < 24.
     * Make sure you check the return value before using it.
     */
    @Nullable
    @Override
    public ConstantState getConstantState() {
        if (mDelegateDrawable != null && Build.VERSION.SDK_INT >= 24) {
            return new AnimatedVectorDrawableDelegateState(mDelegateDrawable.getConstantState());
        }
        // We can't support constant state in older platform.
        // We need Context to create the animator, and we can't save the context in the constant
        // state.
        return null;
    }

    @Override
    public int getChangingConfigurations() {
        if (mDelegateDrawable != null) {
            return mDelegateDrawable.getChangingConfigurations();
        }
        return super.getChangingConfigurations() | mAnimatedVectorState.mChangingConfigurations;
    }

    @Override
    public void draw(@NonNull Canvas canvas) {
        if (mDelegateDrawable != null) {
            mDelegateDrawable.draw(canvas);
            return;
        }
        mAnimatedVectorState.mVectorDrawable.draw(canvas);
        if (mAnimatedVectorState.mAnimatorSet.isStarted()) {
            invalidateSelf();
        }
    }

    @Override
    protected void onBoundsChange(Rect bounds) {
        if (mDelegateDrawable != null) {
            mDelegateDrawable.setBounds(bounds);
            return;
        }
        mAnimatedVectorState.mVectorDrawable.setBounds(bounds);
    }

    @Override
    protected boolean onStateChange(int[] state) {
        if (mDelegateDrawable != null) {
            return mDelegateDrawable.setState(state);
        }
        return mAnimatedVectorState.mVectorDrawable.setState(state);
    }

    @Override
    protected boolean onLevelChange(int level) {
        if (mDelegateDrawable != null) {
            return mDelegateDrawable.setLevel(level);
        }
        return mAnimatedVectorState.mVectorDrawable.setLevel(level);
    }

    @Override
    public int getAlpha() {
        if (mDelegateDrawable != null) {
            return DrawableCompat.getAlpha(mDelegateDrawable);
        }
        return mAnimatedVectorState.mVectorDrawable.getAlpha();
    }

    @Override
    public void setAlpha(int alpha) {
        if (mDelegateDrawable != null) {
            mDelegateDrawable.setAlpha(alpha);
            return;
        }
        mAnimatedVectorState.mVectorDrawable.setAlpha(alpha);
    }

    @Override
    public void setColorFilter(@Nullable ColorFilter colorFilter) {
        if (mDelegateDrawable != null) {
            mDelegateDrawable.setColorFilter(colorFilter);
            return;
        }
        mAnimatedVectorState.mVectorDrawable.setColorFilter(colorFilter);
    }

    @Nullable
    @Override
    public ColorFilter getColorFilter() {
        if (mDelegateDrawable != null) {
            return DrawableCompat.getColorFilter(mDelegateDrawable);
        }
        return mAnimatedVectorState.mVectorDrawable.getColorFilter();
    }

    @Override
    public void setTint(int tint) {
        if (mDelegateDrawable != null) {
            DrawableCompat.setTint(mDelegateDrawable, tint);
            return;
        }

        mAnimatedVectorState.mVectorDrawable.setTint(tint);
    }

    @Override
    public void setTintList(@Nullable ColorStateList tint) {
        if (mDelegateDrawable != null) {
            DrawableCompat.setTintList(mDelegateDrawable, tint);
            return;
        }

        mAnimatedVectorState.mVectorDrawable.setTintList(tint);
    }

    @Override
    public void setTintMode(@Nullable PorterDuff.Mode tintMode) {
        if (mDelegateDrawable != null) {
            DrawableCompat.setTintMode(mDelegateDrawable, tintMode);
            return;
        }

        mAnimatedVectorState.mVectorDrawable.setTintMode(tintMode);
    }

    @Override
    public boolean setVisible(boolean visible, boolean restart) {
        if (mDelegateDrawable != null) {
            return mDelegateDrawable.setVisible(visible, restart);
        }
        mAnimatedVectorState.mVectorDrawable.setVisible(visible, restart);
        return super.setVisible(visible, restart);
    }

    @Override
    public boolean isStateful() {
        if (mDelegateDrawable != null) {
            return mDelegateDrawable.isStateful();
        }
        return mAnimatedVectorState.mVectorDrawable.isStateful();
    }

    @Override
    public int getOpacity() {
        if (mDelegateDrawable != null) {
            return mDelegateDrawable.getOpacity();
        }
        return mAnimatedVectorState.mVectorDrawable.getOpacity();
    }

    @Override
    public int getIntrinsicWidth() {
        if (mDelegateDrawable != null) {
            return mDelegateDrawable.getIntrinsicWidth();
        }
        return mAnimatedVectorState.mVectorDrawable.getIntrinsicWidth();
    }

    @Override
    public int getIntrinsicHeight() {
        if (mDelegateDrawable != null) {
            return mDelegateDrawable.getIntrinsicHeight();
        }
        return mAnimatedVectorState.mVectorDrawable.getIntrinsicHeight();
    }

    @Override
    public boolean isAutoMirrored() {
        if (mDelegateDrawable != null) {
            return DrawableCompat.isAutoMirrored(mDelegateDrawable);
        }
        return mAnimatedVectorState.mVectorDrawable.isAutoMirrored();
    }

    @Override
    public void setAutoMirrored(boolean mirrored) {
        if (mDelegateDrawable != null) {
            DrawableCompat.setAutoMirrored(mDelegateDrawable, mirrored);
            return;
        }
        mAnimatedVectorState.mVectorDrawable.setAutoMirrored(mirrored);
    }

    @Override
    public void inflate(@NonNull Resources res, @NonNull XmlPullParser parser,
            @NonNull AttributeSet attrs, @Nullable Theme theme)
            throws XmlPullParserException, IOException {
        if (mDelegateDrawable != null) {
            DrawableCompat.inflate(mDelegateDrawable, res, parser, attrs, theme);
            return;
        }
        int eventType = parser.getEventType();
        final int innerDepth = parser.getDepth() + 1;

        // Parse everything until the end of the animated-vector element.
        while (eventType != XmlPullParser.END_DOCUMENT
                && (parser.getDepth() >= innerDepth || eventType != XmlPullParser.END_TAG)) {
            if (eventType == XmlPullParser.START_TAG) {
                final String tagName = parser.getName();
                if (DBG_ANIMATION_VECTOR_DRAWABLE) {
                    Log.v(LOGTAG, "tagName is " + tagName);
                }
                if (ANIMATED_VECTOR.equals(tagName)) {
                    final TypedArray a =
                            TypedArrayUtils.obtainAttributes(res, theme, attrs,
                                    AndroidResources.STYLEABLE_ANIMATED_VECTOR_DRAWABLE);

                    int drawableRes = a.getResourceId(
                            AndroidResources.STYLEABLE_ANIMATED_VECTOR_DRAWABLE_DRAWABLE, 0);
                    if (DBG_ANIMATION_VECTOR_DRAWABLE) {
                        Log.v(LOGTAG, "drawableRes is " + drawableRes);
                    }
                    if (drawableRes != 0) {
                        VectorDrawableCompat vectorDrawable = VectorDrawableCompat.create(res,
                                drawableRes, theme);
                        ObjectsCompat.requireNonNull(vectorDrawable, "Failed to load drawable");
                        vectorDrawable.setAllowCaching(false);
                        vectorDrawable.setCallback(mCallback);
                        if (mAnimatedVectorState.mVectorDrawable != null) {
                            mAnimatedVectorState.mVectorDrawable.setCallback(null);
                        }
                        mAnimatedVectorState.mVectorDrawable = vectorDrawable;
                    }
                    a.recycle();
                } else if (TARGET.equals(tagName)) {
                    final TypedArray a = res.obtainAttributes(attrs,
                                    AndroidResources.STYLEABLE_ANIMATED_VECTOR_DRAWABLE_TARGET);
                    final String target = a.getString(
                            AndroidResources.STYLEABLE_ANIMATED_VECTOR_DRAWABLE_TARGET_NAME);

                    int id = a.getResourceId(
                            AndroidResources.STYLEABLE_ANIMATED_VECTOR_DRAWABLE_TARGET_ANIMATION,
                            0);
                    if (id != 0) {
                        if (mContext != null) {
                            // There are some important features (like path morphing), added into
                            // Animator code to support AVD at API 21.
                            Animator objectAnimator = AnimatorInflaterCompat.loadAnimator(
                                    mContext, id);
                            setupAnimatorsForTarget(target, objectAnimator);
                        } else {
                            a.recycle();
                            throw new IllegalStateException("Context can't be null when inflating"
                                    + " animators");
                        }
                    }
                    a.recycle();
                }
            }
            eventType = parser.next();
        }

        mAnimatedVectorState.setupAnimatorSet();
    }

    @Override
    public void inflate(@NonNull Resources res, @NonNull XmlPullParser parser,
            @NonNull AttributeSet attrs) throws XmlPullParserException, IOException {
        inflate(res, parser, attrs, null);
    }

    @Override
    public void applyTheme(@NonNull Theme t) {
        if (mDelegateDrawable != null) {
            DrawableCompat.applyTheme(mDelegateDrawable, t);
        }
        // TODO: support theming in older platform.
    }

    @Override
    public boolean canApplyTheme() {
        if (mDelegateDrawable != null) {
            return DrawableCompat.canApplyTheme(mDelegateDrawable);
        }
        // TODO: support theming in older platform.
        return false;
    }

    /**
     * Constant state for delegating the creating drawable job.
     * Instead of creating a VectorDrawable, create a VectorDrawableCompat instance which contains
     * a delegated VectorDrawable instance.
     */
    @RequiresApi(24)
    private static class AnimatedVectorDrawableDelegateState extends ConstantState {
        private final ConstantState mDelegateState;

        AnimatedVectorDrawableDelegateState(ConstantState state) {
            mDelegateState = state;
        }

        @Override
        public Drawable newDrawable() {
            AnimatedVectorDrawableCompat drawableCompat =
                    new AnimatedVectorDrawableCompat();
            drawableCompat.mDelegateDrawable = mDelegateState.newDrawable();
            drawableCompat.mDelegateDrawable.setCallback(drawableCompat.mCallback);
            return drawableCompat;
        }

        @Override
        public Drawable newDrawable(Resources res) {
            AnimatedVectorDrawableCompat drawableCompat =
                    new AnimatedVectorDrawableCompat();
            drawableCompat.mDelegateDrawable = mDelegateState.newDrawable(res);
            drawableCompat.mDelegateDrawable.setCallback(drawableCompat.mCallback);
            return drawableCompat;
        }

        @Override
        public Drawable newDrawable(Resources res, Theme theme) {
            AnimatedVectorDrawableCompat drawableCompat =
                    new AnimatedVectorDrawableCompat();
            drawableCompat.mDelegateDrawable = mDelegateState.newDrawable(res, theme);
            drawableCompat.mDelegateDrawable.setCallback(drawableCompat.mCallback);
            return drawableCompat;
        }

        @Override
        public boolean canApplyTheme() {
            return mDelegateState.canApplyTheme();
        }

        @Override
        public int getChangingConfigurations() {
            return mDelegateState.getChangingConfigurations();
        }
    }

    private static class AnimatedVectorDrawableCompatState extends ConstantState {
        int mChangingConfigurations;
        VectorDrawableCompat mVectorDrawable;
        // Combining the array of Animators into a single AnimatorSet to hook up listener easier.
        AnimatorSet mAnimatorSet;
        ArrayList<Animator> mAnimators;
        ArrayMap<Animator, String> mTargetNameMap;

        @SuppressWarnings("unused")
        AnimatedVectorDrawableCompatState(Context context,
                AnimatedVectorDrawableCompatState copy, Callback owner, Resources res) {
            if (copy != null) {
                mChangingConfigurations = copy.mChangingConfigurations;
                if (copy.mVectorDrawable != null) {
                    final ConstantState cs = copy.mVectorDrawable.getConstantState();
                    if (res != null) {
                        mVectorDrawable = (VectorDrawableCompat) cs.newDrawable(res);
                    } else {
                        mVectorDrawable = (VectorDrawableCompat) cs.newDrawable();
                    }
                    mVectorDrawable = (VectorDrawableCompat) mVectorDrawable.mutate();
                    mVectorDrawable.setCallback(owner);
                    mVectorDrawable.setBounds(copy.mVectorDrawable.getBounds());
                    mVectorDrawable.setAllowCaching(false);
                }
                if (copy.mAnimators != null) {
                    final int numAnimators = copy.mAnimators.size();
                    mAnimators = new ArrayList<>(numAnimators);
                    mTargetNameMap = new ArrayMap<>(numAnimators);
                    for (int i = 0; i < numAnimators; ++i) {
                        Animator anim = copy.mAnimators.get(i);
                        Animator animClone = anim.clone();
                        String targetName = copy.mTargetNameMap.get(anim);
                        Object targetObject = mVectorDrawable.getTargetByName(targetName);
                        animClone.setTarget(targetObject);
                        mAnimators.add(animClone);
                        mTargetNameMap.put(animClone, targetName);
                    }
                    setupAnimatorSet();
                }
            }
        }

        @Override
        public Drawable newDrawable() {
            throw new IllegalStateException("No constant state support for SDK < 24.");
        }

        @Override
        public Drawable newDrawable(Resources res) {
            throw new IllegalStateException("No constant state support for SDK < 24.");
        }

        @Override
        public int getChangingConfigurations() {
            return mChangingConfigurations;
        }

        public void setupAnimatorSet() {
            if (mAnimatorSet == null) {
                mAnimatorSet = new AnimatorSet();
            }
            mAnimatorSet.playTogether(mAnimators);
        }
    }

    /**
     * Utility function to fix color interpolation prior to Lollipop. Without this fix, colors
     * are evaluated as raw integers instead of as colors, which leads to artifacts during
     * fillColor animations.
     */
    private void setupColorAnimator(Animator animator) {
        if (animator instanceof AnimatorSet) {
            List<Animator> childAnimators = ((AnimatorSet) animator).getChildAnimations();
            if (childAnimators != null) {
                for (int i = 0; i < childAnimators.size(); ++i) {
                    setupColorAnimator(childAnimators.get(i));
                }
            }
        }
        if (animator instanceof ObjectAnimator) {
            ObjectAnimator objectAnim = (ObjectAnimator) animator;
            final String propertyName = objectAnim.getPropertyName();
            if ("fillColor".equals(propertyName) || "strokeColor".equals(propertyName)) {
                if (mArgbEvaluator == null) {
                    mArgbEvaluator = new ArgbEvaluator();
                }
                objectAnim.setEvaluator(mArgbEvaluator);
            }
        }
    }

    private void setupAnimatorsForTarget(String name, Animator animator) {
        Object target = mAnimatedVectorState.mVectorDrawable.getTargetByName(name);
        animator.setTarget(target);
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
            setupColorAnimator(animator);
        }
        if (mAnimatedVectorState.mAnimators == null) {
            mAnimatedVectorState.mAnimators = new ArrayList<>();
            mAnimatedVectorState.mTargetNameMap = new ArrayMap<>();
        }
        mAnimatedVectorState.mAnimators.add(animator);
        mAnimatedVectorState.mTargetNameMap.put(animator, name);
        if (DBG_ANIMATION_VECTOR_DRAWABLE) {
            Log.v(LOGTAG, "add animator  for target " + name + " " + animator);
        }
    }

    @SuppressLint("NewApi") // mDelegateDrawable != null is an implicit API check
    @Override
    public boolean isRunning() {
        if (mDelegateDrawable != null) {
            return ((AnimatedVectorDrawable) mDelegateDrawable).isRunning();
        }
        return mAnimatedVectorState.mAnimatorSet.isRunning();
    }

    @SuppressLint("NewApi") // mDelegateDrawable != null is an implicit API check
    @Override
    public void start() {
        if (mDelegateDrawable != null) {
            ((AnimatedVectorDrawable) mDelegateDrawable).start();
            return;
        }
        // If any one of the animator has not ended, do nothing.
        if (mAnimatedVectorState.mAnimatorSet.isStarted()) {
            return;
        }
        // Otherwise, kick off animatorSet.
        mAnimatedVectorState.mAnimatorSet.start();
        invalidateSelf();
    }

    @SuppressLint("NewApi") // mDelegateDrawable != null is an implicit API check
    @Override
    public void stop() {
        if (mDelegateDrawable != null) {
            ((AnimatedVectorDrawable) mDelegateDrawable).stop();
            return;
        }
        mAnimatedVectorState.mAnimatorSet.end();
    }

    final Callback mCallback = new Callback() {
        @Override
        public void invalidateDrawable(Drawable who) {
            invalidateSelf();
        }

        @Override
        public void scheduleDrawable(Drawable who, Runnable what, long when) {
            scheduleSelf(what, when);
        }

        @Override
        public void unscheduleDrawable(Drawable who, Runnable what) {
            unscheduleSelf(what);
        }
    };

    /**
     * A helper function to unregister the Animatable2Compat callback from the platform's
     * Animatable2 callback, while keeping the internal array of callback up to date.
     */
    @RequiresApi(23)
    private static boolean unregisterPlatformCallback(AnimatedVectorDrawable dr,
            AnimationCallback callback) {
        return Api23Impl.unregisterAnimationCallback(dr, callback.getPlatformCallback());
    }

    @Override
    public void registerAnimationCallback(@Nullable AnimationCallback callback) {
        if (callback == null) {
            return;
        }

        if (mDelegateDrawable != null) {
            //noinspection AndroidLintNewApi - Implicit when delegate is non-null.
            registerPlatformCallback((AnimatedVectorDrawable) mDelegateDrawable, callback);
            return;
        }

        // Add listener accordingly.
        if (mAnimationCallbacks == null) {
            mAnimationCallbacks = new ArrayList<>();
        }

        if (mAnimationCallbacks.contains(callback)) {
            // If this call back is already in, then don't need to append another copy.
            return;
        }

        mAnimationCallbacks.add(callback);

        if (mAnimatorListener == null) {
            // Create a animator listener and trigger the callback events when listener is
            // triggered.
            mAnimatorListener = new AnimatorListenerAdapter() {
                @Override
                public void onAnimationStart(Animator animation) {
                    ArrayList<AnimationCallback> tmpCallbacks =
                            new ArrayList<>(mAnimationCallbacks);
                    int size = tmpCallbacks.size();
                    for (int i = 0; i < size; i++) {
                        tmpCallbacks.get(i).onAnimationStart(AnimatedVectorDrawableCompat.this);
                    }
                }

                @Override
                public void onAnimationEnd(Animator animation) {
                    ArrayList<AnimationCallback> tmpCallbacks =
                            new ArrayList<>(mAnimationCallbacks);
                    int size = tmpCallbacks.size();
                    for (int i = 0; i < size; i++) {
                        tmpCallbacks.get(i).onAnimationEnd(AnimatedVectorDrawableCompat.this);
                    }
                }
            };
        }
        mAnimatedVectorState.mAnimatorSet.addListener(mAnimatorListener);
    }

    /**
     * A helper function to register the Animatable2Compat callback on the platform's Animatable2
     * callback.
     */
    @RequiresApi(23)
    private static void registerPlatformCallback(@NonNull AnimatedVectorDrawable avd,
            @NonNull final AnimationCallback callback) {
        Api23Impl.registerAnimationCallback(avd, callback.getPlatformCallback());
    }

    /**
     * A helper function to clean up the animator listener in the mAnimatorSet.
     */
    private void removeAnimatorSetListener() {
        if (mAnimatorListener != null) {
            mAnimatedVectorState.mAnimatorSet.removeListener(mAnimatorListener);
            mAnimatorListener = null;
        }
    }

    @Override
    public boolean unregisterAnimationCallback(@Nullable AnimationCallback callback) {
        if (callback == null) {
            // Nothing to be removed.
            return false;
        }

        if (mDelegateDrawable != null) {
            //noinspection AndroidLintNewApi - Implicit when delegate is non-null.
            unregisterPlatformCallback((AnimatedVectorDrawable) mDelegateDrawable, callback);
        }

        if (mAnimationCallbacks == null) {
            // Nothing to be removed.
            return false;
        }
        boolean removed = mAnimationCallbacks.remove(callback);

        //  When the last call back unregistered, remove the listener accordingly.
        if (mAnimationCallbacks.size() == 0) {
            removeAnimatorSetListener();
        }
        return removed;
    }

    @SuppressLint("NewApi")
    @Override
    public void clearAnimationCallbacks() {
        if (mDelegateDrawable != null) {
            Api23Impl.clearAnimationCallbacks(mDelegateDrawable);
            return;
        }
        removeAnimatorSetListener();
        if (mAnimationCallbacks == null) {
            return;
        }

        mAnimationCallbacks.clear();
    }

    /**
     * Utility function to register callback to Drawable, when the drawable is created from XML and
     * referred in Java code, e.g: ImageView.getDrawable().
     * From API 24 on, the drawable is treated as an AnimatedVectorDrawable.
     * Otherwise, it is treated as AnimatedVectorDrawableCompat.
     */
    public static void registerAnimationCallback(@Nullable Drawable dr,
            @Nullable AnimationCallback callback) {
        if (dr == null || callback == null) {
            return;
        }
        if (!(dr instanceof Animatable)) {
            return;
        }

        if (Build.VERSION.SDK_INT >= 24) {
            registerPlatformCallback((AnimatedVectorDrawable) dr, callback);
        } else {
            ((AnimatedVectorDrawableCompat) dr).registerAnimationCallback(callback);
        }
    }

    /**
     * Utility function to unregister animation callback from Drawable, when the drawable is
     * created from XML and referred in Java code, e.g: ImageView.getDrawable().
     * From API 24 on, the drawable is treated as an AnimatedVectorDrawable.
     * Otherwise, it is treated as AnimatedVectorDrawableCompat.
     */
    public static boolean unregisterAnimationCallback(@Nullable Drawable dr,
            @Nullable AnimationCallback callback) {
        if (dr == null || callback == null) {
            return false;
        }
        if (!(dr instanceof Animatable)) {
            return false;
        }

        if (Build.VERSION.SDK_INT >= 24) {
            return unregisterPlatformCallback((AnimatedVectorDrawable) dr, callback);
        } else {
            return ((AnimatedVectorDrawableCompat) dr).unregisterAnimationCallback(callback);
        }
    }

    /**
     * Utility function to clear animation callbacks from Drawable, when the drawable is
     * created from XML and referred in Java code, e.g: ImageView.getDrawable().
     * From API 24 on, the drawable is treated as an AnimatedVectorDrawable.
     * Otherwise, it is treated as AnimatedVectorDrawableCompat.
     */
    public static void clearAnimationCallbacks(@Nullable Drawable dr) {
        if (!(dr instanceof Animatable)) {
            return;
        }
        if (Build.VERSION.SDK_INT >= 24) {
            Api23Impl.clearAnimationCallbacks(dr);
        } else {
            ((AnimatedVectorDrawableCompat) dr).clearAnimationCallbacks();
        }

    }

    @RequiresApi(23)
    static class Api23Impl {
        private Api23Impl() {
            // This class is not instantiable.
        }

        @DoNotInline
        static boolean unregisterAnimationCallback(Object animatedVectorDrawable,
                Object callback) {
            return ((AnimatedVectorDrawable) animatedVectorDrawable).unregisterAnimationCallback(
                    (Animatable2.AnimationCallback) callback);
        }

        @DoNotInline
        static void clearAnimationCallbacks(Object animatedVectorDrawable) {
            ((AnimatedVectorDrawable) animatedVectorDrawable).clearAnimationCallbacks();
        }

        @DoNotInline
        static void registerAnimationCallback(Object animatedVectorDrawable, Object callback) {
            ((AnimatedVectorDrawable) animatedVectorDrawable).registerAnimationCallback(
                    (Animatable2.AnimationCallback) callback);
        }
    }
}