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