1 /* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package android.animation; 17 18 import android.content.Context; 19 import android.content.res.Resources; 20 import android.content.res.TypedArray; 21 import android.content.res.XmlResourceParser; 22 import android.content.res.Resources.NotFoundException; 23 import android.util.AttributeSet; 24 import android.util.TypedValue; 25 import android.util.Xml; 26 import android.view.animation.AnimationUtils; 27 import org.xmlpull.v1.XmlPullParser; 28 import org.xmlpull.v1.XmlPullParserException; 29 30 import java.io.IOException; 31 import java.util.ArrayList; 32 33 /** 34 * This class is used to instantiate animator XML files into Animator objects. 35 * <p> 36 * For performance reasons, inflation relies heavily on pre-processing of 37 * XML files that is done at build time. Therefore, it is not currently possible 38 * to use this inflater with an XmlPullParser over a plain XML file at runtime; 39 * it only works with an XmlPullParser returned from a compiled resource (R. 40 * <em>something</em> file.) 41 */ 42 public class AnimatorInflater { 43 44 /** 45 * These flags are used when parsing AnimatorSet objects 46 */ 47 private static final int TOGETHER = 0; 48 private static final int SEQUENTIALLY = 1; 49 50 /** 51 * Enum values used in XML attributes to indicate the value for mValueType 52 */ 53 private static final int VALUE_TYPE_FLOAT = 0; 54 private static final int VALUE_TYPE_INT = 1; 55 private static final int VALUE_TYPE_COLOR = 4; 56 private static final int VALUE_TYPE_CUSTOM = 5; 57 58 /** 59 * Loads an {@link Animator} object from a resource 60 * 61 * @param context Application context used to access resources 62 * @param id The resource id of the animation to load 63 * @return The animator object reference by the specified id 64 * @throws android.content.res.Resources.NotFoundException when the animation cannot be loaded 65 */ loadAnimator(Context context, int id)66 public static Animator loadAnimator(Context context, int id) 67 throws NotFoundException { 68 69 XmlResourceParser parser = null; 70 try { 71 parser = context.getResources().getAnimation(id); 72 return createAnimatorFromXml(context, parser); 73 } catch (XmlPullParserException ex) { 74 Resources.NotFoundException rnf = 75 new Resources.NotFoundException("Can't load animation resource ID #0x" + 76 Integer.toHexString(id)); 77 rnf.initCause(ex); 78 throw rnf; 79 } catch (IOException ex) { 80 Resources.NotFoundException rnf = 81 new Resources.NotFoundException("Can't load animation resource ID #0x" + 82 Integer.toHexString(id)); 83 rnf.initCause(ex); 84 throw rnf; 85 } finally { 86 if (parser != null) parser.close(); 87 } 88 } 89 createAnimatorFromXml(Context c, XmlPullParser parser)90 private static Animator createAnimatorFromXml(Context c, XmlPullParser parser) 91 throws XmlPullParserException, IOException { 92 93 return createAnimatorFromXml(c, parser, Xml.asAttributeSet(parser), null, 0); 94 } 95 createAnimatorFromXml(Context c, XmlPullParser parser, AttributeSet attrs, AnimatorSet parent, int sequenceOrdering)96 private static Animator createAnimatorFromXml(Context c, XmlPullParser parser, 97 AttributeSet attrs, AnimatorSet parent, int sequenceOrdering) 98 throws XmlPullParserException, IOException { 99 100 Animator anim = null; 101 ArrayList<Animator> childAnims = null; 102 103 // Make sure we are on a start tag. 104 int type; 105 int depth = parser.getDepth(); 106 107 while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) 108 && type != XmlPullParser.END_DOCUMENT) { 109 110 if (type != XmlPullParser.START_TAG) { 111 continue; 112 } 113 114 String name = parser.getName(); 115 116 if (name.equals("objectAnimator")) { 117 anim = loadObjectAnimator(c, attrs); 118 } else if (name.equals("animator")) { 119 anim = loadAnimator(c, attrs, null); 120 } else if (name.equals("set")) { 121 anim = new AnimatorSet(); 122 TypedArray a = c.obtainStyledAttributes(attrs, 123 com.android.internal.R.styleable.AnimatorSet); 124 int ordering = a.getInt(com.android.internal.R.styleable.AnimatorSet_ordering, 125 TOGETHER); 126 createAnimatorFromXml(c, parser, attrs, (AnimatorSet) anim, ordering); 127 a.recycle(); 128 } else { 129 throw new RuntimeException("Unknown animator name: " + parser.getName()); 130 } 131 132 if (parent != null) { 133 if (childAnims == null) { 134 childAnims = new ArrayList<Animator>(); 135 } 136 childAnims.add(anim); 137 } 138 } 139 if (parent != null && childAnims != null) { 140 Animator[] animsArray = new Animator[childAnims.size()]; 141 int index = 0; 142 for (Animator a : childAnims) { 143 animsArray[index++] = a; 144 } 145 if (sequenceOrdering == TOGETHER) { 146 parent.playTogether(animsArray); 147 } else { 148 parent.playSequentially(animsArray); 149 } 150 } 151 152 return anim; 153 154 } 155 loadObjectAnimator(Context context, AttributeSet attrs)156 private static ObjectAnimator loadObjectAnimator(Context context, AttributeSet attrs) 157 throws NotFoundException { 158 159 ObjectAnimator anim = new ObjectAnimator(); 160 161 loadAnimator(context, attrs, anim); 162 163 TypedArray a = 164 context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.PropertyAnimator); 165 166 String propertyName = a.getString(com.android.internal.R.styleable.PropertyAnimator_propertyName); 167 168 anim.setPropertyName(propertyName); 169 170 a.recycle(); 171 172 return anim; 173 } 174 175 /** 176 * Creates a new animation whose parameters come from the specified context and 177 * attributes set. 178 * 179 * @param context the application environment 180 * @param attrs the set of attributes holding the animation parameters 181 */ loadAnimator(Context context, AttributeSet attrs, ValueAnimator anim)182 private static ValueAnimator loadAnimator(Context context, AttributeSet attrs, ValueAnimator anim) 183 throws NotFoundException { 184 185 TypedArray a = 186 context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.Animator); 187 188 long duration = a.getInt(com.android.internal.R.styleable.Animator_duration, 0); 189 190 long startDelay = a.getInt(com.android.internal.R.styleable.Animator_startOffset, 0); 191 192 int valueType = a.getInt(com.android.internal.R.styleable.Animator_valueType, 193 VALUE_TYPE_FLOAT); 194 195 if (anim == null) { 196 anim = new ValueAnimator(); 197 } 198 TypeEvaluator evaluator = null; 199 200 int valueFromIndex = com.android.internal.R.styleable.Animator_valueFrom; 201 int valueToIndex = com.android.internal.R.styleable.Animator_valueTo; 202 203 boolean getFloats = (valueType == VALUE_TYPE_FLOAT); 204 205 TypedValue tvFrom = a.peekValue(valueFromIndex); 206 boolean hasFrom = (tvFrom != null); 207 int fromType = hasFrom ? tvFrom.type : 0; 208 TypedValue tvTo = a.peekValue(valueToIndex); 209 boolean hasTo = (tvTo != null); 210 int toType = hasTo ? tvTo.type : 0; 211 212 if ((hasFrom && (fromType >= TypedValue.TYPE_FIRST_COLOR_INT) && 213 (fromType <= TypedValue.TYPE_LAST_COLOR_INT)) || 214 (hasTo && (toType >= TypedValue.TYPE_FIRST_COLOR_INT) && 215 (toType <= TypedValue.TYPE_LAST_COLOR_INT))) { 216 // special case for colors: ignore valueType and get ints 217 getFloats = false; 218 anim.setEvaluator(new ArgbEvaluator()); 219 } 220 221 if (getFloats) { 222 float valueFrom; 223 float valueTo; 224 if (hasFrom) { 225 if (fromType == TypedValue.TYPE_DIMENSION) { 226 valueFrom = a.getDimension(valueFromIndex, 0f); 227 } else { 228 valueFrom = a.getFloat(valueFromIndex, 0f); 229 } 230 if (hasTo) { 231 if (toType == TypedValue.TYPE_DIMENSION) { 232 valueTo = a.getDimension(valueToIndex, 0f); 233 } else { 234 valueTo = a.getFloat(valueToIndex, 0f); 235 } 236 anim.setFloatValues(valueFrom, valueTo); 237 } else { 238 anim.setFloatValues(valueFrom); 239 } 240 } else { 241 if (toType == TypedValue.TYPE_DIMENSION) { 242 valueTo = a.getDimension(valueToIndex, 0f); 243 } else { 244 valueTo = a.getFloat(valueToIndex, 0f); 245 } 246 anim.setFloatValues(valueTo); 247 } 248 } else { 249 int valueFrom; 250 int valueTo; 251 if (hasFrom) { 252 if (fromType == TypedValue.TYPE_DIMENSION) { 253 valueFrom = (int) a.getDimension(valueFromIndex, 0f); 254 } else if ((fromType >= TypedValue.TYPE_FIRST_COLOR_INT) && 255 (fromType <= TypedValue.TYPE_LAST_COLOR_INT)) { 256 valueFrom = a.getColor(valueFromIndex, 0); 257 } else { 258 valueFrom = a.getInt(valueFromIndex, 0); 259 } 260 if (hasTo) { 261 if (toType == TypedValue.TYPE_DIMENSION) { 262 valueTo = (int) a.getDimension(valueToIndex, 0f); 263 } else if ((toType >= TypedValue.TYPE_FIRST_COLOR_INT) && 264 (toType <= TypedValue.TYPE_LAST_COLOR_INT)) { 265 valueTo = a.getColor(valueToIndex, 0); 266 } else { 267 valueTo = a.getInt(valueToIndex, 0); 268 } 269 anim.setIntValues(valueFrom, valueTo); 270 } else { 271 anim.setIntValues(valueFrom); 272 } 273 } else { 274 if (hasTo) { 275 if (toType == TypedValue.TYPE_DIMENSION) { 276 valueTo = (int) a.getDimension(valueToIndex, 0f); 277 } else if ((toType >= TypedValue.TYPE_FIRST_COLOR_INT) && 278 (toType <= TypedValue.TYPE_LAST_COLOR_INT)) { 279 valueTo = a.getColor(valueToIndex, 0); 280 } else { 281 valueTo = a.getInt(valueToIndex, 0); 282 } 283 anim.setIntValues(valueTo); 284 } 285 } 286 } 287 288 anim.setDuration(duration); 289 anim.setStartDelay(startDelay); 290 291 if (a.hasValue(com.android.internal.R.styleable.Animator_repeatCount)) { 292 anim.setRepeatCount( 293 a.getInt(com.android.internal.R.styleable.Animator_repeatCount, 0)); 294 } 295 if (a.hasValue(com.android.internal.R.styleable.Animator_repeatMode)) { 296 anim.setRepeatMode( 297 a.getInt(com.android.internal.R.styleable.Animator_repeatMode, 298 ValueAnimator.RESTART)); 299 } 300 if (evaluator != null) { 301 anim.setEvaluator(evaluator); 302 } 303 304 final int resID = 305 a.getResourceId(com.android.internal.R.styleable.Animator_interpolator, 0); 306 if (resID > 0) { 307 anim.setInterpolator(AnimationUtils.loadInterpolator(context, resID)); 308 } 309 a.recycle(); 310 311 return anim; 312 } 313 } 314