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