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 void | applyTheme(Theme t)
|
public boolean | canApplyTheme()
|
public void | clearAnimationCallbacks()
|
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(). |
public static AnimatedVectorDrawableCompat | create(Context context, int resId)
Create a AnimatedVectorDrawableCompat object. |
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
. |
public void | draw(Canvas canvas)
|
public int | getAlpha()
|
public int | getChangingConfigurations()
|
public ColorFilter | getColorFilter()
|
public ConstantState | getConstantState()
Note that we don't support constant state when SDK < 24. |
public int | getIntrinsicHeight()
|
public int | getIntrinsicWidth()
|
public int | getOpacity()
|
public void | inflate(Resources res, XmlPullParser parser, AttributeSet attrs)
|
public void | inflate(Resources res, XmlPullParser parser, AttributeSet attrs, Theme theme)
|
public boolean | isAutoMirrored()
|
public boolean | isRunning()
|
public boolean | isStateful()
|
public Drawable | mutate()
mutate() will be effective only if the getConstantState() is returning non-null. |
protected void | onBoundsChange(Rect bounds)
|
protected boolean | onLevelChange(int level)
|
protected boolean | onStateChange(int[] state[])
|
public void | registerAnimationCallback(Animatable2Compat.AnimationCallback callback)
|
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(). |
public void | setAlpha(int alpha)
|
public void | setAutoMirrored(boolean mirrored)
|
public void | setColorFilter(ColorFilter colorFilter)
|
public void | setTint(int tint)
|
public void | setTintList(ColorStateList tint)
|
public void | setTintMode(PorterDuff.Mode tintMode)
|
public boolean | setVisible(boolean visible, boolean restart)
|
public void | start()
|
public void | stop()
|
public boolean | unregisterAnimationCallback(Animatable2Compat.AnimationCallback callback)
|
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 androidx.vectordrawable.graphics.drawable.VectorDrawableCommon | applyTheme, clearColorFilter, getCurrent, getMinimumHeight, getMinimumWidth, getPadding, getState, getTransparentRegion, jumpToCurrentState, setChangingConfigurations, setColorFilter, setFilterBitmap, setHotspot, setHotspotBounds, setState |
from java.lang.Object | clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait |
Methods
mutate() will be effective only if the getConstantState() is returning non-null.
Otherwise, it just return the current object without modification.
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 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
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
clearAnimationCallbacks()
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.
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><vector></td>
* <td>alpha</td>
* </tr>
* <tr>
* <td rowspan="7"><group></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"><path></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);
}
}
}