/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.view.animation;

import android.annotation.AnimRes;
import android.annotation.InterpolatorRes;
import android.annotation.TestApi;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.Resources.NotFoundException;
import android.content.res.Resources.Theme;
import android.content.res.XmlResourceParser;
import android.os.SystemClock;
import android.util.AttributeSet;
import android.util.Xml;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

import java.io.IOException;

Defines common utilities for working with animations.
/** * Defines common utilities for working with animations. * */
public class AnimationUtils {
These flags are used when parsing AnimatorSet objects
/** * These flags are used when parsing AnimatorSet objects */
private static final int TOGETHER = 0; private static final int SEQUENTIALLY = 1; private static class AnimationState { boolean animationClockLocked; long currentVsyncTimeMillis; long lastReportedTimeMillis; }; private static ThreadLocal<AnimationState> sAnimationState = new ThreadLocal<AnimationState>() { @Override protected AnimationState initialValue() { return new AnimationState(); } };
Locks AnimationUtilscurrentAnimationTimeMillis() to a fixed value for the current thread. This is used by Choreographer to ensure that all accesses during a vsync update are synchronized to the timestamp of the vsync. It is also exposed to tests to allow for rapid, flake-free headless testing. Must be followed by a call to unlockAnimationClock() to allow time to progress. Failing to do this will result in stuck animations, scrolls, and flings. Note that time is not allowed to "rewind" and must perpetually flow forward. So the lock may fail if the time is in the past from a previously returned value, however time will be frozen for the duration of the lock. The clock is a thread-local, so ensure that lockAnimationClock(long), unlockAnimationClock(), and currentAnimationTimeMillis() are all called on the same thread. This is also not reference counted in any way. Any call to unlockAnimationClock() will unlock the clock for everyone on the same thread. It is therefore recommended for tests to use their own thread to ensure that there is no collision with any existing Choreographer instance.
@hide
/** * Locks AnimationUtils{@link #currentAnimationTimeMillis()} to a fixed value for the current * thread. This is used by {@link android.view.Choreographer} to ensure that all accesses * during a vsync update are synchronized to the timestamp of the vsync. * * It is also exposed to tests to allow for rapid, flake-free headless testing. * * Must be followed by a call to {@link #unlockAnimationClock()} to allow time to * progress. Failing to do this will result in stuck animations, scrolls, and flings. * * Note that time is not allowed to "rewind" and must perpetually flow forward. So the * lock may fail if the time is in the past from a previously returned value, however * time will be frozen for the duration of the lock. The clock is a thread-local, so * ensure that {@link #lockAnimationClock(long)}, {@link #unlockAnimationClock()}, and * {@link #currentAnimationTimeMillis()} are all called on the same thread. * * This is also not reference counted in any way. Any call to {@link #unlockAnimationClock()} * will unlock the clock for everyone on the same thread. It is therefore recommended * for tests to use their own thread to ensure that there is no collision with any existing * {@link android.view.Choreographer} instance. * * @hide * */
@TestApi public static void lockAnimationClock(long vsyncMillis) { AnimationState state = sAnimationState.get(); state.animationClockLocked = true; state.currentVsyncTimeMillis = vsyncMillis; }
Frees the time lock set in place by lockAnimationClock(long). Must be called to allow the animation clock to self-update.
@hide
/** * Frees the time lock set in place by {@link #lockAnimationClock(long)}. Must be called * to allow the animation clock to self-update. * * @hide */
@TestApi public static void unlockAnimationClock() { sAnimationState.get().animationClockLocked = false; }
Returns the current animation time in milliseconds. This time should be used when invoking Animation.setStartTime(long). Refer to SystemClock for more information about the different available clocks. The clock used by this method is not the "wall" clock (it is not System.currentTimeMillis).
See Also:
Returns:the current animation time in milliseconds
/** * Returns the current animation time in milliseconds. This time should be used when invoking * {@link Animation#setStartTime(long)}. Refer to {@link android.os.SystemClock} for more * information about the different available clocks. The clock used by this method is * <em>not</em> the "wall" clock (it is not {@link System#currentTimeMillis}). * * @return the current animation time in milliseconds * * @see android.os.SystemClock */
public static long currentAnimationTimeMillis() { AnimationState state = sAnimationState.get(); if (state.animationClockLocked) { // It's important that time never rewinds return Math.max(state.currentVsyncTimeMillis, state.lastReportedTimeMillis); } state.lastReportedTimeMillis = SystemClock.uptimeMillis(); return state.lastReportedTimeMillis; }
Loads an Animation object from a resource
Params:
  • context – Application context used to access resources
  • id – The resource id of the animation to load
Throws:
Returns:The animation object reference by the specified id
/** * Loads an {@link Animation} object from a resource * * @param context Application context used to access resources * @param id The resource id of the animation to load * @return The animation object reference by the specified id * @throws NotFoundException when the animation cannot be loaded */
public static Animation loadAnimation(Context context, @AnimRes int id) throws NotFoundException { XmlResourceParser parser = null; try { parser = context.getResources().getAnimation(id); return createAnimationFromXml(context, parser); } catch (XmlPullParserException ex) { NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" + Integer.toHexString(id)); rnf.initCause(ex); throw rnf; } catch (IOException ex) { NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" + Integer.toHexString(id)); rnf.initCause(ex); throw rnf; } finally { if (parser != null) parser.close(); } } private static Animation createAnimationFromXml(Context c, XmlPullParser parser) throws XmlPullParserException, IOException { return createAnimationFromXml(c, parser, null, Xml.asAttributeSet(parser)); } private static Animation createAnimationFromXml(Context c, XmlPullParser parser, AnimationSet parent, AttributeSet attrs) throws XmlPullParserException, IOException { Animation anim = null; // Make sure we are on a start tag. int type; int depth = parser.getDepth(); while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) { if (type != XmlPullParser.START_TAG) { continue; } String name = parser.getName(); if (name.equals("set")) { anim = new AnimationSet(c, attrs); createAnimationFromXml(c, parser, (AnimationSet)anim, attrs); } else if (name.equals("alpha")) { anim = new AlphaAnimation(c, attrs); } else if (name.equals("scale")) { anim = new ScaleAnimation(c, attrs); } else if (name.equals("rotate")) { anim = new RotateAnimation(c, attrs); } else if (name.equals("translate")) { anim = new TranslateAnimation(c, attrs); } else if (name.equals("cliprect")) { anim = new ClipRectAnimation(c, attrs); } else { throw new RuntimeException("Unknown animation name: " + parser.getName()); } if (parent != null) { parent.addAnimation(anim); } } return anim; }
Loads a LayoutAnimationController object from a resource
Params:
  • context – Application context used to access resources
  • id – The resource id of the animation to load
Throws:
Returns:The animation object reference by the specified id
/** * Loads a {@link LayoutAnimationController} object from a resource * * @param context Application context used to access resources * @param id The resource id of the animation to load * @return The animation object reference by the specified id * @throws NotFoundException when the layout animation controller cannot be loaded */
public static LayoutAnimationController loadLayoutAnimation(Context context, @AnimRes int id) throws NotFoundException { XmlResourceParser parser = null; try { parser = context.getResources().getAnimation(id); return createLayoutAnimationFromXml(context, parser); } catch (XmlPullParserException ex) { NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" + Integer.toHexString(id)); rnf.initCause(ex); throw rnf; } catch (IOException ex) { NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" + Integer.toHexString(id)); rnf.initCause(ex); throw rnf; } finally { if (parser != null) parser.close(); } } private static LayoutAnimationController createLayoutAnimationFromXml(Context c, XmlPullParser parser) throws XmlPullParserException, IOException { return createLayoutAnimationFromXml(c, parser, Xml.asAttributeSet(parser)); } private static LayoutAnimationController createLayoutAnimationFromXml(Context c, XmlPullParser parser, AttributeSet attrs) throws XmlPullParserException, IOException { LayoutAnimationController controller = null; int type; int depth = parser.getDepth(); while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) { if (type != XmlPullParser.START_TAG) { continue; } String name = parser.getName(); if ("layoutAnimation".equals(name)) { controller = new LayoutAnimationController(c, attrs); } else if ("gridLayoutAnimation".equals(name)) { controller = new GridLayoutAnimationController(c, attrs); } else { throw new RuntimeException("Unknown layout animation name: " + name); } } return controller; }
Make an animation for objects becoming visible. Uses a slide and fade effect.
Params:
  • c – Context for loading resources
  • fromLeft – is the object to be animated coming from the left
Returns:The new animation
/** * Make an animation for objects becoming visible. Uses a slide and fade * effect. * * @param c Context for loading resources * @param fromLeft is the object to be animated coming from the left * @return The new animation */
public static Animation makeInAnimation(Context c, boolean fromLeft) { Animation a; if (fromLeft) { a = AnimationUtils.loadAnimation(c, com.android.internal.R.anim.slide_in_left); } else { a = AnimationUtils.loadAnimation(c, com.android.internal.R.anim.slide_in_right); } a.setInterpolator(new DecelerateInterpolator()); a.setStartTime(currentAnimationTimeMillis()); return a; }
Make an animation for objects becoming invisible. Uses a slide and fade effect.
Params:
  • c – Context for loading resources
  • toRight – is the object to be animated exiting to the right
Returns:The new animation
/** * Make an animation for objects becoming invisible. Uses a slide and fade * effect. * * @param c Context for loading resources * @param toRight is the object to be animated exiting to the right * @return The new animation */
public static Animation makeOutAnimation(Context c, boolean toRight) { Animation a; if (toRight) { a = AnimationUtils.loadAnimation(c, com.android.internal.R.anim.slide_out_right); } else { a = AnimationUtils.loadAnimation(c, com.android.internal.R.anim.slide_out_left); } a.setInterpolator(new AccelerateInterpolator()); a.setStartTime(currentAnimationTimeMillis()); return a; }
Make an animation for objects becoming visible. Uses a slide up and fade effect.
Params:
  • c – Context for loading resources
Returns:The new animation
/** * Make an animation for objects becoming visible. Uses a slide up and fade * effect. * * @param c Context for loading resources * @return The new animation */
public static Animation makeInChildBottomAnimation(Context c) { Animation a; a = AnimationUtils.loadAnimation(c, com.android.internal.R.anim.slide_in_child_bottom); a.setInterpolator(new AccelerateInterpolator()); a.setStartTime(currentAnimationTimeMillis()); return a; }
Loads an Interpolator object from a resource
Params:
  • context – Application context used to access resources
  • id – The resource id of the animation to load
Throws:
Returns:The animation object reference by the specified id
/** * Loads an {@link Interpolator} object from a resource * * @param context Application context used to access resources * @param id The resource id of the animation to load * @return The animation object reference by the specified id * @throws NotFoundException */
public static Interpolator loadInterpolator(Context context, @AnimRes @InterpolatorRes int id) throws NotFoundException { XmlResourceParser parser = null; try { parser = context.getResources().getAnimation(id); return createInterpolatorFromXml(context.getResources(), context.getTheme(), parser); } catch (XmlPullParserException ex) { NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" + Integer.toHexString(id)); rnf.initCause(ex); throw rnf; } catch (IOException ex) { NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" + Integer.toHexString(id)); rnf.initCause(ex); throw rnf; } finally { if (parser != null) parser.close(); } }
Loads an Interpolator object from a resource
Params:
  • res – The resources
  • id – The resource id of the animation to load
Throws:
Returns:The interpolator object reference by the specified id
@hide
/** * Loads an {@link Interpolator} object from a resource * * @param res The resources * @param id The resource id of the animation to load * @return The interpolator object reference by the specified id * @throws NotFoundException * @hide */
public static Interpolator loadInterpolator(Resources res, Theme theme, int id) throws NotFoundException { XmlResourceParser parser = null; try { parser = res.getAnimation(id); return createInterpolatorFromXml(res, theme, parser); } catch (XmlPullParserException ex) { NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" + Integer.toHexString(id)); rnf.initCause(ex); throw rnf; } catch (IOException ex) { NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" + Integer.toHexString(id)); rnf.initCause(ex); throw rnf; } finally { if (parser != null) parser.close(); } } private static Interpolator createInterpolatorFromXml(Resources res, Theme theme, XmlPullParser parser) throws XmlPullParserException, IOException { BaseInterpolator interpolator = null; // Make sure we are on a start tag. int type; int depth = parser.getDepth(); while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) { if (type != XmlPullParser.START_TAG) { continue; } AttributeSet attrs = Xml.asAttributeSet(parser); String name = parser.getName(); if (name.equals("linearInterpolator")) { interpolator = new LinearInterpolator(); } else if (name.equals("accelerateInterpolator")) { interpolator = new AccelerateInterpolator(res, theme, attrs); } else if (name.equals("decelerateInterpolator")) { interpolator = new DecelerateInterpolator(res, theme, attrs); } else if (name.equals("accelerateDecelerateInterpolator")) { interpolator = new AccelerateDecelerateInterpolator(); } else if (name.equals("cycleInterpolator")) { interpolator = new CycleInterpolator(res, theme, attrs); } else if (name.equals("anticipateInterpolator")) { interpolator = new AnticipateInterpolator(res, theme, attrs); } else if (name.equals("overshootInterpolator")) { interpolator = new OvershootInterpolator(res, theme, attrs); } else if (name.equals("anticipateOvershootInterpolator")) { interpolator = new AnticipateOvershootInterpolator(res, theme, attrs); } else if (name.equals("bounceInterpolator")) { interpolator = new BounceInterpolator(); } else if (name.equals("pathInterpolator")) { interpolator = new PathInterpolator(res, theme, attrs); } else { throw new RuntimeException("Unknown interpolator name: " + parser.getName()); } } return interpolator; } }