1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436
|
/*
* 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.annotation.UnsupportedAppUsage;
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.
*
*/
public class AnimationUtils {
/**
* 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 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 {@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
* {@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 {@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 referenced 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));
}
@UnsupportedAppUsage
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 {@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 controller object referenced 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.
*
* @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.
*
* @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.
*
* @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 {@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 interpolator object referenced 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 {@link Interpolator} object from a resource
*
* @param res The resources
* @param id The resource id of the animation to load
* @return The interpolator object referenced 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;
}
}
|