AnimatedVectorDrawable.java revision fd3c4744f265c5277e6e2641a18d5ec3dff19f6b
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.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 final AnimatedVectorDrawableState mAnimatedVectorState; 135 136 public AnimatedVectorDrawable() { 137 mAnimatedVectorState = new AnimatedVectorDrawableState( 138 new AnimatedVectorDrawableState(null)); 139 } 140 141 private AnimatedVectorDrawable(AnimatedVectorDrawableState state, Resources res, 142 Theme theme) { 143 // TODO: Correctly handle the constant state for AVD. 144 mAnimatedVectorState = new AnimatedVectorDrawableState(state); 145 if (theme != null && canApplyTheme()) { 146 applyTheme(theme); 147 } 148 } 149 150 @Override 151 public ConstantState getConstantState() { 152 return null; 153 } 154 155 @Override 156 public void draw(Canvas canvas) { 157 mAnimatedVectorState.mVectorDrawable.draw(canvas); 158 if (isStarted()) { 159 invalidateSelf(); 160 } 161 } 162 163 @Override 164 protected void onBoundsChange(Rect bounds) { 165 mAnimatedVectorState.mVectorDrawable.setBounds(bounds); 166 } 167 168 @Override 169 protected boolean onStateChange(int[] state) { 170 return mAnimatedVectorState.mVectorDrawable.setState(state); 171 } 172 173 @Override 174 protected boolean onLevelChange(int level) { 175 return mAnimatedVectorState.mVectorDrawable.setLevel(level); 176 } 177 178 @Override 179 public int getAlpha() { 180 return mAnimatedVectorState.mVectorDrawable.getAlpha(); 181 } 182 183 @Override 184 public void setAlpha(int alpha) { 185 mAnimatedVectorState.mVectorDrawable.setAlpha(alpha); 186 } 187 188 @Override 189 public void setColorFilter(ColorFilter colorFilter) { 190 mAnimatedVectorState.mVectorDrawable.setColorFilter(colorFilter); 191 } 192 193 @Override 194 public void setTintList(ColorStateList tint) { 195 mAnimatedVectorState.mVectorDrawable.setTintList(tint); 196 } 197 198 @Override 199 public void setHotspot(float x, float y) { 200 mAnimatedVectorState.mVectorDrawable.setHotspot(x, y); 201 } 202 203 @Override 204 public void setHotspotBounds(int left, int top, int right, int bottom) { 205 mAnimatedVectorState.mVectorDrawable.setHotspotBounds(left, top, right, bottom); 206 } 207 208 @Override 209 public void setTintMode(PorterDuff.Mode tintMode) { 210 mAnimatedVectorState.mVectorDrawable.setTintMode(tintMode); 211 } 212 213 @Override 214 public boolean setVisible(boolean visible, boolean restart) { 215 mAnimatedVectorState.mVectorDrawable.setVisible(visible, restart); 216 return super.setVisible(visible, restart); 217 } 218 219 /** {@hide} */ 220 @Override 221 public void setLayoutDirection(int layoutDirection) { 222 mAnimatedVectorState.mVectorDrawable.setLayoutDirection(layoutDirection); 223 } 224 225 @Override 226 public boolean isStateful() { 227 return mAnimatedVectorState.mVectorDrawable.isStateful(); 228 } 229 230 @Override 231 public int getOpacity() { 232 return mAnimatedVectorState.mVectorDrawable.getOpacity(); 233 } 234 235 @Override 236 public int getIntrinsicWidth() { 237 return mAnimatedVectorState.mVectorDrawable.getIntrinsicWidth(); 238 } 239 240 @Override 241 public int getIntrinsicHeight() { 242 return mAnimatedVectorState.mVectorDrawable.getIntrinsicHeight(); 243 } 244 245 @Override 246 public void getOutline(@NonNull Outline outline) { 247 mAnimatedVectorState.mVectorDrawable.getOutline(outline); 248 } 249 250 @Override 251 public void inflate(Resources res, XmlPullParser parser, AttributeSet attrs, Theme theme) 252 throws XmlPullParserException, IOException { 253 254 int eventType = parser.getEventType(); 255 float pathErrorScale = 1; 256 while (eventType != XmlPullParser.END_DOCUMENT) { 257 if (eventType == XmlPullParser.START_TAG) { 258 final String tagName = parser.getName(); 259 if (ANIMATED_VECTOR.equals(tagName)) { 260 final TypedArray a = obtainAttributes(res, theme, attrs, 261 R.styleable.AnimatedVectorDrawable); 262 int drawableRes = a.getResourceId( 263 R.styleable.AnimatedVectorDrawable_drawable, 0); 264 if (drawableRes != 0) { 265 VectorDrawable vectorDrawable = (VectorDrawable) res.getDrawable( 266 drawableRes, theme).mutate(); 267 vectorDrawable.setAllowCaching(false); 268 pathErrorScale = vectorDrawable.getPixelSize(); 269 mAnimatedVectorState.mVectorDrawable = vectorDrawable; 270 } 271 a.recycle(); 272 } else if (TARGET.equals(tagName)) { 273 final TypedArray a = obtainAttributes(res, theme, attrs, 274 R.styleable.AnimatedVectorDrawableTarget); 275 final String target = a.getString( 276 R.styleable.AnimatedVectorDrawableTarget_name); 277 278 int id = a.getResourceId( 279 R.styleable.AnimatedVectorDrawableTarget_animation, 0); 280 if (id != 0) { 281 Animator objectAnimator = AnimatorInflater.loadAnimator(res, theme, id, 282 pathErrorScale); 283 setupAnimatorsForTarget(target, objectAnimator); 284 } 285 a.recycle(); 286 } 287 } 288 289 eventType = parser.next(); 290 } 291 } 292 293 @Override 294 public boolean canApplyTheme() { 295 return super.canApplyTheme() || mAnimatedVectorState != null 296 && mAnimatedVectorState.mVectorDrawable != null 297 && mAnimatedVectorState.mVectorDrawable.canApplyTheme(); 298 } 299 300 @Override 301 public void applyTheme(Theme t) { 302 super.applyTheme(t); 303 304 final VectorDrawable vectorDrawable = mAnimatedVectorState.mVectorDrawable; 305 if (vectorDrawable != null && vectorDrawable.canApplyTheme()) { 306 vectorDrawable.applyTheme(t); 307 } 308 } 309 310 private static class AnimatedVectorDrawableState extends ConstantState { 311 int mChangingConfigurations; 312 VectorDrawable mVectorDrawable; 313 ArrayList<Animator> mAnimators; 314 315 public AnimatedVectorDrawableState(AnimatedVectorDrawableState copy) { 316 if (copy != null) { 317 mChangingConfigurations = copy.mChangingConfigurations; 318 // TODO: Make sure the constant state are handled correctly. 319 mVectorDrawable = new VectorDrawable(); 320 mVectorDrawable.setAllowCaching(false); 321 mAnimators = new ArrayList<Animator>(); 322 } 323 } 324 325 @Override 326 public Drawable newDrawable() { 327 return new AnimatedVectorDrawable(this, null, null); 328 } 329 330 @Override 331 public Drawable newDrawable(Resources res) { 332 return new AnimatedVectorDrawable(this, res, null); 333 } 334 335 @Override 336 public Drawable newDrawable(Resources res, Theme theme) { 337 return new AnimatedVectorDrawable(this, res, theme); 338 } 339 340 @Override 341 public int getChangingConfigurations() { 342 return mChangingConfigurations; 343 } 344 } 345 346 private void setupAnimatorsForTarget(String name, Animator animator) { 347 Object target = mAnimatedVectorState.mVectorDrawable.getTargetByName(name); 348 animator.setTarget(target); 349 mAnimatedVectorState.mAnimators.add(animator); 350 if (DBG_ANIMATION_VECTOR_DRAWABLE) { 351 Log.v(LOGTAG, "add animator for target " + name + " " + animator); 352 } 353 } 354 355 @Override 356 public boolean isRunning() { 357 final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators; 358 final int size = animators.size(); 359 for (int i = 0; i < size; i++) { 360 final Animator animator = animators.get(i); 361 if (animator.isRunning()) { 362 return true; 363 } 364 } 365 return false; 366 } 367 368 private boolean isStarted() { 369 final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators; 370 final int size = animators.size(); 371 for (int i = 0; i < size; i++) { 372 final Animator animator = animators.get(i); 373 if (animator.isStarted()) { 374 return true; 375 } 376 } 377 return false; 378 } 379 380 @Override 381 public void start() { 382 final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators; 383 final int size = animators.size(); 384 for (int i = 0; i < size; i++) { 385 final Animator animator = animators.get(i); 386 if (!animator.isStarted()) { 387 animator.start(); 388 } 389 } 390 invalidateSelf(); 391 } 392 393 @Override 394 public void stop() { 395 final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators; 396 final int size = animators.size(); 397 for (int i = 0; i < size; i++) { 398 final Animator animator = animators.get(i); 399 animator.end(); 400 } 401 } 402 403 /** 404 * Reverses ongoing animations or starts pending animations in reverse. 405 * <p> 406 * NOTE: Only works of all animations are ValueAnimators. 407 * @hide 408 */ 409 public void reverse() { 410 final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators; 411 final int size = animators.size(); 412 for (int i = 0; i < size; i++) { 413 final Animator animator = animators.get(i); 414 if (animator.canReverse()) { 415 animator.reverse(); 416 } else { 417 Log.w(LOGTAG, "AnimatedVectorDrawable can't reverse()"); 418 } 419 } 420 } 421 422 /** 423 * @hide 424 */ 425 public boolean canReverse() { 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.canReverse()) { 431 return false; 432 } 433 } 434 return true; 435 } 436} 437