AnimatedVectorDrawable.java revision 7bc6a3f023ca3e1dde91fc97b6036dee3ba538a2
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 15package android.graphics.drawable; 16 17import android.animation.Animator; 18import android.animation.AnimatorInflater; 19import android.animation.ValueAnimator; 20import android.content.res.Resources; 21import android.content.res.Resources.Theme; 22import android.content.res.TypedArray; 23import android.graphics.Canvas; 24import android.graphics.ColorFilter; 25import android.graphics.Rect; 26import android.util.AttributeSet; 27import android.util.Log; 28 29import com.android.internal.R; 30 31import org.xmlpull.v1.XmlPullParser; 32import org.xmlpull.v1.XmlPullParserException; 33 34import java.io.IOException; 35import java.util.ArrayList; 36 37/** 38 * This class uses {@link android.animation.ObjectAnimator} and 39 * {@link android.animation.AnimatorSet} to animate the properties of a 40 * {@link android.graphics.drawable.VectorDrawable} to create an animated drawable. 41 * <p> 42 * AnimatedVectorDrawable are normally defined as 3 separate XML files. 43 * </p> 44 * <p> 45 * First is the XML file for {@link android.graphics.drawable.VectorDrawable}. 46 * Note that we allow the animation happen on the group's attributes and path's 47 * attributes, which requires they are uniquely named in this xml file. Groups 48 * and paths without animations do not need names. 49 * </p> 50 * <li>Here is a simple VectorDrawable in this vectordrawable.xml file. 51 * <pre> 52 * <vector xmlns:android="http://schemas.android.com/apk/res/android" > 53 * <size 54 * android:height="64dp" 55 * android:width="64dp" /> 56 * <viewport 57 * android:viewportHeight="600" 58 * android:viewportWidth="600" /> 59 * <group 60 * android:name="rotationGroup" 61 * android:pivotX="300.0" 62 * android:pivotY="300.0" 63 * android:rotation="45.0" > 64 * <path 65 * android:name="v" 66 * android:fill="#000000" 67 * android:pathData="M300,70 l 0,-70 70,70 0,0 -70,70z" /> 68 * </group> 69 * </vector> 70 * </pre></li> 71 * <p> 72 * Second is the AnimatedVectorDrawable's xml file, which defines the target 73 * VectorDrawable, the target paths and groups to animate, the properties of the 74 * path and group to animate and the animations defined as the ObjectAnimators 75 * or AnimatorSets. 76 * </p> 77 * <li>Here is a simple AnimatedVectorDrawable defined in this avd.xml file. 78 * Note how we use the names to refer to the groups and paths in the vectordrawable.xml. 79 * <pre> 80 * <animated-vector xmlns:android="http://schemas.android.com/apk/res/android" 81 * android:drawable="@drawable/vectordrawable" > 82 * <target 83 * android:name="rotationGroup" 84 * android:animation="@anim/rotation" /> 85 * <target 86 * android:name="v" 87 * android:animation="@anim/path_morph" /> 88 * </animated-vector> 89 * </pre></li> 90 * <p> 91 * Last is the Animator xml file, which is the same as a normal ObjectAnimator 92 * or AnimatorSet. 93 * To complete this example, here are the 2 animator files used in avd.xml: 94 * rotation.xml and path_morph.xml. 95 * </p> 96 * <li>Here is the rotation.xml, which will rotate the target group for 360 degrees. 97 * <pre> 98 * <objectAnimator 99 * android:duration="6000" 100 * android:propertyName="rotation" 101 * android:valueFrom="0" 102 * android:valueTo="360" /> 103 * </pre></li> 104 * <li>Here is the path_morph.xml, which will morph the path from one shape to 105 * the other. Note that the paths must be compatible for morphing. 106 * In more details, the paths should have exact same length of commands , and 107 * exact same length of parameters for each commands. 108 * Note that the path string are better stored in strings.xml for reusing. 109 * <pre> 110 * <set xmlns:android="http://schemas.android.com/apk/res/android"> 111 * <objectAnimator 112 * android:duration="3000" 113 * android:propertyName="pathData" 114 * android:valueFrom="M300,70 l 0,-70 70,70 0,0 -70,70z" 115 * android:valueTo="M300,70 l 0,-70 70,0 0,140 -70,0 z" 116 * android:valueType="pathType"/> 117 * </set> 118 * </pre></li> 119 * 120 * @attr ref android.R.styleable#AnimatedVectorDrawable_drawable 121 * @attr ref android.R.styleable#AnimatedVectorDrawableTarget_name 122 * @attr ref android.R.styleable#AnimatedVectorDrawableTarget_animation 123 */ 124public class AnimatedVectorDrawable extends Drawable implements Animatable { 125 private static final String LOGTAG = AnimatedVectorDrawable.class.getSimpleName(); 126 127 private static final String ANIMATED_VECTOR = "animated-vector"; 128 private static final String TARGET = "target"; 129 130 private static final boolean DBG_ANIMATION_VECTOR_DRAWABLE = false; 131 132 private final AnimatedVectorDrawableState mAnimatedVectorState; 133 134 135 public AnimatedVectorDrawable() { 136 mAnimatedVectorState = new AnimatedVectorDrawableState( 137 new AnimatedVectorDrawableState(null)); 138 } 139 140 private AnimatedVectorDrawable(AnimatedVectorDrawableState state, Resources res, 141 Theme theme) { 142 // TODO: Correctly handle the constant state for AVD. 143 mAnimatedVectorState = new AnimatedVectorDrawableState(state); 144 if (theme != null && canApplyTheme()) { 145 applyTheme(theme); 146 } 147 } 148 149 @Override 150 public ConstantState getConstantState() { 151 return null; 152 } 153 154 @Override 155 public void draw(Canvas canvas) { 156 mAnimatedVectorState.mVectorDrawable.draw(canvas); 157 if (isRunning()) { 158 invalidateSelf(); 159 } 160 } 161 162 @Override 163 protected void onBoundsChange(Rect bounds) { 164 mAnimatedVectorState.mVectorDrawable.setBounds(bounds); 165 } 166 167 @Override 168 public int getAlpha() { 169 return mAnimatedVectorState.mVectorDrawable.getAlpha(); 170 } 171 172 @Override 173 public void setAlpha(int alpha) { 174 mAnimatedVectorState.mVectorDrawable.setAlpha(alpha); 175 } 176 177 @Override 178 public void setColorFilter(ColorFilter colorFilter) { 179 mAnimatedVectorState.mVectorDrawable.setColorFilter(colorFilter); 180 } 181 182 @Override 183 public int getOpacity() { 184 return mAnimatedVectorState.mVectorDrawable.getOpacity(); 185 } 186 187 @Override 188 public int getIntrinsicWidth() { 189 return mAnimatedVectorState.mVectorDrawable.getIntrinsicWidth(); 190 } 191 192 @Override 193 public int getIntrinsicHeight() { 194 return mAnimatedVectorState.mVectorDrawable.getIntrinsicHeight(); 195 } 196 197 @Override 198 public void inflate(Resources res, XmlPullParser parser, AttributeSet attrs, Theme theme) 199 throws XmlPullParserException, IOException { 200 201 int eventType = parser.getEventType(); 202 while (eventType != XmlPullParser.END_DOCUMENT) { 203 if (eventType == XmlPullParser.START_TAG) { 204 final String tagName = parser.getName(); 205 if (ANIMATED_VECTOR.equals(tagName)) { 206 final TypedArray a = obtainAttributes(res, theme, attrs, 207 R.styleable.AnimatedVectorDrawable); 208 int drawableRes = a.getResourceId( 209 R.styleable.AnimatedVectorDrawable_drawable, 0); 210 if (drawableRes != 0) { 211 mAnimatedVectorState.mVectorDrawable = (VectorDrawable) res.getDrawable( 212 drawableRes, theme).mutate(); 213 mAnimatedVectorState.mVectorDrawable.setAllowCaching(false); 214 } 215 a.recycle(); 216 } else if (TARGET.equals(tagName)) { 217 final TypedArray a = obtainAttributes(res, theme, attrs, 218 R.styleable.AnimatedVectorDrawableTarget); 219 final String target = a.getString( 220 R.styleable.AnimatedVectorDrawableTarget_name); 221 222 int id = a.getResourceId( 223 R.styleable.AnimatedVectorDrawableTarget_animation, 0); 224 if (id != 0) { 225 Animator objectAnimator = AnimatorInflater.loadAnimator(res, theme, id); 226 setupAnimatorsForTarget(target, objectAnimator); 227 } 228 a.recycle(); 229 } 230 } 231 232 eventType = parser.next(); 233 } 234 } 235 236 @Override 237 public boolean canApplyTheme() { 238 return super.canApplyTheme() || mAnimatedVectorState != null 239 && mAnimatedVectorState.mVectorDrawable != null 240 && mAnimatedVectorState.mVectorDrawable.canApplyTheme(); 241 } 242 243 @Override 244 public void applyTheme(Theme t) { 245 super.applyTheme(t); 246 247 final VectorDrawable vectorDrawable = mAnimatedVectorState.mVectorDrawable; 248 if (vectorDrawable != null && vectorDrawable.canApplyTheme()) { 249 vectorDrawable.applyTheme(t); 250 } 251 } 252 253 private static class AnimatedVectorDrawableState extends ConstantState { 254 int mChangingConfigurations; 255 VectorDrawable mVectorDrawable; 256 ArrayList<Animator> mAnimators; 257 258 public AnimatedVectorDrawableState(AnimatedVectorDrawableState copy) { 259 if (copy != null) { 260 mChangingConfigurations = copy.mChangingConfigurations; 261 // TODO: Make sure the constant state are handled correctly. 262 mVectorDrawable = new VectorDrawable(); 263 mVectorDrawable.setAllowCaching(false); 264 mAnimators = new ArrayList<Animator>(); 265 } 266 } 267 268 @Override 269 public Drawable newDrawable() { 270 return new AnimatedVectorDrawable(this, null, null); 271 } 272 273 @Override 274 public Drawable newDrawable(Resources res) { 275 return new AnimatedVectorDrawable(this, res, null); 276 } 277 278 @Override 279 public Drawable newDrawable(Resources res, Theme theme) { 280 return new AnimatedVectorDrawable(this, res, theme); 281 } 282 283 @Override 284 public int getChangingConfigurations() { 285 return mChangingConfigurations; 286 } 287 } 288 289 private void setupAnimatorsForTarget(String name, Animator animator) { 290 Object target = mAnimatedVectorState.mVectorDrawable.getTargetByName(name); 291 animator.setTarget(target); 292 mAnimatedVectorState.mAnimators.add(animator); 293 if (DBG_ANIMATION_VECTOR_DRAWABLE) { 294 Log.v(LOGTAG, "add animator for target " + name + " " + animator); 295 } 296 } 297 298 @Override 299 public boolean isRunning() { 300 final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators; 301 final int size = animators.size(); 302 for (int i = 0; i < size; i++) { 303 final Animator animator = animators.get(i); 304 if (animator.isRunning()) { 305 return true; 306 } 307 } 308 return false; 309 } 310 311 @Override 312 public void start() { 313 final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators; 314 final int size = animators.size(); 315 for (int i = 0; i < size; i++) { 316 final Animator animator = animators.get(i); 317 if (animator.isPaused()) { 318 animator.resume(); 319 } else if (!animator.isRunning()) { 320 animator.start(); 321 } 322 } 323 invalidateSelf(); 324 } 325 326 @Override 327 public void stop() { 328 final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators; 329 final int size = animators.size(); 330 for (int i = 0; i < size; i++) { 331 final Animator animator = animators.get(i); 332 animator.pause(); 333 } 334 } 335 336 /** 337 * Reverses ongoing animations or starts pending animations in reverse. 338 * <p> 339 * NOTE: Only works of all animations are ValueAnimators. 340 * @hide 341 */ 342 public void reverse() { 343 final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators; 344 final int size = animators.size(); 345 for (int i = 0; i < size; i++) { 346 final Animator animator = animators.get(i); 347 if (animator.canReverse()) { 348 animator.reverse(); 349 } else { 350 Log.w(LOGTAG, "AnimatedVectorDrawable can't reverse()"); 351 } 352 } 353 } 354 355 /** 356 * @hide 357 */ 358 public boolean canReverse() { 359 final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators; 360 final int size = animators.size(); 361 for (int i = 0; i < size; i++) { 362 final Animator animator = animators.get(i); 363 if (!animator.canReverse()) { 364 return false; 365 } 366 } 367 return true; 368 } 369} 370