AnimatedVectorDrawable.java revision a12962207155305da44b5a1b8fb9acaed358c14c
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.annotation.NonNull; 20import android.content.res.ColorStateList; 21import android.content.res.Resources; 22import android.content.res.Resources.Theme; 23import android.content.res.TypedArray; 24import android.graphics.Canvas; 25import android.graphics.ColorFilter; 26import android.graphics.Outline; 27import android.graphics.PorterDuff; 28import android.graphics.Rect; 29import android.util.ArrayMap; 30import android.util.AttributeSet; 31import android.util.Log; 32 33import com.android.internal.R; 34 35import org.xmlpull.v1.XmlPullParser; 36import org.xmlpull.v1.XmlPullParserException; 37 38import java.io.IOException; 39import 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 * <vector xmlns:android="http://schemas.android.com/apk/res/android" 57 * android:height="64dp" 58 * android:width="64dp" 59 * android:viewportHeight="600" 60 * android:viewportWidth="600" > 61 * <group 62 * android:name="rotationGroup" 63 * android:pivotX="300.0" 64 * android:pivotY="300.0" 65 * android:rotation="45.0" > 66 * <path 67 * android:name="v" 68 * android:fillColor="#000000" 69 * android:pathData="M300,70 l 0,-70 70,70 0,0 -70,70z" /> 70 * </group> 71 * </vector> 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 * <animated-vector xmlns:android="http://schemas.android.com/apk/res/android" 83 * android:drawable="@drawable/vectordrawable" > 84 * <target 85 * android:name="rotationGroup" 86 * android:animation="@anim/rotation" /> 87 * <target 88 * android:name="v" 89 * android:animation="@anim/path_morph" /> 90 * </animated-vector> 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 * <objectAnimator 101 * android:duration="6000" 102 * android:propertyName="rotation" 103 * android:valueFrom="0" 104 * android:valueTo="360" /> 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 * <set xmlns:android="http://schemas.android.com/apk/res/android"> 113 * <objectAnimator 114 * android:duration="3000" 115 * android:propertyName="pathData" 116 * android:valueFrom="M300,70 l 0,-70 70,70 0,0 -70,70z" 117 * android:valueTo="M300,70 l 0,-70 70,0 0,140 -70,0 z" 118 * android:valueType="pathType"/> 119 * </set> 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 */ 126public 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 boolean onLayoutDirectionChange(int layoutDirection) { 201 return mAnimatedVectorState.mVectorDrawable.setLayoutDirection(layoutDirection); 202 } 203 204 @Override 205 public int getAlpha() { 206 return mAnimatedVectorState.mVectorDrawable.getAlpha(); 207 } 208 209 @Override 210 public void setAlpha(int alpha) { 211 mAnimatedVectorState.mVectorDrawable.setAlpha(alpha); 212 } 213 214 @Override 215 public void setColorFilter(ColorFilter colorFilter) { 216 mAnimatedVectorState.mVectorDrawable.setColorFilter(colorFilter); 217 } 218 219 @Override 220 public void setTintList(ColorStateList tint) { 221 mAnimatedVectorState.mVectorDrawable.setTintList(tint); 222 } 223 224 @Override 225 public void setHotspot(float x, float y) { 226 mAnimatedVectorState.mVectorDrawable.setHotspot(x, y); 227 } 228 229 @Override 230 public void setHotspotBounds(int left, int top, int right, int bottom) { 231 mAnimatedVectorState.mVectorDrawable.setHotspotBounds(left, top, right, bottom); 232 } 233 234 @Override 235 public void setTintMode(PorterDuff.Mode tintMode) { 236 mAnimatedVectorState.mVectorDrawable.setTintMode(tintMode); 237 } 238 239 @Override 240 public boolean setVisible(boolean visible, boolean restart) { 241 mAnimatedVectorState.mVectorDrawable.setVisible(visible, restart); 242 return super.setVisible(visible, restart); 243 } 244 245 @Override 246 public boolean isStateful() { 247 return mAnimatedVectorState.mVectorDrawable.isStateful(); 248 } 249 250 @Override 251 public int getOpacity() { 252 return mAnimatedVectorState.mVectorDrawable.getOpacity(); 253 } 254 255 @Override 256 public int getIntrinsicWidth() { 257 return mAnimatedVectorState.mVectorDrawable.getIntrinsicWidth(); 258 } 259 260 @Override 261 public int getIntrinsicHeight() { 262 return mAnimatedVectorState.mVectorDrawable.getIntrinsicHeight(); 263 } 264 265 @Override 266 public void getOutline(@NonNull Outline outline) { 267 mAnimatedVectorState.mVectorDrawable.getOutline(outline); 268 } 269 270 @Override 271 public void inflate(Resources res, XmlPullParser parser, AttributeSet attrs, Theme theme) 272 throws XmlPullParserException, IOException { 273 274 int eventType = parser.getEventType(); 275 float pathErrorScale = 1; 276 while (eventType != XmlPullParser.END_DOCUMENT) { 277 if (eventType == XmlPullParser.START_TAG) { 278 final String tagName = parser.getName(); 279 if (ANIMATED_VECTOR.equals(tagName)) { 280 final TypedArray a = obtainAttributes(res, theme, attrs, 281 R.styleable.AnimatedVectorDrawable); 282 int drawableRes = a.getResourceId( 283 R.styleable.AnimatedVectorDrawable_drawable, 0); 284 if (drawableRes != 0) { 285 VectorDrawable vectorDrawable = (VectorDrawable) res.getDrawable( 286 drawableRes, theme).mutate(); 287 vectorDrawable.setAllowCaching(false); 288 vectorDrawable.setCallback(mCallback); 289 pathErrorScale = vectorDrawable.getPixelSize(); 290 if (mAnimatedVectorState.mVectorDrawable != null) { 291 mAnimatedVectorState.mVectorDrawable.setCallback(null); 292 } 293 mAnimatedVectorState.mVectorDrawable = vectorDrawable; 294 } 295 a.recycle(); 296 } else if (TARGET.equals(tagName)) { 297 final TypedArray a = obtainAttributes(res, theme, attrs, 298 R.styleable.AnimatedVectorDrawableTarget); 299 final String target = a.getString( 300 R.styleable.AnimatedVectorDrawableTarget_name); 301 302 int id = a.getResourceId( 303 R.styleable.AnimatedVectorDrawableTarget_animation, 0); 304 if (id != 0) { 305 Animator objectAnimator = AnimatorInflater.loadAnimator(res, theme, id, 306 pathErrorScale); 307 setupAnimatorsForTarget(target, objectAnimator); 308 } 309 a.recycle(); 310 } 311 } 312 313 eventType = parser.next(); 314 } 315 } 316 317 @Override 318 public boolean canApplyTheme() { 319 return (mAnimatedVectorState != null && mAnimatedVectorState.canApplyTheme()) 320 || super.canApplyTheme(); 321 } 322 323 @Override 324 public void applyTheme(Theme t) { 325 super.applyTheme(t); 326 327 final VectorDrawable vectorDrawable = mAnimatedVectorState.mVectorDrawable; 328 if (vectorDrawable != null && vectorDrawable.canApplyTheme()) { 329 vectorDrawable.applyTheme(t); 330 } 331 } 332 333 private static class AnimatedVectorDrawableState extends ConstantState { 334 int mChangingConfigurations; 335 VectorDrawable mVectorDrawable; 336 ArrayList<Animator> mAnimators; 337 ArrayMap<Animator, String> mTargetNameMap; 338 339 public AnimatedVectorDrawableState(AnimatedVectorDrawableState copy, 340 Callback owner, Resources res) { 341 if (copy != null) { 342 mChangingConfigurations = copy.mChangingConfigurations; 343 if (copy.mVectorDrawable != null) { 344 final ConstantState cs = copy.mVectorDrawable.getConstantState(); 345 if (res != null) { 346 mVectorDrawable = (VectorDrawable) cs.newDrawable(res); 347 } else { 348 mVectorDrawable = (VectorDrawable) cs.newDrawable(); 349 } 350 mVectorDrawable = (VectorDrawable) mVectorDrawable.mutate(); 351 mVectorDrawable.setCallback(owner); 352 mVectorDrawable.setLayoutDirection(copy.mVectorDrawable.getLayoutDirection()); 353 mVectorDrawable.setBounds(copy.mVectorDrawable.getBounds()); 354 mVectorDrawable.setAllowCaching(false); 355 } 356 if (copy.mAnimators != null) { 357 final int numAnimators = copy.mAnimators.size(); 358 mAnimators = new ArrayList<Animator>(numAnimators); 359 mTargetNameMap = new ArrayMap<Animator, String>(numAnimators); 360 for (int i = 0; i < numAnimators; ++i) { 361 Animator anim = copy.mAnimators.get(i); 362 Animator animClone = anim.clone(); 363 String targetName = copy.mTargetNameMap.get(anim); 364 Object targetObject = mVectorDrawable.getTargetByName(targetName); 365 animClone.setTarget(targetObject); 366 mAnimators.add(animClone); 367 mTargetNameMap.put(animClone, targetName); 368 } 369 } 370 } else { 371 mVectorDrawable = new VectorDrawable(); 372 } 373 } 374 375 @Override 376 public boolean canApplyTheme() { 377 return (mVectorDrawable != null && mVectorDrawable.canApplyTheme()) 378 || super.canApplyTheme(); 379 } 380 381 @Override 382 public Drawable newDrawable() { 383 return new AnimatedVectorDrawable(this, null); 384 } 385 386 @Override 387 public Drawable newDrawable(Resources res) { 388 return new AnimatedVectorDrawable(this, res); 389 } 390 391 @Override 392 public int getChangingConfigurations() { 393 return mChangingConfigurations; 394 } 395 } 396 397 private void setupAnimatorsForTarget(String name, Animator animator) { 398 Object target = mAnimatedVectorState.mVectorDrawable.getTargetByName(name); 399 animator.setTarget(target); 400 if (mAnimatedVectorState.mAnimators == null) { 401 mAnimatedVectorState.mAnimators = new ArrayList<Animator>(); 402 mAnimatedVectorState.mTargetNameMap = new ArrayMap<Animator, String>(); 403 } 404 mAnimatedVectorState.mAnimators.add(animator); 405 mAnimatedVectorState.mTargetNameMap.put(animator, name); 406 if (DBG_ANIMATION_VECTOR_DRAWABLE) { 407 Log.v(LOGTAG, "add animator for target " + name + " " + animator); 408 } 409 } 410 411 @Override 412 public boolean isRunning() { 413 final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators; 414 final int size = animators.size(); 415 for (int i = 0; i < size; i++) { 416 final Animator animator = animators.get(i); 417 if (animator.isRunning()) { 418 return true; 419 } 420 } 421 return false; 422 } 423 424 private boolean isStarted() { 425 final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators; 426 final int size = animators.size(); 427 for (int i = 0; i < size; i++) { 428 final Animator animator = animators.get(i); 429 if (animator.isStarted()) { 430 return true; 431 } 432 } 433 return false; 434 } 435 436 @Override 437 public void start() { 438 // If any one of the animator has not ended, do nothing. 439 if (isStarted()) { 440 return; 441 } 442 // Otherwise, kick off every animator. 443 final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators; 444 final int size = animators.size(); 445 for (int i = 0; i < size; i++) { 446 final Animator animator = animators.get(i); 447 animator.start(); 448 } 449 invalidateSelf(); 450 } 451 452 @Override 453 public void stop() { 454 final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators; 455 final int size = animators.size(); 456 for (int i = 0; i < size; i++) { 457 final Animator animator = animators.get(i); 458 animator.end(); 459 } 460 } 461 462 /** 463 * Reverses ongoing animations or starts pending animations in reverse. 464 * <p> 465 * NOTE: Only works if all animations support reverse. Otherwise, this will 466 * do nothing. 467 * @hide 468 */ 469 public void reverse() { 470 // Only reverse when all the animators can be reverse. Otherwise, partially 471 // reverse is confusing. 472 if (!canReverse()) { 473 Log.w(LOGTAG, "AnimatedVectorDrawable can't reverse()"); 474 return; 475 } 476 final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators; 477 final int size = animators.size(); 478 for (int i = 0; i < size; i++) { 479 final Animator animator = animators.get(i); 480 animator.reverse(); 481 } 482 } 483 484 /** 485 * @hide 486 */ 487 public boolean canReverse() { 488 final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators; 489 final int size = animators.size(); 490 for (int i = 0; i < size; i++) { 491 final Animator animator = animators.get(i); 492 if (!animator.canReverse()) { 493 return false; 494 } 495 } 496 return true; 497 } 498 499 private final Callback mCallback = new Callback() { 500 @Override 501 public void invalidateDrawable(Drawable who) { 502 invalidateSelf(); 503 } 504 505 @Override 506 public void scheduleDrawable(Drawable who, Runnable what, long when) { 507 scheduleSelf(what, when); 508 } 509 510 @Override 511 public void unscheduleDrawable(Drawable who, Runnable what) { 512 unscheduleSelf(what); 513 } 514 }; 515} 516