Home | History | Annotate | Download | only in drawable
      1 /*
      2  * Copyright (C) 2014 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
      5  * in compliance with the License. You may obtain a copy of the License at
      6  *
      7  * http://www.apache.org/licenses/LICENSE-2.0
      8  *
      9  * Unless required by applicable law or agreed to in writing, software distributed under the License
     10  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
     11  * or implied. See the License for the specific language governing permissions and limitations under
     12  * the License.
     13  */
     14 
     15 package android.graphics.drawable;
     16 
     17 import android.animation.Animator;
     18 import android.animation.AnimatorInflater;
     19 import android.annotation.NonNull;
     20 import android.content.res.ColorStateList;
     21 import android.content.res.Resources;
     22 import android.content.res.Resources.Theme;
     23 import android.content.res.TypedArray;
     24 import android.graphics.Canvas;
     25 import android.graphics.ColorFilter;
     26 import android.graphics.Outline;
     27 import android.graphics.PorterDuff;
     28 import android.graphics.Rect;
     29 import android.util.ArrayMap;
     30 import android.util.AttributeSet;
     31 import android.util.Log;
     32 
     33 import com.android.internal.R;
     34 
     35 import org.xmlpull.v1.XmlPullParser;
     36 import org.xmlpull.v1.XmlPullParserException;
     37 
     38 import java.io.IOException;
     39 import java.util.ArrayList;
     40 
     41 /**
     42  * This class uses {@link android.animation.ObjectAnimator} and
     43  * {@link android.animation.AnimatorSet} to animate the properties of a
     44  * {@link android.graphics.drawable.VectorDrawable} to create an animated drawable.
     45  * <p>
     46  * AnimatedVectorDrawable are normally defined as 3 separate XML files.
     47  * </p>
     48  * <p>
     49  * First is the XML file for {@link android.graphics.drawable.VectorDrawable}.
     50  * Note that we allow the animation happen on the group's attributes and path's
     51  * attributes, which requires they are uniquely named in this xml file. Groups
     52  * and paths without animations do not need names.
     53  * </p>
     54  * <li>Here is a simple VectorDrawable in this vectordrawable.xml file.
     55  * <pre>
     56  * &lt;vector xmlns:android=&quot;http://schemas.android.com/apk/res/android";
     57  *     android:height=&quot;64dp&quot;
     58  *     android:width=&quot;64dp&quot;
     59  *     android:viewportHeight=&quot;600&quot;
     60  *     android:viewportWidth=&quot;600&quot; &gt;
     61  *     &lt;group
     62  *         android:name=&quot;rotationGroup&quot;
     63  *         android:pivotX=&quot;300.0&quot;
     64  *         android:pivotY=&quot;300.0&quot;
     65  *         android:rotation=&quot;45.0&quot; &gt;
     66  *         &lt;path
     67  *             android:name=&quot;v&quot;
     68  *             android:fillColor=&quot;#000000&quot;
     69  *             android:pathData=&quot;M300,70 l 0,-70 70,70 0,0 -70,70z&quot; /&gt;
     70  *     &lt;/group&gt;
     71  * &lt;/vector&gt;
     72  * </pre></li>
     73  * <p>
     74  * Second is the AnimatedVectorDrawable's xml file, which defines the target
     75  * VectorDrawable, the target paths and groups to animate, the properties of the
     76  * path and group to animate and the animations defined as the ObjectAnimators
     77  * or AnimatorSets.
     78  * </p>
     79  * <li>Here is a simple AnimatedVectorDrawable defined in this avd.xml file.
     80  * Note how we use the names to refer to the groups and paths in the vectordrawable.xml.
     81  * <pre>
     82  * &lt;animated-vector xmlns:android=&quot;http://schemas.android.com/apk/res/android";
     83  *   android:drawable=&quot;@drawable/vectordrawable&quot; &gt;
     84  *     &lt;target
     85  *         android:name=&quot;rotationGroup&quot;
     86  *         android:animation=&quot;@anim/rotation&quot; /&gt;
     87  *     &lt;target
     88  *         android:name=&quot;v&quot;
     89  *         android:animation=&quot;@anim/path_morph&quot; /&gt;
     90  * &lt;/animated-vector&gt;
     91  * </pre></li>
     92  * <p>
     93  * Last is the Animator xml file, which is the same as a normal ObjectAnimator
     94  * or AnimatorSet.
     95  * To complete this example, here are the 2 animator files used in avd.xml:
     96  * rotation.xml and path_morph.xml.
     97  * </p>
     98  * <li>Here is the rotation.xml, which will rotate the target group for 360 degrees.
     99  * <pre>
    100  * &lt;objectAnimator
    101  *     android:duration=&quot;6000&quot;
    102  *     android:propertyName=&quot;rotation&quot;
    103  *     android:valueFrom=&quot;0&quot;
    104  *     android:valueTo=&quot;360&quot; /&gt;
    105  * </pre></li>
    106  * <li>Here is the path_morph.xml, which will morph the path from one shape to
    107  * the other. Note that the paths must be compatible for morphing.
    108  * In more details, the paths should have exact same length of commands , and
    109  * exact same length of parameters for each commands.
    110  * Note that the path string are better stored in strings.xml for reusing.
    111  * <pre>
    112  * &lt;set xmlns:android=&quot;http://schemas.android.com/apk/res/android">;
    113  *     &lt;objectAnimator
    114  *         android:duration=&quot;3000&quot;
    115  *         android:propertyName=&quot;pathData&quot;
    116  *         android:valueFrom=&quot;M300,70 l 0,-70 70,70 0,0   -70,70z&quot;
    117  *         android:valueTo=&quot;M300,70 l 0,-70 70,0  0,140 -70,0 z&quot;
    118  *         android:valueType=&quot;pathType&quot;/&gt;
    119  * &lt;/set&gt;
    120  * </pre></li>
    121  *
    122  * @attr ref android.R.styleable#AnimatedVectorDrawable_drawable
    123  * @attr ref android.R.styleable#AnimatedVectorDrawableTarget_name
    124  * @attr ref android.R.styleable#AnimatedVectorDrawableTarget_animation
    125  */
    126 public class AnimatedVectorDrawable extends Drawable implements Animatable {
    127     private static final String LOGTAG = AnimatedVectorDrawable.class.getSimpleName();
    128 
    129     private static final String ANIMATED_VECTOR = "animated-vector";
    130     private static final String TARGET = "target";
    131 
    132     private static final boolean DBG_ANIMATION_VECTOR_DRAWABLE = false;
    133 
    134     private AnimatedVectorDrawableState mAnimatedVectorState;
    135 
    136     private boolean mMutated;
    137 
    138     public AnimatedVectorDrawable() {
    139         this(null, null);
    140     }
    141 
    142     private AnimatedVectorDrawable(AnimatedVectorDrawableState state, Resources res) {
    143         mAnimatedVectorState = new AnimatedVectorDrawableState(state, mCallback, res);
    144     }
    145 
    146     @Override
    147     public Drawable mutate() {
    148         if (!mMutated && super.mutate() == this) {
    149             mAnimatedVectorState = new AnimatedVectorDrawableState(
    150                     mAnimatedVectorState, mCallback, null);
    151             mMutated = true;
    152         }
    153         return this;
    154     }
    155 
    156     /**
    157      * @hide
    158      */
    159     public void clearMutated() {
    160         super.clearMutated();
    161         mAnimatedVectorState.mVectorDrawable.clearMutated();
    162         mMutated = false;
    163     }
    164 
    165     @Override
    166     public ConstantState getConstantState() {
    167         mAnimatedVectorState.mChangingConfigurations = getChangingConfigurations();
    168         return mAnimatedVectorState;
    169     }
    170 
    171     @Override
    172     public int getChangingConfigurations() {
    173         return super.getChangingConfigurations() | mAnimatedVectorState.mChangingConfigurations;
    174     }
    175 
    176     @Override
    177     public void draw(Canvas canvas) {
    178         mAnimatedVectorState.mVectorDrawable.draw(canvas);
    179         if (isStarted()) {
    180             invalidateSelf();
    181         }
    182     }
    183 
    184     @Override
    185     protected void onBoundsChange(Rect bounds) {
    186         mAnimatedVectorState.mVectorDrawable.setBounds(bounds);
    187     }
    188 
    189     @Override
    190     protected boolean onStateChange(int[] state) {
    191         return mAnimatedVectorState.mVectorDrawable.setState(state);
    192     }
    193 
    194     @Override
    195     protected boolean onLevelChange(int level) {
    196         return mAnimatedVectorState.mVectorDrawable.setLevel(level);
    197     }
    198 
    199     @Override
    200     public int getAlpha() {
    201         return mAnimatedVectorState.mVectorDrawable.getAlpha();
    202     }
    203 
    204     @Override
    205     public void setAlpha(int alpha) {
    206         mAnimatedVectorState.mVectorDrawable.setAlpha(alpha);
    207     }
    208 
    209     @Override
    210     public void setColorFilter(ColorFilter colorFilter) {
    211         mAnimatedVectorState.mVectorDrawable.setColorFilter(colorFilter);
    212     }
    213 
    214     @Override
    215     public void setTintList(ColorStateList tint) {
    216         mAnimatedVectorState.mVectorDrawable.setTintList(tint);
    217     }
    218 
    219     @Override
    220     public void setHotspot(float x, float y) {
    221         mAnimatedVectorState.mVectorDrawable.setHotspot(x, y);
    222     }
    223 
    224     @Override
    225     public void setHotspotBounds(int left, int top, int right, int bottom) {
    226         mAnimatedVectorState.mVectorDrawable.setHotspotBounds(left, top, right, bottom);
    227     }
    228 
    229     @Override
    230     public void setTintMode(PorterDuff.Mode tintMode) {
    231         mAnimatedVectorState.mVectorDrawable.setTintMode(tintMode);
    232     }
    233 
    234     @Override
    235     public boolean setVisible(boolean visible, boolean restart) {
    236         mAnimatedVectorState.mVectorDrawable.setVisible(visible, restart);
    237         return super.setVisible(visible, restart);
    238     }
    239 
    240     /** {@hide} */
    241     @Override
    242     public void setLayoutDirection(int layoutDirection) {
    243         mAnimatedVectorState.mVectorDrawable.setLayoutDirection(layoutDirection);
    244     }
    245 
    246     @Override
    247     public boolean isStateful() {
    248         return mAnimatedVectorState.mVectorDrawable.isStateful();
    249     }
    250 
    251     @Override
    252     public int getOpacity() {
    253         return mAnimatedVectorState.mVectorDrawable.getOpacity();
    254     }
    255 
    256     @Override
    257     public int getIntrinsicWidth() {
    258         return mAnimatedVectorState.mVectorDrawable.getIntrinsicWidth();
    259     }
    260 
    261     @Override
    262     public int getIntrinsicHeight() {
    263         return mAnimatedVectorState.mVectorDrawable.getIntrinsicHeight();
    264     }
    265 
    266     @Override
    267     public void getOutline(@NonNull Outline outline) {
    268         mAnimatedVectorState.mVectorDrawable.getOutline(outline);
    269     }
    270 
    271     @Override
    272     public void inflate(Resources res, XmlPullParser parser, AttributeSet attrs, Theme theme)
    273             throws XmlPullParserException, IOException {
    274 
    275         int eventType = parser.getEventType();
    276         float pathErrorScale = 1;
    277         while (eventType != XmlPullParser.END_DOCUMENT) {
    278             if (eventType == XmlPullParser.START_TAG) {
    279                 final String tagName = parser.getName();
    280                 if (ANIMATED_VECTOR.equals(tagName)) {
    281                     final TypedArray a = obtainAttributes(res, theme, attrs,
    282                             R.styleable.AnimatedVectorDrawable);
    283                     int drawableRes = a.getResourceId(
    284                             R.styleable.AnimatedVectorDrawable_drawable, 0);
    285                     if (drawableRes != 0) {
    286                         VectorDrawable vectorDrawable = (VectorDrawable) res.getDrawable(
    287                                 drawableRes, theme).mutate();
    288                         vectorDrawable.setAllowCaching(false);
    289                         vectorDrawable.setCallback(mCallback);
    290                         pathErrorScale = vectorDrawable.getPixelSize();
    291                         if (mAnimatedVectorState.mVectorDrawable != null) {
    292                             mAnimatedVectorState.mVectorDrawable.setCallback(null);
    293                         }
    294                         mAnimatedVectorState.mVectorDrawable = vectorDrawable;
    295                     }
    296                     a.recycle();
    297                 } else if (TARGET.equals(tagName)) {
    298                     final TypedArray a = obtainAttributes(res, theme, attrs,
    299                             R.styleable.AnimatedVectorDrawableTarget);
    300                     final String target = a.getString(
    301                             R.styleable.AnimatedVectorDrawableTarget_name);
    302 
    303                     int id = a.getResourceId(
    304                             R.styleable.AnimatedVectorDrawableTarget_animation, 0);
    305                     if (id != 0) {
    306                         Animator objectAnimator = AnimatorInflater.loadAnimator(res, theme, id,
    307                                 pathErrorScale);
    308                         setupAnimatorsForTarget(target, objectAnimator);
    309                     }
    310                     a.recycle();
    311                 }
    312             }
    313 
    314             eventType = parser.next();
    315         }
    316     }
    317 
    318     @Override
    319     public boolean canApplyTheme() {
    320         return (mAnimatedVectorState != null && mAnimatedVectorState.canApplyTheme())
    321                 || super.canApplyTheme();
    322     }
    323 
    324     @Override
    325     public void applyTheme(Theme t) {
    326         super.applyTheme(t);
    327 
    328         final VectorDrawable vectorDrawable = mAnimatedVectorState.mVectorDrawable;
    329         if (vectorDrawable != null && vectorDrawable.canApplyTheme()) {
    330             vectorDrawable.applyTheme(t);
    331         }
    332     }
    333 
    334     private static class AnimatedVectorDrawableState extends ConstantState {
    335         int mChangingConfigurations;
    336         VectorDrawable mVectorDrawable;
    337         ArrayList<Animator> mAnimators;
    338         ArrayMap<Animator, String> mTargetNameMap;
    339 
    340         public AnimatedVectorDrawableState(AnimatedVectorDrawableState copy,
    341                 Callback owner, Resources res) {
    342             if (copy != null) {
    343                 mChangingConfigurations = copy.mChangingConfigurations;
    344                 if (copy.mVectorDrawable != null) {
    345                     final ConstantState cs = copy.mVectorDrawable.getConstantState();
    346                     if (res != null) {
    347                         mVectorDrawable = (VectorDrawable) cs.newDrawable(res);
    348                     } else {
    349                         mVectorDrawable = (VectorDrawable) cs.newDrawable();
    350                     }
    351                     mVectorDrawable = (VectorDrawable) mVectorDrawable.mutate();
    352                     mVectorDrawable.setCallback(owner);
    353                     mVectorDrawable.setLayoutDirection(copy.mVectorDrawable.getLayoutDirection());
    354                     mVectorDrawable.setBounds(copy.mVectorDrawable.getBounds());
    355                     mVectorDrawable.setAllowCaching(false);
    356                 }
    357                 if (copy.mAnimators != null) {
    358                     final int numAnimators = copy.mAnimators.size();
    359                     mAnimators = new ArrayList<Animator>(numAnimators);
    360                     mTargetNameMap = new ArrayMap<Animator, String>(numAnimators);
    361                     for (int i = 0; i < numAnimators; ++i) {
    362                         Animator anim = copy.mAnimators.get(i);
    363                         Animator animClone = anim.clone();
    364                         String targetName = copy.mTargetNameMap.get(anim);
    365                         Object targetObject = mVectorDrawable.getTargetByName(targetName);
    366                         animClone.setTarget(targetObject);
    367                         mAnimators.add(animClone);
    368                         mTargetNameMap.put(animClone, targetName);
    369                     }
    370                 }
    371             } else {
    372                 mVectorDrawable = new VectorDrawable();
    373             }
    374         }
    375 
    376         @Override
    377         public boolean canApplyTheme() {
    378             return (mVectorDrawable != null && mVectorDrawable.canApplyTheme())
    379                     || super.canApplyTheme();
    380         }
    381 
    382         @Override
    383         public Drawable newDrawable() {
    384             return new AnimatedVectorDrawable(this, null);
    385         }
    386 
    387         @Override
    388         public Drawable newDrawable(Resources res) {
    389             return new AnimatedVectorDrawable(this, res);
    390         }
    391 
    392         @Override
    393         public int getChangingConfigurations() {
    394             return mChangingConfigurations;
    395         }
    396     }
    397 
    398     private void setupAnimatorsForTarget(String name, Animator animator) {
    399         Object target = mAnimatedVectorState.mVectorDrawable.getTargetByName(name);
    400         animator.setTarget(target);
    401         if (mAnimatedVectorState.mAnimators == null) {
    402             mAnimatedVectorState.mAnimators = new ArrayList<Animator>();
    403             mAnimatedVectorState.mTargetNameMap = new ArrayMap<Animator, String>();
    404         }
    405         mAnimatedVectorState.mAnimators.add(animator);
    406         mAnimatedVectorState.mTargetNameMap.put(animator, name);
    407         if (DBG_ANIMATION_VECTOR_DRAWABLE) {
    408             Log.v(LOGTAG, "add animator  for target " + name + " " + animator);
    409         }
    410     }
    411 
    412     @Override
    413     public boolean isRunning() {
    414         final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators;
    415         final int size = animators.size();
    416         for (int i = 0; i < size; i++) {
    417             final Animator animator = animators.get(i);
    418             if (animator.isRunning()) {
    419                 return true;
    420             }
    421         }
    422         return false;
    423     }
    424 
    425     private boolean isStarted() {
    426         final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators;
    427         final int size = animators.size();
    428         for (int i = 0; i < size; i++) {
    429             final Animator animator = animators.get(i);
    430             if (animator.isStarted()) {
    431                 return true;
    432             }
    433         }
    434         return false;
    435     }
    436 
    437     @Override
    438     public void start() {
    439         // If any one of the animator has not ended, do nothing.
    440         if (isStarted()) {
    441             return;
    442         }
    443         // Otherwise, kick off every animator.
    444         final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators;
    445         final int size = animators.size();
    446         for (int i = 0; i < size; i++) {
    447             final Animator animator = animators.get(i);
    448             animator.start();
    449         }
    450         invalidateSelf();
    451     }
    452 
    453     @Override
    454     public void stop() {
    455         final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators;
    456         final int size = animators.size();
    457         for (int i = 0; i < size; i++) {
    458             final Animator animator = animators.get(i);
    459             animator.end();
    460         }
    461     }
    462 
    463     /**
    464      * Reverses ongoing animations or starts pending animations in reverse.
    465      * <p>
    466      * NOTE: Only works if all animations support reverse. Otherwise, this will
    467      * do nothing.
    468      * @hide
    469      */
    470     public void reverse() {
    471         // Only reverse when all the animators can be reverse. Otherwise, partially
    472         // reverse is confusing.
    473         if (!canReverse()) {
    474             Log.w(LOGTAG, "AnimatedVectorDrawable can't reverse()");
    475             return;
    476         }
    477         final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators;
    478         final int size = animators.size();
    479         for (int i = 0; i < size; i++) {
    480             final Animator animator = animators.get(i);
    481             animator.reverse();
    482         }
    483     }
    484 
    485     /**
    486      * @hide
    487      */
    488     public boolean canReverse() {
    489         final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators;
    490         final int size = animators.size();
    491         for (int i = 0; i < size; i++) {
    492             final Animator animator = animators.get(i);
    493             if (!animator.canReverse()) {
    494                 return false;
    495             }
    496         }
    497         return true;
    498     }
    499 
    500     private final Callback mCallback = new Callback() {
    501         @Override
    502         public void invalidateDrawable(Drawable who) {
    503             invalidateSelf();
    504         }
    505 
    506         @Override
    507         public void scheduleDrawable(Drawable who, Runnable what, long when) {
    508             scheduleSelf(what, when);
    509         }
    510 
    511         @Override
    512         public void unscheduleDrawable(Drawable who, Runnable what) {
    513             unscheduleSelf(what);
    514         }
    515     };
    516 }
    517