1 /* 2 * Copyright (C) 2007 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 17 package android.view.animation; 18 19 import org.xmlpull.v1.XmlPullParser; 20 import org.xmlpull.v1.XmlPullParserException; 21 22 import android.annotation.AnimRes; 23 import android.annotation.InterpolatorRes; 24 import android.content.Context; 25 import android.content.res.Resources; 26 import android.content.res.Resources.Theme; 27 import android.content.res.XmlResourceParser; 28 import android.content.res.Resources.NotFoundException; 29 import android.util.AttributeSet; 30 import android.util.Xml; 31 import android.os.SystemClock; 32 33 import java.io.IOException; 34 35 /** 36 * Defines common utilities for working with animations. 37 * 38 */ 39 public class AnimationUtils { 40 41 /** 42 * These flags are used when parsing AnimatorSet objects 43 */ 44 private static final int TOGETHER = 0; 45 private static final int SEQUENTIALLY = 1; 46 47 private static class AnimationState { 48 boolean animationClockLocked; 49 long currentVsyncTimeMillis; 50 long lastReportedTimeMillis; 51 }; 52 53 private static ThreadLocal<AnimationState> sAnimationState 54 = new ThreadLocal<AnimationState>() { 55 @Override 56 protected AnimationState initialValue() { 57 return new AnimationState(); 58 } 59 }; 60 61 /** @hide */ lockAnimationClock(long vsyncMillis)62 public static void lockAnimationClock(long vsyncMillis) { 63 AnimationState state = sAnimationState.get(); 64 state.animationClockLocked = true; 65 state.currentVsyncTimeMillis = vsyncMillis; 66 } 67 68 /** @hide */ unlockAnimationClock()69 public static void unlockAnimationClock() { 70 sAnimationState.get().animationClockLocked = false; 71 } 72 73 /** 74 * Returns the current animation time in milliseconds. This time should be used when invoking 75 * {@link Animation#setStartTime(long)}. Refer to {@link android.os.SystemClock} for more 76 * information about the different available clocks. The clock used by this method is 77 * <em>not</em> the "wall" clock (it is not {@link System#currentTimeMillis}). 78 * 79 * @return the current animation time in milliseconds 80 * 81 * @see android.os.SystemClock 82 */ currentAnimationTimeMillis()83 public static long currentAnimationTimeMillis() { 84 AnimationState state = sAnimationState.get(); 85 if (state.animationClockLocked) { 86 // It's important that time never rewinds 87 return Math.max(state.currentVsyncTimeMillis, 88 state.lastReportedTimeMillis); 89 } 90 state.lastReportedTimeMillis = SystemClock.uptimeMillis(); 91 return state.lastReportedTimeMillis; 92 } 93 94 /** 95 * Loads an {@link Animation} object from a resource 96 * 97 * @param context Application context used to access resources 98 * @param id The resource id of the animation to load 99 * @return The animation object reference by the specified id 100 * @throws NotFoundException when the animation cannot be loaded 101 */ loadAnimation(Context context, @AnimRes int id)102 public static Animation loadAnimation(Context context, @AnimRes int id) 103 throws NotFoundException { 104 105 XmlResourceParser parser = null; 106 try { 107 parser = context.getResources().getAnimation(id); 108 return createAnimationFromXml(context, parser); 109 } catch (XmlPullParserException ex) { 110 NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" + 111 Integer.toHexString(id)); 112 rnf.initCause(ex); 113 throw rnf; 114 } catch (IOException ex) { 115 NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" + 116 Integer.toHexString(id)); 117 rnf.initCause(ex); 118 throw rnf; 119 } finally { 120 if (parser != null) parser.close(); 121 } 122 } 123 createAnimationFromXml(Context c, XmlPullParser parser)124 private static Animation createAnimationFromXml(Context c, XmlPullParser parser) 125 throws XmlPullParserException, IOException { 126 127 return createAnimationFromXml(c, parser, null, Xml.asAttributeSet(parser)); 128 } 129 createAnimationFromXml(Context c, XmlPullParser parser, AnimationSet parent, AttributeSet attrs)130 private static Animation createAnimationFromXml(Context c, XmlPullParser parser, 131 AnimationSet parent, AttributeSet attrs) throws XmlPullParserException, IOException { 132 133 Animation anim = null; 134 135 // Make sure we are on a start tag. 136 int type; 137 int depth = parser.getDepth(); 138 139 while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) 140 && type != XmlPullParser.END_DOCUMENT) { 141 142 if (type != XmlPullParser.START_TAG) { 143 continue; 144 } 145 146 String name = parser.getName(); 147 148 if (name.equals("set")) { 149 anim = new AnimationSet(c, attrs); 150 createAnimationFromXml(c, parser, (AnimationSet)anim, attrs); 151 } else if (name.equals("alpha")) { 152 anim = new AlphaAnimation(c, attrs); 153 } else if (name.equals("scale")) { 154 anim = new ScaleAnimation(c, attrs); 155 } else if (name.equals("rotate")) { 156 anim = new RotateAnimation(c, attrs); 157 } else if (name.equals("translate")) { 158 anim = new TranslateAnimation(c, attrs); 159 } else { 160 throw new RuntimeException("Unknown animation name: " + parser.getName()); 161 } 162 163 if (parent != null) { 164 parent.addAnimation(anim); 165 } 166 } 167 168 return anim; 169 170 } 171 172 /** 173 * Loads a {@link LayoutAnimationController} object from a resource 174 * 175 * @param context Application context used to access resources 176 * @param id The resource id of the animation to load 177 * @return The animation object reference by the specified id 178 * @throws NotFoundException when the layout animation controller cannot be loaded 179 */ loadLayoutAnimation(Context context, @AnimRes int id)180 public static LayoutAnimationController loadLayoutAnimation(Context context, @AnimRes int id) 181 throws NotFoundException { 182 183 XmlResourceParser parser = null; 184 try { 185 parser = context.getResources().getAnimation(id); 186 return createLayoutAnimationFromXml(context, parser); 187 } catch (XmlPullParserException ex) { 188 NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" + 189 Integer.toHexString(id)); 190 rnf.initCause(ex); 191 throw rnf; 192 } catch (IOException ex) { 193 NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" + 194 Integer.toHexString(id)); 195 rnf.initCause(ex); 196 throw rnf; 197 } finally { 198 if (parser != null) parser.close(); 199 } 200 } 201 createLayoutAnimationFromXml(Context c, XmlPullParser parser)202 private static LayoutAnimationController createLayoutAnimationFromXml(Context c, 203 XmlPullParser parser) throws XmlPullParserException, IOException { 204 205 return createLayoutAnimationFromXml(c, parser, Xml.asAttributeSet(parser)); 206 } 207 createLayoutAnimationFromXml(Context c, XmlPullParser parser, AttributeSet attrs)208 private static LayoutAnimationController createLayoutAnimationFromXml(Context c, 209 XmlPullParser parser, AttributeSet attrs) throws XmlPullParserException, IOException { 210 211 LayoutAnimationController controller = null; 212 213 int type; 214 int depth = parser.getDepth(); 215 216 while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) 217 && type != XmlPullParser.END_DOCUMENT) { 218 219 if (type != XmlPullParser.START_TAG) { 220 continue; 221 } 222 223 String name = parser.getName(); 224 225 if ("layoutAnimation".equals(name)) { 226 controller = new LayoutAnimationController(c, attrs); 227 } else if ("gridLayoutAnimation".equals(name)) { 228 controller = new GridLayoutAnimationController(c, attrs); 229 } else { 230 throw new RuntimeException("Unknown layout animation name: " + name); 231 } 232 } 233 234 return controller; 235 } 236 237 /** 238 * Make an animation for objects becoming visible. Uses a slide and fade 239 * effect. 240 * 241 * @param c Context for loading resources 242 * @param fromLeft is the object to be animated coming from the left 243 * @return The new animation 244 */ makeInAnimation(Context c, boolean fromLeft)245 public static Animation makeInAnimation(Context c, boolean fromLeft) { 246 Animation a; 247 if (fromLeft) { 248 a = AnimationUtils.loadAnimation(c, com.android.internal.R.anim.slide_in_left); 249 } else { 250 a = AnimationUtils.loadAnimation(c, com.android.internal.R.anim.slide_in_right); 251 } 252 253 a.setInterpolator(new DecelerateInterpolator()); 254 a.setStartTime(currentAnimationTimeMillis()); 255 return a; 256 } 257 258 /** 259 * Make an animation for objects becoming invisible. Uses a slide and fade 260 * effect. 261 * 262 * @param c Context for loading resources 263 * @param toRight is the object to be animated exiting to the right 264 * @return The new animation 265 */ makeOutAnimation(Context c, boolean toRight)266 public static Animation makeOutAnimation(Context c, boolean toRight) { 267 Animation a; 268 if (toRight) { 269 a = AnimationUtils.loadAnimation(c, com.android.internal.R.anim.slide_out_right); 270 } else { 271 a = AnimationUtils.loadAnimation(c, com.android.internal.R.anim.slide_out_left); 272 } 273 274 a.setInterpolator(new AccelerateInterpolator()); 275 a.setStartTime(currentAnimationTimeMillis()); 276 return a; 277 } 278 279 280 /** 281 * Make an animation for objects becoming visible. Uses a slide up and fade 282 * effect. 283 * 284 * @param c Context for loading resources 285 * @return The new animation 286 */ makeInChildBottomAnimation(Context c)287 public static Animation makeInChildBottomAnimation(Context c) { 288 Animation a; 289 a = AnimationUtils.loadAnimation(c, com.android.internal.R.anim.slide_in_child_bottom); 290 a.setInterpolator(new AccelerateInterpolator()); 291 a.setStartTime(currentAnimationTimeMillis()); 292 return a; 293 } 294 295 /** 296 * Loads an {@link Interpolator} object from a resource 297 * 298 * @param context Application context used to access resources 299 * @param id The resource id of the animation to load 300 * @return The animation object reference by the specified id 301 * @throws NotFoundException 302 */ loadInterpolator(Context context, @AnimRes @InterpolatorRes int id)303 public static Interpolator loadInterpolator(Context context, @AnimRes @InterpolatorRes int id) 304 throws NotFoundException { 305 XmlResourceParser parser = null; 306 try { 307 parser = context.getResources().getAnimation(id); 308 return createInterpolatorFromXml(context.getResources(), context.getTheme(), parser); 309 } catch (XmlPullParserException ex) { 310 NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" + 311 Integer.toHexString(id)); 312 rnf.initCause(ex); 313 throw rnf; 314 } catch (IOException ex) { 315 NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" + 316 Integer.toHexString(id)); 317 rnf.initCause(ex); 318 throw rnf; 319 } finally { 320 if (parser != null) parser.close(); 321 } 322 323 } 324 325 /** 326 * Loads an {@link Interpolator} object from a resource 327 * 328 * @param res The resources 329 * @param id The resource id of the animation to load 330 * @return The interpolator object reference by the specified id 331 * @throws NotFoundException 332 * @hide 333 */ loadInterpolator(Resources res, Theme theme, int id)334 public static Interpolator loadInterpolator(Resources res, Theme theme, int id) throws NotFoundException { 335 XmlResourceParser parser = null; 336 try { 337 parser = res.getAnimation(id); 338 return createInterpolatorFromXml(res, theme, parser); 339 } catch (XmlPullParserException ex) { 340 NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" + 341 Integer.toHexString(id)); 342 rnf.initCause(ex); 343 throw rnf; 344 } catch (IOException ex) { 345 NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" + 346 Integer.toHexString(id)); 347 rnf.initCause(ex); 348 throw rnf; 349 } finally { 350 if (parser != null) 351 parser.close(); 352 } 353 354 } 355 createInterpolatorFromXml(Resources res, Theme theme, XmlPullParser parser)356 private static Interpolator createInterpolatorFromXml(Resources res, Theme theme, XmlPullParser parser) 357 throws XmlPullParserException, IOException { 358 359 BaseInterpolator interpolator = null; 360 361 // Make sure we are on a start tag. 362 int type; 363 int depth = parser.getDepth(); 364 365 while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) 366 && type != XmlPullParser.END_DOCUMENT) { 367 368 if (type != XmlPullParser.START_TAG) { 369 continue; 370 } 371 372 AttributeSet attrs = Xml.asAttributeSet(parser); 373 374 String name = parser.getName(); 375 376 if (name.equals("linearInterpolator")) { 377 interpolator = new LinearInterpolator(); 378 } else if (name.equals("accelerateInterpolator")) { 379 interpolator = new AccelerateInterpolator(res, theme, attrs); 380 } else if (name.equals("decelerateInterpolator")) { 381 interpolator = new DecelerateInterpolator(res, theme, attrs); 382 } else if (name.equals("accelerateDecelerateInterpolator")) { 383 interpolator = new AccelerateDecelerateInterpolator(); 384 } else if (name.equals("cycleInterpolator")) { 385 interpolator = new CycleInterpolator(res, theme, attrs); 386 } else if (name.equals("anticipateInterpolator")) { 387 interpolator = new AnticipateInterpolator(res, theme, attrs); 388 } else if (name.equals("overshootInterpolator")) { 389 interpolator = new OvershootInterpolator(res, theme, attrs); 390 } else if (name.equals("anticipateOvershootInterpolator")) { 391 interpolator = new AnticipateOvershootInterpolator(res, theme, attrs); 392 } else if (name.equals("bounceInterpolator")) { 393 interpolator = new BounceInterpolator(); 394 } else if (name.equals("pathInterpolator")) { 395 interpolator = new PathInterpolator(res, theme, attrs); 396 } else { 397 throw new RuntimeException("Unknown interpolator name: " + parser.getName()); 398 } 399 } 400 return interpolator; 401 } 402 } 403