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