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.content.Context; 20 import android.content.res.Resources; 21 import android.content.res.TypedArray; 22 import android.content.res.XmlResourceParser; 23 import android.util.AttributeSet; 24 import android.util.Xml; 25 import android.view.InflateException; 26 import android.view.ViewGroup; 27 import android.view.animation.AnimationUtils; 28 import org.xmlpull.v1.XmlPullParser; 29 import org.xmlpull.v1.XmlPullParserException; 30 31 import java.io.IOException; 32 import java.util.ArrayList; 33 34 /** 35 * This class inflates scenes and transitions from resource files. 36 * 37 * Information on XML resource descriptions for transitions can be found for 38 * {@link android.R.styleable#Transition}, {@link android.R.styleable#TransitionSet}, 39 * {@link android.R.styleable#TransitionTarget}, {@link android.R.styleable#Fade}, 40 * and {@link android.R.styleable#TransitionManager}. 41 */ 42 public class TransitionInflater { 43 44 private Context mContext; 45 46 private TransitionInflater(Context context) { 47 mContext = context; 48 } 49 50 /** 51 * Obtains the TransitionInflater from the given context. 52 */ 53 public static TransitionInflater from(Context context) { 54 return new TransitionInflater(context); 55 } 56 57 /** 58 * Loads a {@link Transition} object from a resource 59 * 60 * @param resource The resource id of the transition to load 61 * @return The loaded Transition object 62 * @throws android.content.res.Resources.NotFoundException when the 63 * transition cannot be loaded 64 */ 65 public Transition inflateTransition(int resource) { 66 XmlResourceParser parser = mContext.getResources().getXml(resource); 67 try { 68 return createTransitionFromXml(parser, Xml.asAttributeSet(parser), null); 69 } catch (XmlPullParserException e) { 70 InflateException ex = new InflateException(e.getMessage()); 71 ex.initCause(e); 72 throw ex; 73 } catch (IOException e) { 74 InflateException ex = new InflateException( 75 parser.getPositionDescription() 76 + ": " + e.getMessage()); 77 ex.initCause(e); 78 throw ex; 79 } finally { 80 parser.close(); 81 } 82 } 83 84 /** 85 * Loads a {@link TransitionManager} object from a resource 86 * 87 * 88 * 89 * @param resource The resource id of the transition manager to load 90 * @return The loaded TransitionManager object 91 * @throws android.content.res.Resources.NotFoundException when the 92 * transition manager cannot be loaded 93 */ 94 public TransitionManager inflateTransitionManager(int resource, ViewGroup sceneRoot) { 95 XmlResourceParser parser = mContext.getResources().getXml(resource); 96 try { 97 return createTransitionManagerFromXml(parser, Xml.asAttributeSet(parser), sceneRoot); 98 } catch (XmlPullParserException e) { 99 InflateException ex = new InflateException(e.getMessage()); 100 ex.initCause(e); 101 throw ex; 102 } catch (IOException e) { 103 InflateException ex = new InflateException( 104 parser.getPositionDescription() 105 + ": " + e.getMessage()); 106 ex.initCause(e); 107 throw ex; 108 } finally { 109 parser.close(); 110 } 111 } 112 113 // 114 // Transition loading 115 // 116 117 private Transition createTransitionFromXml(XmlPullParser parser, 118 AttributeSet attrs, TransitionSet transitionSet) 119 throws XmlPullParserException, IOException { 120 121 Transition transition = null; 122 123 // Make sure we are on a start tag. 124 int type; 125 int depth = parser.getDepth(); 126 127 while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) 128 && type != XmlPullParser.END_DOCUMENT) { 129 130 boolean newTransition = false; 131 132 if (type != XmlPullParser.START_TAG) { 133 continue; 134 } 135 136 String name = parser.getName(); 137 if ("fade".equals(name)) { 138 TypedArray a = mContext.obtainStyledAttributes(attrs, 139 com.android.internal.R.styleable.Fade); 140 int fadingMode = a.getInt(com.android.internal.R.styleable.Fade_fadingMode, 141 Fade.IN | Fade.OUT); 142 transition = new Fade(fadingMode); 143 newTransition = true; 144 } else if ("changeBounds".equals(name)) { 145 transition = new ChangeBounds(); 146 newTransition = true; 147 } else if ("slide".equals(name)) { 148 transition = new Slide(); 149 newTransition = true; 150 } else if ("autoTransition".equals(name)) { 151 transition = new AutoTransition(); 152 newTransition = true; 153 } else if ("recolor".equals(name)) { 154 transition = new Recolor(); 155 newTransition = true; 156 } else if ("transitionSet".equals(name)) { 157 transition = new TransitionSet(); 158 TypedArray a = mContext.obtainStyledAttributes(attrs, 159 com.android.internal.R.styleable.TransitionSet); 160 int ordering = a.getInt( 161 com.android.internal.R.styleable.TransitionSet_transitionOrdering, 162 TransitionSet.ORDERING_TOGETHER); 163 ((TransitionSet) transition).setOrdering(ordering); 164 createTransitionFromXml(parser, attrs, ((TransitionSet) transition)); 165 a.recycle(); 166 newTransition = true; 167 } else if ("targets".equals(name)) { 168 if (parser.getDepth() - 1 > depth && transition != null) { 169 // We're inside the child tag - add targets to the child 170 getTargetIds(parser, attrs, transition); 171 } else if (parser.getDepth() - 1 == depth && transitionSet != null) { 172 // add targets to the set 173 getTargetIds(parser, attrs, transitionSet); 174 } 175 } 176 if (transition != null || "targets".equals(name)) { 177 if (newTransition) { 178 loadTransition(transition, attrs); 179 if (transitionSet != null) { 180 transitionSet.addTransition(transition); 181 } 182 } 183 } else { 184 throw new RuntimeException("Unknown scene name: " + parser.getName()); 185 } 186 } 187 188 return transition; 189 } 190 191 private void getTargetIds(XmlPullParser parser, 192 AttributeSet attrs, Transition transition) throws XmlPullParserException, IOException { 193 194 // Make sure we are on a start tag. 195 int type; 196 int depth = parser.getDepth(); 197 198 ArrayList<Integer> targetIds = new ArrayList<Integer>(); 199 while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) 200 && type != XmlPullParser.END_DOCUMENT) { 201 202 if (type != XmlPullParser.START_TAG) { 203 continue; 204 } 205 206 String name = parser.getName(); 207 if (name.equals("target")) { 208 TypedArray a = mContext.obtainStyledAttributes(attrs, 209 com.android.internal.R.styleable.TransitionTarget); 210 int id = a.getResourceId( 211 com.android.internal.R.styleable.TransitionTarget_targetId, -1); 212 if (id >= 0) { 213 targetIds.add(id); 214 } 215 } else { 216 throw new RuntimeException("Unknown scene name: " + parser.getName()); 217 } 218 } 219 int numTargets = targetIds.size(); 220 if (numTargets > 0) { 221 for (int i = 0; i < numTargets; ++i) { 222 transition.addTarget(targetIds.get(i)); 223 } 224 } 225 } 226 227 private Transition loadTransition(Transition transition, AttributeSet attrs) 228 throws Resources.NotFoundException { 229 230 TypedArray a = 231 mContext.obtainStyledAttributes(attrs, com.android.internal.R.styleable.Transition); 232 long duration = a.getInt(com.android.internal.R.styleable.Transition_duration, -1); 233 if (duration >= 0) { 234 transition.setDuration(duration); 235 } 236 long startDelay = a.getInt(com.android.internal.R.styleable.Transition_startDelay, -1); 237 if (startDelay > 0) { 238 transition.setStartDelay(startDelay); 239 } 240 final int resID = 241 a.getResourceId(com.android.internal.R.styleable.Animator_interpolator, 0); 242 if (resID > 0) { 243 transition.setInterpolator(AnimationUtils.loadInterpolator(mContext, resID)); 244 } 245 a.recycle(); 246 return transition; 247 } 248 249 // 250 // TransitionManager loading 251 // 252 253 private TransitionManager createTransitionManagerFromXml(XmlPullParser parser, 254 AttributeSet attrs, ViewGroup sceneRoot) throws XmlPullParserException, IOException { 255 256 // Make sure we are on a start tag. 257 int type; 258 int depth = parser.getDepth(); 259 TransitionManager transitionManager = null; 260 261 while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) 262 && type != XmlPullParser.END_DOCUMENT) { 263 264 if (type != XmlPullParser.START_TAG) { 265 continue; 266 } 267 268 String name = parser.getName(); 269 if (name.equals("transitionManager")) { 270 transitionManager = new TransitionManager(); 271 } else if (name.equals("transition") && (transitionManager != null)) { 272 loadTransition(attrs, sceneRoot, transitionManager); 273 } else { 274 throw new RuntimeException("Unknown scene name: " + parser.getName()); 275 } 276 } 277 return transitionManager; 278 } 279 280 private void loadTransition(AttributeSet attrs, ViewGroup sceneRoot, 281 TransitionManager transitionManager) throws Resources.NotFoundException { 282 283 TypedArray a = mContext.obtainStyledAttributes(attrs, 284 com.android.internal.R.styleable.TransitionManager); 285 int transitionId = a.getResourceId( 286 com.android.internal.R.styleable.TransitionManager_transition, -1); 287 Scene fromScene = null, toScene = null; 288 int fromId = a.getResourceId( 289 com.android.internal.R.styleable.TransitionManager_fromScene, -1); 290 if (fromId >= 0) fromScene = Scene.getSceneForLayout(sceneRoot, fromId, mContext); 291 int toId = a.getResourceId( 292 com.android.internal.R.styleable.TransitionManager_toScene, -1); 293 if (toId >= 0) toScene = Scene.getSceneForLayout(sceneRoot, toId, mContext); 294 if (transitionId >= 0) { 295 Transition transition = inflateTransition(transitionId); 296 if (transition != null) { 297 if (fromScene != null) { 298 if (toScene == null){ 299 throw new RuntimeException("No matching toScene for given fromScene " + 300 "for transition ID " + transitionId); 301 } else { 302 transitionManager.setTransition(fromScene, toScene, transition); 303 } 304 } else if (toId >= 0) { 305 transitionManager.setTransition(toScene, transition); 306 } 307 } 308 } 309 a.recycle(); 310 } 311 } 312