java.lang.Object
↳Drawable
↳androidx.vectordrawable.graphics.drawable.SeekableAnimatedVectorDrawable
Gradle dependencies
compile group: 'androidx.vectordrawable', name: 'vectordrawable-seekable', version: '1.0.0'
- groupId: androidx.vectordrawable
- artifactId: vectordrawable-seekable
- version: 1.0.0
Artifact androidx.vectordrawable:vectordrawable-seekable:1.0.0 it located at Google repository (https://maven.google.com/)
Overview
This class animates properties of a VectorDrawableCompat with animations defined using
ObjectAnimator or AnimatorSet.
SeekableAnimatedVectorDrawable is 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 SeekableAnimatedVectorDrawable object and use it as a Drawable by the
Java API. In order to refer to SeekableAnimatedVectorDrawable inside an XML file, you can
use app:srcCompat attribute in AppCompat library's ImageButton or ImageView.
SeekableAnimatedVectorDrawable supports the following features too:
- 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.
Unlike AnimatedVectorDrawableCompat, this class does not delegate to the platform
on any API levels.
Summary
Methods |
---|
public void | applyTheme(Theme t)
|
public boolean | canApplyTheme()
|
public void | clearAnimationCallbacks()
Removes all existing animation callbacks. |
public static SeekableAnimatedVectorDrawable | create(Context context, int resId)
Create a SeekableAnimatedVectorDrawable object. |
public static SeekableAnimatedVectorDrawable | createFromXmlInner(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
Create a SeekableAnimatedVectorDrawable 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()
|
public long | getCurrentPlayTime()
Returns the milliseconds elapsed since the start of the animation. |
public int | getIntrinsicHeight()
|
public int | getIntrinsicWidth()
|
public int | getOpacity()
|
public long | getTotalDuration()
Gets the total duration of the animation, accounting for animation sequences, start delay,
and repeating. |
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 | isPaused()
Returns whether the animation is currently in a paused state. |
public boolean | isRunning()
Returns whether the animation is running (has started and not yet ended). |
public boolean | isStateful()
|
public Drawable | mutate()
mutate() is not supported. |
protected void | onBoundsChange(Rect bounds)
|
protected boolean | onLevelChange(int level)
|
protected boolean | onStateChange(int[] state[])
|
public void | pause()
Pauses a running animation. |
public void | registerAnimationCallback(SeekableAnimatedVectorDrawable.AnimationCallback callback)
Adds a callback to listen to the animation events. |
public void | resume()
Resumes a paused animation. |
public void | setAlpha(int alpha)
|
public void | setAutoMirrored(boolean mirrored)
|
public void | setColorFilter(ColorFilter colorFilter)
|
public void | setCurrentPlayTime(long playTime)
Sets the position of the animation to the specified point in time. |
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(SeekableAnimatedVectorDrawable.AnimationCallback callback)
Removes the specified animation callback. |
from java.lang.Object | clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait |
Methods
mutate() is not supported. This method simply returns this.
Create a SeekableAnimatedVectorDrawable object.
Parameters:
context: the context for creating the animators.
resId: the resource ID for SeekableAnimatedVectorDrawable object.
Returns:
a new SeekableAnimatedVectorDrawable or null if parsing error is found.
Create a SeekableAnimatedVectorDrawable 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()
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()
Deprecated: This method is no longer used in graphics optimizations
Returns:
The opacity class of the Drawable.
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()
Returns whether the animation is running (has started and not yet ended).
Returns:
true if the animation is running.
public boolean
isPaused()
Returns whether the animation is currently in a paused state.
Returns:
true if the animation is paused.
Pauses a running animation. This method should only be called on the same thread on which
the animation was started. If the animation has not yet been started or has since ended,
then the call is ignored. Paused animations can be resumed by calling SeekableAnimatedVectorDrawable.resume().
Resumes a paused animation. The animation resumes from where it left off when it was
paused. This method should only be called on the same thread on which the animation was
started. Calls will be ignored if this SeekableAnimatedVectorDrawable is not
currently paused.
public void
setCurrentPlayTime(long playTime)
Sets the position of the animation to the specified point in time. This time should be
between 0 and the total duration of the animation, including any repetition. If the
animation has not yet been started, then it will not advance forward after it is set to this
time; it will simply set the time to this value and perform any appropriate actions based on
that time. If the animation is already running, then setCurrentPlayTime() will set the
current playing time to this value and continue playing from that point.
Parameters:
playTime: The time, in milliseconds, to which the animation is advanced or rewound.
Unless the animation is reversing, the playtime is considered the time since
the end of the start delay of the AnimatorSet in a forward playing direction.
public long
getCurrentPlayTime()
Returns the milliseconds elapsed since the start of the animation.
For ongoing animations, this method returns the current progress of the animation in
terms of play time. For an animation that has not yet been started: if the animation has been
seeked to a certain time via SeekableAnimatedVectorDrawable.setCurrentPlayTime(long), the seeked play time will
be returned; otherwise, this method will return 0.
Returns:
the current position in time of the animation in milliseconds
public long
getTotalDuration()
Gets the total duration of the animation, accounting for animation sequences, start delay,
and repeating. Return Animator.DURATION_INFINITE if the duration is infinite.
Returns:
Total time the animation takes to finish, starting from the time SeekableAnimatedVectorDrawable.start()
is called. Animator.DURATION_INFINITE if the animation or any of the child
animations repeats infinite times.
Adds a callback to listen to the animation events.
Parameters:
callback: Callback to add.
Removes the specified animation callback.
Parameters:
callback: Callback to remove.
Returns:
false if callback didn't exist in the call back list, or true if
callback has been removed successfully.
public void
clearAnimationCallbacks()
Removes all existing animation callbacks.
Source
/*
* Copyright 2020 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.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.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Xml;
import androidx.annotation.ColorInt;
import androidx.annotation.DrawableRes;
import androidx.annotation.IntRange;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.collection.SimpleArrayMap;
import androidx.core.animation.Animator;
import androidx.core.animation.AnimatorInflater;
import androidx.core.animation.AnimatorListenerAdapter;
import androidx.core.animation.AnimatorSet;
import androidx.core.animation.ObjectAnimator;
import androidx.core.content.res.TypedArrayUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.util.ArrayList;
/**
* This class animates properties of a {@link VectorDrawableCompat} with animations defined using
* {@link ObjectAnimator} or {@link AnimatorSet}.
*
* <p>
* SeekableAnimatedVectorDrawable is defined in the same XML format as
* {@link android.graphics.drawable.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 SeekableAnimatedVectorDrawable object and use it as a Drawable by the
* Java API. In order to refer to SeekableAnimatedVectorDrawable inside an XML file, you can
* use app:srcCompat attribute in AppCompat library's ImageButton or ImageView.
* <p>
* SeekableAnimatedVectorDrawable supports the following features too:
* <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>
* <p>
* Unlike {@code AnimatedVectorDrawableCompat}, this class does not delegate to the platform
* {@link android.graphics.drawable.AnimatedVectorDrawable} on any API levels.
*/
public class SeekableAnimatedVectorDrawable extends Drawable implements Animatable {
private static final String LOGTAG = "SeekableAVD";
private static final String ANIMATED_VECTOR = "animated-vector";
private static final String TARGET = "target";
private static final boolean DBG_ANIMATION_VECTOR_DRAWABLE = false;
private AnimatedVectorDrawableState mAnimatedVectorState;
// An internal listener to bridge between Animator and SAVD's callbacks.
private InternalAnimatorListener mAnimatorListener = null;
// An array to keep track of multiple callbacks associated with one drawable.
@SuppressWarnings("WeakerAccess")
ArrayList<AnimationCallback> mAnimationCallbacks = null;
/**
* Abstract class for animation callback. Used to notify animation events.
*/
public abstract static class AnimationCallback {
/**
* Called when the animation starts.
*
* @param drawable The drawable started the animation.
*/
public void onAnimationStart(@NonNull SeekableAnimatedVectorDrawable drawable) {
}
/**
* Called when the animation ends.
*
* @param drawable The drawable finished the animation.
*/
public void onAnimationEnd(@NonNull SeekableAnimatedVectorDrawable drawable) {
}
/**
* Called when the animation is paused.
*
* @param drawable The drawable.
*/
public void onAnimationPause(@NonNull SeekableAnimatedVectorDrawable drawable) {
}
/**
* Called when the animation is resumed.
*
* @param drawable The drawable.
*/
public void onAnimationResume(@NonNull SeekableAnimatedVectorDrawable drawable) {
}
/**
* Called on every frame while the animation is running. The implementation must not
* register or unregister any {@link AnimationCallback} here.
*
* @param drawable The drawable.
*/
public void onAnimationUpdate(@NonNull SeekableAnimatedVectorDrawable drawable) {
}
}
private final Callback mCallback = new Callback() {
@Override
public void invalidateDrawable(@NonNull Drawable who) {
invalidateSelf();
}
@Override
public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) {
scheduleSelf(what, when);
}
@Override
public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) {
unscheduleSelf(what);
}
};
private SeekableAnimatedVectorDrawable() {
this(null, null);
}
private SeekableAnimatedVectorDrawable(
@Nullable AnimatedVectorDrawableState state,
@Nullable Resources res
) {
if (state != null) {
mAnimatedVectorState = state;
} else {
mAnimatedVectorState =
new AnimatedVectorDrawableState(null, mCallback, res);
}
}
/**
* mutate() is not supported. This method simply returns {@code this}.
*/
@NonNull
@Override
public Drawable mutate() {
return this;
}
/**
* Create a SeekableAnimatedVectorDrawable object.
*
* @param context the context for creating the animators.
* @param resId the resource ID for SeekableAnimatedVectorDrawable object.
* @return a new SeekableAnimatedVectorDrawable or null if parsing error is found.
*/
@Nullable
public static SeekableAnimatedVectorDrawable create(
@NonNull Context context,
@DrawableRes int resId
) {
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;
do {
type = parser.next();
} while (type != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT);
if (type != XmlPullParser.START_TAG) {
throw new XmlPullParserException("No start tag found");
}
return createFromXmlInner(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 SeekableAnimatedVectorDrawable 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 SeekableAnimatedVectorDrawable createFromXmlInner(
@NonNull Resources r,
@NonNull XmlPullParser parser,
@NonNull AttributeSet attrs,
@Nullable Theme theme
) throws XmlPullParserException, IOException {
final SeekableAnimatedVectorDrawable drawable = new SeekableAnimatedVectorDrawable();
drawable.inflate(r, parser, attrs, theme);
return drawable;
}
@Nullable
@Override
public ConstantState 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() {
return super.getChangingConfigurations() | mAnimatedVectorState.mChangingConfigurations;
}
@Override
public void draw(@NonNull Canvas canvas) {
mAnimatedVectorState.mVectorDrawable.draw(canvas);
if (mAnimatedVectorState.mAnimatorSet.isStarted()) {
invalidateSelf();
}
}
@Override
protected void onBoundsChange(@NonNull Rect bounds) {
mAnimatedVectorState.mVectorDrawable.setBounds(bounds);
}
@Override
protected boolean onStateChange(@NonNull int[] state) {
return mAnimatedVectorState.mVectorDrawable.setState(state);
}
@Override
protected boolean onLevelChange(int level) {
return mAnimatedVectorState.mVectorDrawable.setLevel(level);
}
@IntRange(from = 0, to = 255)
@Override
public int getAlpha() {
return mAnimatedVectorState.mVectorDrawable.getAlpha();
}
@Override
public void setAlpha(@IntRange(from = 0, to = 255) int alpha) {
mAnimatedVectorState.mVectorDrawable.setAlpha(alpha);
}
@Override
public void setColorFilter(@Nullable ColorFilter colorFilter) {
mAnimatedVectorState.mVectorDrawable.setColorFilter(colorFilter);
}
@Nullable
@Override
public ColorFilter getColorFilter() {
return mAnimatedVectorState.mVectorDrawable.getColorFilter();
}
@Override
public void setTint(@ColorInt int tint) {
mAnimatedVectorState.mVectorDrawable.setTint(tint);
}
@Override
public void setTintList(@Nullable ColorStateList tint) {
mAnimatedVectorState.mVectorDrawable.setTintList(tint);
}
@Override
public void setTintMode(@Nullable PorterDuff.Mode tintMode) {
mAnimatedVectorState.mVectorDrawable.setTintMode(tintMode);
}
@Override
public boolean setVisible(boolean visible, boolean restart) {
mAnimatedVectorState.mVectorDrawable.setVisible(visible, restart);
return super.setVisible(visible, restart);
}
@Override
public boolean isStateful() {
return mAnimatedVectorState.mVectorDrawable.isStateful();
}
/**
* @return The opacity class of the Drawable.
* @deprecated This method is no longer used in graphics optimizations
*/
@Deprecated
@Override
public int getOpacity() {
return mAnimatedVectorState.mVectorDrawable.getOpacity();
}
@Override
public int getIntrinsicWidth() {
return mAnimatedVectorState.mVectorDrawable.getIntrinsicWidth();
}
@Override
public int getIntrinsicHeight() {
return mAnimatedVectorState.mVectorDrawable.getIntrinsicHeight();
}
@Override
public boolean isAutoMirrored() {
return mAnimatedVectorState.mVectorDrawable.isAutoMirrored();
}
@Override
public void setAutoMirrored(boolean mirrored) {
mAnimatedVectorState.mVectorDrawable.setAutoMirrored(mirrored);
}
@Override
public void inflate(
@NonNull Resources res,
@NonNull XmlPullParser parser,
@NonNull AttributeSet attrs,
@Nullable Theme theme
) throws XmlPullParserException, IOException {
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.createWithoutDelegate(res, drawableRes, theme);
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) {
// There are some important features (like path morphing), added into
// Animator code to support AVD at API 21.
Animator objectAnimator = AnimatorInflater.loadAnimator(res, theme, id);
setupAnimatorsForTarget(target, objectAnimator);
}
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) {
// TODO(b/149342571): support theming in older platform.
}
@Override
public boolean canApplyTheme() {
// TODO(b/149342571): support theming in older platform.
return false;
}
private static class AnimatedVectorDrawableState 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;
SimpleArrayMap<Animator, String> mTargetNameMap;
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) {
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 SimpleArrayMap<>(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();
}
}
}
@NonNull
@Override
public Drawable newDrawable() {
throw new IllegalStateException("No constant state support for SDK < 24.");
}
@NonNull
@Override
public Drawable newDrawable(Resources res) {
throw new IllegalStateException("No constant state support for SDK < 24.");
}
@Override
public int getChangingConfigurations() {
return mChangingConfigurations;
}
void setupAnimatorSet() {
if (mAnimatorSet == null) {
mAnimatorSet = new AnimatorSet();
}
mAnimatorSet.playTogether(mAnimators);
}
}
private void setupAnimatorsForTarget(String name, Animator animator) {
Object target = mAnimatedVectorState.mVectorDrawable.getTargetByName(name);
animator.setTarget(target);
if (mAnimatedVectorState.mAnimators == null) {
mAnimatedVectorState.mAnimators = new ArrayList<>();
mAnimatedVectorState.mTargetNameMap = new SimpleArrayMap<>();
}
mAnimatedVectorState.mAnimators.add(animator);
mAnimatedVectorState.mTargetNameMap.put(animator, name);
if (DBG_ANIMATION_VECTOR_DRAWABLE) {
Log.v(LOGTAG, "add animator for target " + name + " " + animator);
}
}
/**
* Returns whether the animation is running (has started and not yet ended).
*
* @return {@code true} if the animation is running.
*/
@Override
public boolean isRunning() {
return mAnimatedVectorState.mAnimatorSet.isRunning();
}
/**
* Returns whether the animation is currently in a paused state.
*
* @return {@code true} if the animation is paused.
*/
public boolean isPaused() {
return mAnimatedVectorState.mAnimatorSet.isPaused();
}
@Override
public void start() {
// If any one of the animator has not ended, do nothing.
if (mAnimatedVectorState.mAnimatorSet.isStarted()) {
return;
}
// Otherwise, kick off animatorSet.
mAnimatedVectorState.mAnimatorSet.start();
invalidateSelf();
}
@Override
public void stop() {
mAnimatedVectorState.mAnimatorSet.end();
}
/**
* Pauses a running animation. This method should only be called on the same thread on which
* the animation was started. If the animation has not yet been started or has since ended,
* then the call is ignored. Paused animations can be resumed by calling {@link #resume()}.
*/
public void pause() {
mAnimatedVectorState.mAnimatorSet.pause();
}
/**
* Resumes a paused animation. The animation resumes from where it left off when it was
* paused. This method should only be called on the same thread on which the animation was
* started. Calls will be ignored if this {@link SeekableAnimatedVectorDrawable} is not
* currently paused.
*/
public void resume() {
mAnimatedVectorState.mAnimatorSet.resume();
}
/**
* Sets the position of the animation to the specified point in time. This time should be
* between 0 and the total duration of the animation, including any repetition. If the
* animation has not yet been started, then it will not advance forward after it is set to this
* time; it will simply set the time to this value and perform any appropriate actions based on
* that time. If the animation is already running, then setCurrentPlayTime() will set the
* current playing time to this value and continue playing from that point.
*
* @param playTime The time, in milliseconds, to which the animation is advanced or rewound.
* Unless the animation is reversing, the playtime is considered the time since
* the end of the start delay of the AnimatorSet in a forward playing direction.
*/
public void setCurrentPlayTime(@IntRange(from = 0) long playTime) {
mAnimatedVectorState.mAnimatorSet.setCurrentPlayTime(playTime);
invalidateSelf();
}
/**
* Returns the milliseconds elapsed since the start of the animation.
*
* <p>For ongoing animations, this method returns the current progress of the animation in
* terms of play time. For an animation that has not yet been started: if the animation has been
* seeked to a certain time via {@link #setCurrentPlayTime(long)}, the seeked play time will
* be returned; otherwise, this method will return 0.
*
* @return the current position in time of the animation in milliseconds
*/
@IntRange(from = 0)
public long getCurrentPlayTime() {
return mAnimatedVectorState.mAnimatorSet.getCurrentPlayTime();
}
/**
* Gets the total duration of the animation, accounting for animation sequences, start delay,
* and repeating. Return {@link Animator#DURATION_INFINITE} if the duration is infinite.
*
* @return Total time the animation takes to finish, starting from the time {@link #start()}
* is called. {@link Animator#DURATION_INFINITE} if the animation or any of the child
* animations repeats infinite times.
*/
public long getTotalDuration() {
return mAnimatedVectorState.mAnimatorSet.getTotalDuration();
}
class InternalAnimatorListener extends AnimatorListenerAdapter
implements Animator.AnimatorUpdateListener {
@Override
public void onAnimationStart(@NonNull Animator animation) {
final ArrayList<AnimationCallback> callbacks = mAnimationCallbacks;
if (callbacks != null) {
for (int i = 0, size = callbacks.size(); i < size; i++) {
callbacks.get(i).onAnimationStart(SeekableAnimatedVectorDrawable.this);
}
}
}
@Override
public void onAnimationEnd(@NonNull Animator animation) {
final ArrayList<AnimationCallback> callbacks = mAnimationCallbacks;
if (callbacks != null) {
for (int i = 0, size = callbacks.size(); i < size; i++) {
callbacks.get(i).onAnimationEnd(SeekableAnimatedVectorDrawable.this);
}
}
}
@Override
public void onAnimationPause(@NonNull Animator animation) {
final ArrayList<AnimationCallback> callbacks = mAnimationCallbacks;
if (callbacks != null) {
for (int i = 0, size = callbacks.size(); i < size; i++) {
callbacks.get(i).onAnimationPause(SeekableAnimatedVectorDrawable.this);
}
}
}
@Override
public void onAnimationResume(@NonNull Animator animation) {
final ArrayList<AnimationCallback> callbacks = mAnimationCallbacks;
if (callbacks != null) {
for (int i = 0, size = callbacks.size(); i < size; i++) {
callbacks.get(i).onAnimationResume(SeekableAnimatedVectorDrawable.this);
}
}
}
@Override
public void onAnimationUpdate(@NonNull Animator animation) {
final ArrayList<AnimationCallback> callbacks = mAnimationCallbacks;
if (callbacks != null) {
for (int i = 0, size = callbacks.size(); i < size; i++) {
callbacks.get(i).onAnimationUpdate(SeekableAnimatedVectorDrawable.this);
}
}
}
}
/**
* Adds a callback to listen to the animation events.
*
* @param callback Callback to add.
*/
public void registerAnimationCallback(@NonNull AnimationCallback callback) {
// Add listener accordingly.
if (mAnimationCallbacks == null) {
mAnimationCallbacks = new ArrayList<>();
} else if (mAnimationCallbacks.contains(callback)) {
// If this call back is already in, then don't need to append another copy.
return;
} else {
mAnimationCallbacks = new ArrayList<>(mAnimationCallbacks);
}
mAnimationCallbacks.add(callback);
if (mAnimatorListener == null) {
// Create an internal listener in order to bridge events to our callbacks.
mAnimatorListener = new InternalAnimatorListener();
mAnimatedVectorState.mAnimatorSet.addListener(mAnimatorListener);
mAnimatedVectorState.mAnimatorSet.addPauseListener(mAnimatorListener);
mAnimatedVectorState.mAnimatorSet.addUpdateListener(mAnimatorListener);
}
}
/**
* A helper function to clean up the animator listener in the mAnimatorSet.
*/
private void removeAnimatorSetListener() {
if (mAnimatorListener != null) {
mAnimatedVectorState.mAnimatorSet.removeListener(mAnimatorListener);
mAnimatedVectorState.mAnimatorSet.removePauseListener(mAnimatorListener);
mAnimatedVectorState.mAnimatorSet.removeUpdateListener(mAnimatorListener);
mAnimatorListener = null;
}
}
/**
* Removes the specified animation callback.
*
* @param callback Callback to remove.
* @return {@code false} if callback didn't exist in the call back list, or {@code true} if
* callback has been removed successfully.
*/
public boolean unregisterAnimationCallback(@NonNull AnimationCallback callback) {
if (mAnimationCallbacks == null) {
// Nothing to be removed.
return false;
}
boolean removed = false;
if (mAnimationCallbacks.contains(callback)) {
mAnimationCallbacks = new ArrayList<>(mAnimationCallbacks);
mAnimationCallbacks.remove(callback);
removed = true;
}
// When the last call back unregistered, remove the listener accordingly.
if (mAnimationCallbacks.isEmpty()) {
removeAnimatorSetListener();
}
return removed;
}
/**
* Removes all existing animation callbacks.
*/
public void clearAnimationCallbacks() {
removeAnimatorSetListener();
if (mAnimationCallbacks == null) {
return;
}
mAnimationCallbacks.clear();
}
}