AnimatedVectorDrawable.java revision 727cae197b123ef764a1f8fbe08a995b000d14c3
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.annotation.NonNull; 21import android.content.res.ColorStateList; 22import android.content.res.Resources; 23import android.content.res.Resources.Theme; 24import android.content.res.TypedArray; 25import android.graphics.Canvas; 26import android.graphics.ColorFilter; 27import android.graphics.Outline; 28import android.graphics.PorterDuff; 29import android.graphics.Rect; 30import android.util.ArrayMap; 31import android.util.AttributeSet; 32import android.util.Log; 33 34import com.android.internal.R; 35 36import org.xmlpull.v1.XmlPullParser; 37import org.xmlpull.v1.XmlPullParserException; 38 39import java.io.IOException; 40import 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 * <vector xmlns:android="http://schemas.android.com/apk/res/android" 58 * android:height="64dp" 59 * android:width="64dp" 60 * android:viewportHeight="600" 61 * android:viewportWidth="600" > 62 * <group 63 * android:name="rotationGroup" 64 * android:pivotX="300.0" 65 * android:pivotY="300.0" 66 * android:rotation="45.0" > 67 * <path 68 * android:name="v" 69 * android:fillColor="#000000" 70 * android:pathData="M300,70 l 0,-70 70,70 0,0 -70,70z" /> 71 * </group> 72 * </vector> 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 * <animated-vector xmlns:android="http://schemas.android.com/apk/res/android" 84 * android:drawable="@drawable/vectordrawable" > 85 * <target 86 * android:name="rotationGroup" 87 * android:animation="@anim/rotation" /> 88 * <target 89 * android:name="v" 90 * android:animation="@anim/path_morph" /> 91 * </animated-vector> 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 * <objectAnimator 102 * android:duration="6000" 103 * android:propertyName="rotation" 104 * android:valueFrom="0" 105 * android:valueTo="360" /> 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 * <set xmlns:android="http://schemas.android.com/apk/res/android"> 114 * <objectAnimator 115 * android:duration="3000" 116 * android:propertyName="pathData" 117 * android:valueFrom="M300,70 l 0,-70 70,70 0,0 -70,70z" 118 * android:valueTo="M300,70 l 0,-70 70,0 0,140 -70,0 z" 119 * android:valueType="pathType"/> 120 * </set> 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 */ 127public 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 /** 161 * @hide 162 */ 163 public void clearMutated() { 164 super.clearMutated(); 165 mAnimatedVectorState.mVectorDrawable.clearMutated(); 166 mMutated = false; 167 } 168 169 @Override 170 public ConstantState getConstantState() { 171 mAnimatedVectorState.mChangingConfigurations = getChangingConfigurations(); 172 return mAnimatedVectorState; 173 } 174 175 @Override 176 public int getChangingConfigurations() { 177 return super.getChangingConfigurations() | mAnimatedVectorState.mChangingConfigurations; 178 } 179 180 @Override 181 public void draw(Canvas canvas) { 182 mAnimatedVectorState.mVectorDrawable.draw(canvas); 183 if (isStarted()) { 184 invalidateSelf(); 185 } 186 } 187 188 @Override 189 protected void onBoundsChange(Rect bounds) { 190 mAnimatedVectorState.mVectorDrawable.setBounds(bounds); 191 } 192 193 @Override 194 protected boolean onStateChange(int[] state) { 195 return mAnimatedVectorState.mVectorDrawable.setState(state); 196 } 197 198 @Override 199 protected boolean onLevelChange(int level) { 200 return mAnimatedVectorState.mVectorDrawable.setLevel(level); 201 } 202 203 @Override 204 public int getAlpha() { 205 return mAnimatedVectorState.mVectorDrawable.getAlpha(); 206 } 207 208 @Override 209 public void setAlpha(int alpha) { 210 mAnimatedVectorState.mVectorDrawable.setAlpha(alpha); 211 } 212 213 @Override 214 public void setColorFilter(ColorFilter colorFilter) { 215 mAnimatedVectorState.mVectorDrawable.setColorFilter(colorFilter); 216 } 217 218 @Override 219 public void setTintList(ColorStateList tint) { 220 mAnimatedVectorState.mVectorDrawable.setTintList(tint); 221 } 222 223 @Override 224 public void setHotspot(float x, float y) { 225 mAnimatedVectorState.mVectorDrawable.setHotspot(x, y); 226 } 227 228 @Override 229 public void setHotspotBounds(int left, int top, int right, int bottom) { 230 mAnimatedVectorState.mVectorDrawable.setHotspotBounds(left, top, right, bottom); 231 } 232 233 @Override 234 public void setTintMode(PorterDuff.Mode tintMode) { 235 mAnimatedVectorState.mVectorDrawable.setTintMode(tintMode); 236 } 237 238 @Override 239 public boolean setVisible(boolean visible, boolean restart) { 240 mAnimatedVectorState.mVectorDrawable.setVisible(visible, restart); 241 return super.setVisible(visible, restart); 242 } 243 244 /** {@hide} */ 245 @Override 246 public void setLayoutDirection(int layoutDirection) { 247 mAnimatedVectorState.mVectorDrawable.setLayoutDirection(layoutDirection); 248 } 249 250 @Override 251 public boolean isStateful() { 252 return mAnimatedVectorState.mVectorDrawable.isStateful(); 253 } 254 255 @Override 256 public int getOpacity() { 257 return mAnimatedVectorState.mVectorDrawable.getOpacity(); 258 } 259 260 @Override 261 public int getIntrinsicWidth() { 262 return mAnimatedVectorState.mVectorDrawable.getIntrinsicWidth(); 263 } 264 265 @Override 266 public int getIntrinsicHeight() { 267 return mAnimatedVectorState.mVectorDrawable.getIntrinsicHeight(); 268 } 269 270 @Override 271 public void getOutline(@NonNull Outline outline) { 272 mAnimatedVectorState.mVectorDrawable.getOutline(outline); 273 } 274 275 @Override 276 public void inflate(Resources res, XmlPullParser parser, AttributeSet attrs, Theme theme) 277 throws XmlPullParserException, IOException { 278 279 int eventType = parser.getEventType(); 280 float pathErrorScale = 1; 281 while (eventType != XmlPullParser.END_DOCUMENT) { 282 if (eventType == XmlPullParser.START_TAG) { 283 final String tagName = parser.getName(); 284 if (ANIMATED_VECTOR.equals(tagName)) { 285 final TypedArray a = obtainAttributes(res, theme, attrs, 286 R.styleable.AnimatedVectorDrawable); 287 int drawableRes = a.getResourceId( 288 R.styleable.AnimatedVectorDrawable_drawable, 0); 289 if (drawableRes != 0) { 290 VectorDrawable vectorDrawable = (VectorDrawable) res.getDrawable( 291 drawableRes, theme).mutate(); 292 vectorDrawable.setAllowCaching(false); 293 pathErrorScale = vectorDrawable.getPixelSize(); 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 super.canApplyTheme() || mAnimatedVectorState != null 321 && mAnimatedVectorState.mVectorDrawable != null 322 && mAnimatedVectorState.mVectorDrawable.canApplyTheme(); 323 } 324 325 @Override 326 public void applyTheme(Theme t) { 327 super.applyTheme(t); 328 329 final VectorDrawable vectorDrawable = mAnimatedVectorState.mVectorDrawable; 330 if (vectorDrawable != null && vectorDrawable.canApplyTheme()) { 331 vectorDrawable.applyTheme(t); 332 } 333 } 334 335 private static class AnimatedVectorDrawableState extends ConstantState { 336 int mChangingConfigurations; 337 VectorDrawable mVectorDrawable; 338 ArrayList<Animator> mAnimators; 339 ArrayMap<Animator, String> mTargetNameMap; 340 341 public AnimatedVectorDrawableState(AnimatedVectorDrawableState copy) { 342 if (copy != null) { 343 mChangingConfigurations = copy.mChangingConfigurations; 344 if (copy.mVectorDrawable != null) { 345 mVectorDrawable = (VectorDrawable) copy.mVectorDrawable.getConstantState().newDrawable(); 346 mVectorDrawable.mutate(); 347 mVectorDrawable.setAllowCaching(false); 348 mVectorDrawable.setBounds(copy.mVectorDrawable.getBounds()); 349 } 350 if (copy.mAnimators != null) { 351 final int numAnimators = copy.mAnimators.size(); 352 mAnimators = new ArrayList<Animator>(numAnimators); 353 mTargetNameMap = new ArrayMap<Animator, String>(numAnimators); 354 for (int i = 0; i < numAnimators; ++i) { 355 Animator anim = copy.mAnimators.get(i); 356 Animator animClone = anim.clone(); 357 String targetName = copy.mTargetNameMap.get(anim); 358 Object targetObject = mVectorDrawable.getTargetByName(targetName); 359 animClone.setTarget(targetObject); 360 mAnimators.add(animClone); 361 mTargetNameMap.put(animClone, targetName); 362 } 363 } 364 } else { 365 mVectorDrawable = new VectorDrawable(); 366 } 367 } 368 369 @Override 370 public Drawable newDrawable() { 371 return new AnimatedVectorDrawable(this, null, null); 372 } 373 374 @Override 375 public Drawable newDrawable(Resources res) { 376 return new AnimatedVectorDrawable(this, res, null); 377 } 378 379 @Override 380 public Drawable newDrawable(Resources res, Theme theme) { 381 return new AnimatedVectorDrawable(this, res, theme); 382 } 383 384 @Override 385 public int getChangingConfigurations() { 386 return mChangingConfigurations; 387 } 388 } 389 390 private void setupAnimatorsForTarget(String name, Animator animator) { 391 Object target = mAnimatedVectorState.mVectorDrawable.getTargetByName(name); 392 animator.setTarget(target); 393 if (mAnimatedVectorState.mAnimators == null) { 394 mAnimatedVectorState.mAnimators = new ArrayList<Animator>(); 395 mAnimatedVectorState.mTargetNameMap = new ArrayMap<Animator, String>(); 396 } 397 mAnimatedVectorState.mAnimators.add(animator); 398 mAnimatedVectorState.mTargetNameMap.put(animator, name); 399 if (DBG_ANIMATION_VECTOR_DRAWABLE) { 400 Log.v(LOGTAG, "add animator for target " + name + " " + animator); 401 } 402 } 403 404 @Override 405 public boolean isRunning() { 406 final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators; 407 final int size = animators.size(); 408 for (int i = 0; i < size; i++) { 409 final Animator animator = animators.get(i); 410 if (animator.isRunning()) { 411 return true; 412 } 413 } 414 return false; 415 } 416 417 private boolean isStarted() { 418 final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators; 419 final int size = animators.size(); 420 for (int i = 0; i < size; i++) { 421 final Animator animator = animators.get(i); 422 if (animator.isStarted()) { 423 return true; 424 } 425 } 426 return false; 427 } 428 429 @Override 430 public void start() { 431 final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators; 432 final int size = animators.size(); 433 for (int i = 0; i < size; i++) { 434 final Animator animator = animators.get(i); 435 if (!animator.isStarted()) { 436 animator.start(); 437 } 438 } 439 invalidateSelf(); 440 } 441 442 @Override 443 public void stop() { 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.end(); 449 } 450 } 451 452 /** 453 * Reverses ongoing animations or starts pending animations in reverse. 454 * <p> 455 * NOTE: Only works of all animations are ValueAnimators. 456 * @hide 457 */ 458 public void reverse() { 459 final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators; 460 final int size = animators.size(); 461 for (int i = 0; i < size; i++) { 462 final Animator animator = animators.get(i); 463 if (animator.canReverse()) { 464 animator.reverse(); 465 } else { 466 Log.w(LOGTAG, "AnimatedVectorDrawable can't reverse()"); 467 } 468 } 469 } 470 471 /** 472 * @hide 473 */ 474 public boolean canReverse() { 475 final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators; 476 final int size = animators.size(); 477 for (int i = 0; i < size; i++) { 478 final Animator animator = animators.get(i); 479 if (!animator.canReverse()) { 480 return false; 481 } 482 } 483 return true; 484 } 485} 486