Home | History | Annotate | Download | only in animation
      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.content.Context;
     22 import android.content.res.Resources;
     23 import android.content.res.Resources.NotFoundException;
     24 import android.content.res.Resources.Theme;
     25 import android.content.res.XmlResourceParser;
     26 import android.os.SystemClock;
     27 import android.util.AttributeSet;
     28 import android.util.Xml;
     29 
     30 import org.xmlpull.v1.XmlPullParser;
     31 import org.xmlpull.v1.XmlPullParserException;
     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 */
     62     public static void lockAnimationClock(long vsyncMillis) {
     63         AnimationState state = sAnimationState.get();
     64         state.animationClockLocked = true;
     65         state.currentVsyncTimeMillis = vsyncMillis;
     66     }
     67 
     68     /** @hide */
     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      */
     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      */
    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 
    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 
    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      */
    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 
    202     private static LayoutAnimationController createLayoutAnimationFromXml(Context c,
    203             XmlPullParser parser) throws XmlPullParserException, IOException {
    204 
    205         return createLayoutAnimationFromXml(c, parser, Xml.asAttributeSet(parser));
    206     }
    207 
    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      */
    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      */
    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      */
    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      */
    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      */
    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 
    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