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