AnimatedVectorDrawable.java revision 06cd7dce24876b54870f9ef3831237f8298773a9
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 (isRunning()) { 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 while (eventType != XmlPullParser.END_DOCUMENT) { 256 if (eventType == XmlPullParser.START_TAG) { 257 final String tagName = parser.getName(); 258 if (ANIMATED_VECTOR.equals(tagName)) { 259 final TypedArray a = obtainAttributes(res, theme, attrs, 260 R.styleable.AnimatedVectorDrawable); 261 int drawableRes = a.getResourceId( 262 R.styleable.AnimatedVectorDrawable_drawable, 0); 263 if (drawableRes != 0) { 264 mAnimatedVectorState.mVectorDrawable = (VectorDrawable) res.getDrawable( 265 drawableRes, theme).mutate(); 266 mAnimatedVectorState.mVectorDrawable.setAllowCaching(false); 267 } 268 a.recycle(); 269 } else if (TARGET.equals(tagName)) { 270 final TypedArray a = obtainAttributes(res, theme, attrs, 271 R.styleable.AnimatedVectorDrawableTarget); 272 final String target = a.getString( 273 R.styleable.AnimatedVectorDrawableTarget_name); 274 275 int id = a.getResourceId( 276 R.styleable.AnimatedVectorDrawableTarget_animation, 0); 277 if (id != 0) { 278 Animator objectAnimator = AnimatorInflater.loadAnimator(res, theme, id); 279 setupAnimatorsForTarget(target, objectAnimator); 280 } 281 a.recycle(); 282 } 283 } 284 285 eventType = parser.next(); 286 } 287 } 288 289 @Override 290 public boolean canApplyTheme() { 291 return super.canApplyTheme() || mAnimatedVectorState != null 292 && mAnimatedVectorState.mVectorDrawable != null 293 && mAnimatedVectorState.mVectorDrawable.canApplyTheme(); 294 } 295 296 @Override 297 public void applyTheme(Theme t) { 298 super.applyTheme(t); 299 300 final VectorDrawable vectorDrawable = mAnimatedVectorState.mVectorDrawable; 301 if (vectorDrawable != null && vectorDrawable.canApplyTheme()) { 302 vectorDrawable.applyTheme(t); 303 } 304 } 305 306 private static class AnimatedVectorDrawableState extends ConstantState { 307 int mChangingConfigurations; 308 VectorDrawable mVectorDrawable; 309 ArrayList<Animator> mAnimators; 310 311 public AnimatedVectorDrawableState(AnimatedVectorDrawableState copy) { 312 if (copy != null) { 313 mChangingConfigurations = copy.mChangingConfigurations; 314 // TODO: Make sure the constant state are handled correctly. 315 mVectorDrawable = new VectorDrawable(); 316 mVectorDrawable.setAllowCaching(false); 317 mAnimators = new ArrayList<Animator>(); 318 } 319 } 320 321 @Override 322 public Drawable newDrawable() { 323 return new AnimatedVectorDrawable(this, null, null); 324 } 325 326 @Override 327 public Drawable newDrawable(Resources res) { 328 return new AnimatedVectorDrawable(this, res, null); 329 } 330 331 @Override 332 public Drawable newDrawable(Resources res, Theme theme) { 333 return new AnimatedVectorDrawable(this, res, theme); 334 } 335 336 @Override 337 public int getChangingConfigurations() { 338 return mChangingConfigurations; 339 } 340 } 341 342 private void setupAnimatorsForTarget(String name, Animator animator) { 343 Object target = mAnimatedVectorState.mVectorDrawable.getTargetByName(name); 344 animator.setTarget(target); 345 mAnimatedVectorState.mAnimators.add(animator); 346 if (DBG_ANIMATION_VECTOR_DRAWABLE) { 347 Log.v(LOGTAG, "add animator for target " + name + " " + animator); 348 } 349 } 350 351 @Override 352 public boolean isRunning() { 353 final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators; 354 final int size = animators.size(); 355 for (int i = 0; i < size; i++) { 356 final Animator animator = animators.get(i); 357 if (animator.isRunning()) { 358 return true; 359 } 360 } 361 return false; 362 } 363 364 @Override 365 public void start() { 366 final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators; 367 final int size = animators.size(); 368 for (int i = 0; i < size; i++) { 369 final Animator animator = animators.get(i); 370 if (!animator.isRunning()) { 371 animator.start(); 372 } 373 } 374 invalidateSelf(); 375 } 376 377 @Override 378 public void stop() { 379 final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators; 380 final int size = animators.size(); 381 for (int i = 0; i < size; i++) { 382 final Animator animator = animators.get(i); 383 animator.end(); 384 } 385 } 386 387 /** 388 * Reverses ongoing animations or starts pending animations in reverse. 389 * <p> 390 * NOTE: Only works of all animations are ValueAnimators. 391 * @hide 392 */ 393 public void reverse() { 394 final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators; 395 final int size = animators.size(); 396 for (int i = 0; i < size; i++) { 397 final Animator animator = animators.get(i); 398 if (animator.canReverse()) { 399 animator.reverse(); 400 } else { 401 Log.w(LOGTAG, "AnimatedVectorDrawable can't reverse()"); 402 } 403 } 404 } 405 406 /** 407 * @hide 408 */ 409 public boolean canReverse() { 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 return false; 416 } 417 } 418 return true; 419 } 420} 421