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