Home | History | Annotate | Download | only in transition
      1 /*
      2  * Copyright (C) 2013 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.transition;
     18 
     19 import android.annotation.TransitionRes;
     20 import com.android.internal.R;
     21 
     22 import org.xmlpull.v1.XmlPullParser;
     23 import org.xmlpull.v1.XmlPullParserException;
     24 
     25 import android.content.Context;
     26 import android.content.res.Resources;
     27 import android.content.res.TypedArray;
     28 import android.content.res.XmlResourceParser;
     29 import android.util.ArrayMap;
     30 import android.util.AttributeSet;
     31 import android.util.Xml;
     32 import android.view.InflateException;
     33 import android.view.ViewGroup;
     34 
     35 import java.io.IOException;
     36 import java.lang.reflect.Constructor;
     37 import java.lang.reflect.InvocationTargetException;
     38 
     39 /**
     40  * This class inflates scenes and transitions from resource files.
     41  *
     42  * Information on XML resource descriptions for transitions can be found for
     43  * {@link android.R.styleable#Transition}, {@link android.R.styleable#TransitionSet},
     44  * {@link android.R.styleable#TransitionTarget}, {@link android.R.styleable#Fade},
     45  * and {@link android.R.styleable#TransitionManager}.
     46  */
     47 public class TransitionInflater {
     48 
     49     private static final Class<?>[] sConstructorSignature = new Class[] {
     50             Context.class, AttributeSet.class};
     51     private final static ArrayMap<String, Constructor> sConstructors =
     52             new ArrayMap<String, Constructor>();
     53 
     54     private Context mContext;
     55 
     56     private TransitionInflater(Context context) {
     57         mContext = context;
     58     }
     59 
     60     /**
     61      * Obtains the TransitionInflater from the given context.
     62      */
     63     public static TransitionInflater from(Context context) {
     64         return new TransitionInflater(context);
     65     }
     66 
     67     /**
     68      * Loads a {@link Transition} object from a resource
     69      *
     70      * @param resource The resource id of the transition to load
     71      * @return The loaded Transition object
     72      * @throws android.content.res.Resources.NotFoundException when the
     73      * transition cannot be loaded
     74      */
     75     public Transition inflateTransition(@TransitionRes int resource) {
     76         //noinspection ResourceType
     77         XmlResourceParser parser =  mContext.getResources().getXml(resource);
     78         try {
     79             return createTransitionFromXml(parser, Xml.asAttributeSet(parser), null);
     80         } catch (XmlPullParserException e) {
     81             InflateException ex = new InflateException(e.getMessage());
     82             ex.initCause(e);
     83             throw ex;
     84         } catch (IOException e) {
     85             InflateException ex = new InflateException(
     86                     parser.getPositionDescription()
     87                             + ": " + e.getMessage());
     88             ex.initCause(e);
     89             throw ex;
     90         } finally {
     91             parser.close();
     92         }
     93     }
     94 
     95     /**
     96      * Loads a {@link TransitionManager} object from a resource
     97      *
     98      * @param resource The resource id of the transition manager to load
     99      * @return The loaded TransitionManager object
    100      * @throws android.content.res.Resources.NotFoundException when the
    101      * transition manager cannot be loaded
    102      */
    103     public TransitionManager inflateTransitionManager(@TransitionRes int resource,
    104             ViewGroup sceneRoot) {
    105         //noinspection ResourceType
    106         XmlResourceParser parser =  mContext.getResources().getXml(resource);
    107         try {
    108             return createTransitionManagerFromXml(parser, Xml.asAttributeSet(parser), sceneRoot);
    109         } catch (XmlPullParserException e) {
    110             InflateException ex = new InflateException(e.getMessage());
    111             ex.initCause(e);
    112             throw ex;
    113         } catch (IOException e) {
    114             InflateException ex = new InflateException(
    115                     parser.getPositionDescription()
    116                             + ": " + e.getMessage());
    117             ex.initCause(e);
    118             throw ex;
    119         } finally {
    120             parser.close();
    121         }
    122     }
    123 
    124     //
    125     // Transition loading
    126     //
    127     private Transition createTransitionFromXml(XmlPullParser parser,
    128             AttributeSet attrs, Transition parent)
    129             throws XmlPullParserException, IOException {
    130 
    131         Transition transition = null;
    132 
    133         // Make sure we are on a start tag.
    134         int type;
    135         int depth = parser.getDepth();
    136 
    137         TransitionSet transitionSet = (parent instanceof TransitionSet)
    138                 ? (TransitionSet) parent : null;
    139 
    140         while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
    141                 && type != XmlPullParser.END_DOCUMENT) {
    142 
    143             if (type != XmlPullParser.START_TAG) {
    144                 continue;
    145             }
    146 
    147             String  name = parser.getName();
    148             if ("fade".equals(name)) {
    149                 transition = new Fade(mContext, attrs);
    150             } else if ("changeBounds".equals(name)) {
    151                 transition = new ChangeBounds(mContext, attrs);
    152             } else if ("slide".equals(name)) {
    153                 transition = new Slide(mContext, attrs);
    154             } else if ("explode".equals(name)) {
    155                 transition = new Explode(mContext, attrs);
    156             } else if ("changeImageTransform".equals(name)) {
    157                 transition = new ChangeImageTransform(mContext, attrs);
    158             } else if ("changeTransform".equals(name)) {
    159                 transition = new ChangeTransform(mContext, attrs);
    160             } else if ("changeClipBounds".equals(name)) {
    161                 transition = new ChangeClipBounds(mContext, attrs);
    162             } else if ("autoTransition".equals(name)) {
    163                 transition = new AutoTransition(mContext, attrs);
    164             } else if ("recolor".equals(name)) {
    165                 transition = new Recolor(mContext, attrs);
    166             } else if ("changeScroll".equals(name)) {
    167                 transition = new ChangeScroll(mContext, attrs);
    168             } else if ("transitionSet".equals(name)) {
    169                 transition = new TransitionSet(mContext, attrs);
    170             } else if ("transition".equals(name)) {
    171                 transition = (Transition) createCustom(attrs, Transition.class, "transition");
    172             } else if ("targets".equals(name)) {
    173                 getTargetIds(parser, attrs, parent);
    174             } else if ("arcMotion".equals(name)) {
    175                 parent.setPathMotion(new ArcMotion(mContext, attrs));
    176             } else if ("pathMotion".equals(name)) {
    177                 parent.setPathMotion((PathMotion)createCustom(attrs, PathMotion.class, "pathMotion"));
    178             } else if ("patternPathMotion".equals(name)) {
    179                 parent.setPathMotion(new PatternPathMotion(mContext, attrs));
    180             } else {
    181                 throw new RuntimeException("Unknown scene name: " + parser.getName());
    182             }
    183             if (transition != null) {
    184                 if (!parser.isEmptyElementTag()) {
    185                     createTransitionFromXml(parser, attrs, transition);
    186                 }
    187                 if (transitionSet != null) {
    188                     transitionSet.addTransition(transition);
    189                     transition = null;
    190                 } else if (parent != null) {
    191                     throw new InflateException("Could not add transition to another transition.");
    192                 }
    193             }
    194         }
    195 
    196         return transition;
    197     }
    198 
    199     private Object createCustom(AttributeSet attrs, Class expectedType, String tag) {
    200         String className = attrs.getAttributeValue(null, "class");
    201 
    202         if (className == null) {
    203             throw new InflateException(tag + " tag must have a 'class' attribute");
    204         }
    205 
    206         try {
    207             synchronized (sConstructors) {
    208                 Constructor constructor = sConstructors.get(className);
    209                 if (constructor == null) {
    210                     Class c = mContext.getClassLoader().loadClass(className)
    211                             .asSubclass(expectedType);
    212                     if (c != null) {
    213                         constructor = c.getConstructor(sConstructorSignature);
    214                         constructor.setAccessible(true);
    215                         sConstructors.put(className, constructor);
    216                     }
    217                 }
    218                 return constructor.newInstance(mContext, attrs);
    219             }
    220         } catch (InstantiationException e) {
    221             throw new InflateException("Could not instantiate " + expectedType + " class " +
    222                     className, e);
    223         } catch (ClassNotFoundException e) {
    224             throw new InflateException("Could not instantiate " + expectedType + " class " +
    225                     className, e);
    226         } catch (InvocationTargetException e) {
    227             throw new InflateException("Could not instantiate " + expectedType + " class " +
    228                     className, e);
    229         } catch (NoSuchMethodException e) {
    230             throw new InflateException("Could not instantiate " + expectedType + " class " +
    231                     className, e);
    232         } catch (IllegalAccessException e) {
    233             throw new InflateException("Could not instantiate " + expectedType + " class " +
    234                     className, e);
    235         }
    236     }
    237 
    238     private void getTargetIds(XmlPullParser parser,
    239             AttributeSet attrs, Transition transition) throws XmlPullParserException, IOException {
    240 
    241         // Make sure we are on a start tag.
    242         int type;
    243         int depth = parser.getDepth();
    244 
    245         while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
    246                 && type != XmlPullParser.END_DOCUMENT) {
    247 
    248             if (type != XmlPullParser.START_TAG) {
    249                 continue;
    250             }
    251 
    252             String  name = parser.getName();
    253             if (name.equals("target")) {
    254                 TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.TransitionTarget);
    255                 int id = a.getResourceId(R.styleable.TransitionTarget_targetId, 0);
    256                 String transitionName;
    257                 if (id != 0) {
    258                     transition.addTarget(id);
    259                 } else if ((id = a.getResourceId(R.styleable.TransitionTarget_excludeId, 0)) != 0) {
    260                     transition.excludeTarget(id, true);
    261                 } else if ((transitionName = a.getString(R.styleable.TransitionTarget_targetName))
    262                         != null) {
    263                     transition.addTarget(transitionName);
    264                 } else if ((transitionName = a.getString(R.styleable.TransitionTarget_excludeName))
    265                         != null) {
    266                     transition.excludeTarget(transitionName, true);
    267                 } else {
    268                     String className = a.getString(R.styleable.TransitionTarget_excludeClass);
    269                     try {
    270                         if (className != null) {
    271                             Class clazz = Class.forName(className);
    272                             transition.excludeTarget(clazz, true);
    273                         } else if ((className =
    274                                 a.getString(R.styleable.TransitionTarget_targetClass)) != null) {
    275                             Class clazz = Class.forName(className);
    276                             transition.addTarget(clazz);
    277                         }
    278                     } catch (ClassNotFoundException e) {
    279                         throw new RuntimeException("Could not create " + className, e);
    280                     }
    281                 }
    282             } else {
    283                 throw new RuntimeException("Unknown scene name: " + parser.getName());
    284             }
    285         }
    286     }
    287 
    288     //
    289     // TransitionManager loading
    290     //
    291 
    292     private TransitionManager createTransitionManagerFromXml(XmlPullParser parser,
    293             AttributeSet attrs, ViewGroup sceneRoot) throws XmlPullParserException, IOException {
    294 
    295         // Make sure we are on a start tag.
    296         int type;
    297         int depth = parser.getDepth();
    298         TransitionManager transitionManager = null;
    299 
    300         while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
    301                 && type != XmlPullParser.END_DOCUMENT) {
    302 
    303             if (type != XmlPullParser.START_TAG) {
    304                 continue;
    305             }
    306 
    307             String  name = parser.getName();
    308             if (name.equals("transitionManager")) {
    309                 transitionManager = new TransitionManager();
    310             } else if (name.equals("transition") && (transitionManager != null)) {
    311                 loadTransition(attrs, sceneRoot, transitionManager);
    312             } else {
    313                 throw new RuntimeException("Unknown scene name: " + parser.getName());
    314             }
    315         }
    316         return transitionManager;
    317     }
    318 
    319     private void loadTransition(AttributeSet attrs, ViewGroup sceneRoot,
    320             TransitionManager transitionManager) throws Resources.NotFoundException {
    321 
    322         TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.TransitionManager);
    323         int transitionId = a.getResourceId(R.styleable.TransitionManager_transition, -1);
    324         int fromId = a.getResourceId(R.styleable.TransitionManager_fromScene, -1);
    325         Scene fromScene = (fromId < 0) ? null: Scene.getSceneForLayout(sceneRoot, fromId, mContext);
    326         int toId = a.getResourceId(R.styleable.TransitionManager_toScene, -1);
    327         Scene toScene = (toId < 0) ? null : Scene.getSceneForLayout(sceneRoot, toId, mContext);
    328 
    329         if (transitionId >= 0) {
    330             Transition transition = inflateTransition(transitionId);
    331             if (transition != null) {
    332                 if (toScene == null) {
    333                     throw new RuntimeException("No toScene for transition ID " + transitionId);
    334                 }
    335                 if (fromScene == null) {
    336                     transitionManager.setTransition(toScene, transition);
    337                 } else {
    338                     transitionManager.setTransition(fromScene, toScene, transition);
    339                 }
    340             }
    341         }
    342         a.recycle();
    343     }
    344 }
    345