FragmentTransitionCompat21.java revision c766ad5a6e0c115f08b933ede22375c7070a3391
1/* 2 * Copyright (C) 2014 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 17package android.support.v4.app; 18 19import android.content.Context; 20import android.content.res.TypedArray; 21import android.graphics.Rect; 22import android.transition.Transition; 23import android.transition.TransitionInflater; 24import android.transition.TransitionManager; 25import android.transition.TransitionSet; 26import android.util.ArrayMap; 27import android.view.View; 28import android.view.ViewGroup; 29import android.view.ViewTreeObserver; 30 31import java.util.ArrayList; 32import java.util.List; 33import java.util.Map; 34 35class FragmentTransitionCompat21 { 36 public static String getTransitionName(View view) { 37 return view.getTransitionName(); 38 } 39 40 public static Object cloneTransition(Object transition) { 41 if (transition != null) { 42 transition = ((Transition)transition).clone(); 43 } 44 return transition; 45 } 46 47 public static Object captureExitingViews(Object exitTransition, View root, 48 ArrayList<View> viewList, Map<String, View> namedViews, View nonExistentView) { 49 if (exitTransition != null) { 50 captureTransitioningViews(viewList, root); 51 if (namedViews != null) { 52 viewList.removeAll(namedViews.values()); 53 } 54 if (viewList.isEmpty()) { 55 exitTransition = null; 56 } else { 57 viewList.add(nonExistentView); 58 addTargets((Transition) exitTransition, viewList); 59 } 60 } 61 return exitTransition; 62 } 63 64 public static void excludeTarget(Object transitionObject, View view, boolean exclude) { 65 Transition transition = (Transition) transitionObject; 66 transition.excludeTarget(view, exclude); 67 } 68 69 public static void beginDelayedTransition(ViewGroup sceneRoot, Object transitionObject) { 70 Transition transition = (Transition) transitionObject; 71 TransitionManager.beginDelayedTransition(sceneRoot, transition); 72 } 73 74 public static void setEpicenter(Object transitionObject, View view) { 75 Transition transition = (Transition) transitionObject; 76 final Rect epicenter = getBoundsOnScreen(view); 77 78 transition.setEpicenterCallback(new Transition.EpicenterCallback() { 79 @Override 80 public Rect onGetEpicenter(Transition transition) { 81 return epicenter; 82 } 83 }); 84 } 85 86 /** 87 * Prepares the enter transition by adding a non-existent view to the transition's target list 88 * and setting it epicenter callback. By adding a non-existent view to the target list, 89 * we can prevent any view from being targeted at the beginning of the transition. 90 * We will add to the views before the end state of the transition is captured so that the 91 * views will appear. At the start of the transition, we clear the list of targets so that 92 * we can restore the state of the transition and use it again. 93 * 94 * <p>The shared element transition maps its shared elements immediately prior to 95 * capturing the final state of the Transition.</p> 96 */ 97 public static void addTransitionTargets(Object enterTransitionObject, 98 Object sharedElementTransitionObject, final View container, 99 final ViewRetriever inFragment, final View nonExistentView, 100 EpicenterView epicenterView, final Map<String, String> nameOverrides, 101 final ArrayList<View> enteringViews, final Map<String, View> renamedViews, 102 final ArrayList<View> sharedElementTargets) { 103 if (enterTransitionObject != null || sharedElementTransitionObject != null) { 104 final Transition enterTransition = (Transition) enterTransitionObject; 105 if (enterTransition != null) { 106 enterTransition.addTarget(nonExistentView); 107 } 108 if (sharedElementTransitionObject != null) { 109 Transition sharedElementTransition = (Transition) sharedElementTransitionObject; 110 addTargets(sharedElementTransition, sharedElementTargets); 111 } 112 113 if (inFragment != null) { 114 container.getViewTreeObserver().addOnPreDrawListener( 115 new ViewTreeObserver.OnPreDrawListener() { 116 public boolean onPreDraw() { 117 container.getViewTreeObserver().removeOnPreDrawListener(this); 118 View fragmentView = inFragment.getView(); 119 if (fragmentView != null) { 120 if (!nameOverrides.isEmpty()) { 121 findNamedViews(renamedViews, fragmentView); 122 renamedViews.keySet().retainAll(nameOverrides.values()); 123 for (Map.Entry<String, String> entry : nameOverrides.entrySet()) { 124 String to = entry.getValue(); 125 View view = renamedViews.get(to); 126 if (view != null) { 127 String from = entry.getKey(); 128 view.setTransitionName(from); 129 } 130 } 131 } 132 if (enterTransition != null) { 133 captureTransitioningViews(enteringViews, fragmentView); 134 enteringViews.removeAll(renamedViews.values()); 135 enteringViews.add(nonExistentView); 136 enterTransition.removeTarget(nonExistentView); 137 addTargets(enterTransition, enteringViews); 138 } 139 } 140 return true; 141 } 142 }); 143 } 144 setSharedElementEpicenter(enterTransition, epicenterView); 145 } 146 } 147 148 public static Object mergeTransitions(Object enterTransitionObject, 149 Object exitTransitionObject, Object sharedElementTransitionObject, 150 boolean allowOverlap) { 151 boolean overlap = true; 152 Transition enterTransition = (Transition) enterTransitionObject; 153 Transition exitTransition = (Transition) exitTransitionObject; 154 Transition sharedElementTransition = (Transition) sharedElementTransitionObject; 155 156 if (enterTransition != null && exitTransition != null) { 157 overlap = allowOverlap; 158 } 159 160 // Wrap the transitions. Explicit targets like in enter and exit will cause the 161 // views to be targeted regardless of excluded views. If that happens, then the 162 // excluded fragments views (hidden fragments) will still be in the transition. 163 164 Transition transition; 165 if (overlap) { 166 // Regular transition -- do it all together 167 TransitionSet transitionSet = new TransitionSet(); 168 if (enterTransition != null) { 169 transitionSet.addTransition(enterTransition); 170 } 171 if (exitTransition != null) { 172 transitionSet.addTransition(exitTransition); 173 } 174 if (sharedElementTransition != null) { 175 transitionSet.addTransition(sharedElementTransition); 176 } 177 transition = transitionSet; 178 } else { 179 // First do exit, then enter, but allow shared element transition to happen 180 // during both. 181 Transition staggered = null; 182 if (exitTransition != null && enterTransition != null) { 183 staggered = new TransitionSet() 184 .addTransition(exitTransition) 185 .addTransition(enterTransition) 186 .setOrdering(TransitionSet.ORDERING_SEQUENTIAL); 187 } else if (exitTransition != null) { 188 staggered = exitTransition; 189 } else if (enterTransition != null) { 190 staggered = enterTransition; 191 } 192 if (sharedElementTransition != null) { 193 TransitionSet together = new TransitionSet(); 194 if (staggered != null) { 195 together.addTransition(staggered); 196 } 197 together.addTransition(sharedElementTransition); 198 transition = together; 199 } else { 200 transition = staggered; 201 } 202 } 203 return transition; 204 } 205 206 207 208 private static void setSharedElementEpicenter(Transition transition, 209 final EpicenterView epicenterView) { 210 if (transition != null) { 211 transition.setEpicenterCallback(new Transition.EpicenterCallback() { 212 private Rect mEpicenter; 213 214 @Override 215 public Rect onGetEpicenter(Transition transition) { 216 if (mEpicenter == null && epicenterView.epicenter != null) { 217 mEpicenter = getBoundsOnScreen(epicenterView.epicenter); 218 } 219 return mEpicenter; 220 } 221 }); 222 } 223 } 224 225 private static Rect getBoundsOnScreen(View view) { 226 Rect epicenter = new Rect(); 227 int[] loc = new int[2]; 228 view.getLocationOnScreen(loc); 229 // not as good as View.getBoundsOnScreen, but that's not public 230 epicenter.set(loc[0], loc[1], loc[0] + view.getWidth(), loc[1] + view.getHeight()); 231 return epicenter; 232 } 233 234 private static void captureTransitioningViews(ArrayList<View> transitioningViews, View view) { 235 if (view.getVisibility() == View.VISIBLE) { 236 if (view instanceof ViewGroup) { 237 ViewGroup viewGroup = (ViewGroup) view; 238 if (viewGroup.isTransitionGroup()) { 239 transitioningViews.add(viewGroup); 240 } else { 241 int count = viewGroup.getChildCount(); 242 for (int i = 0; i < count; i++) { 243 View child = viewGroup.getChildAt(i); 244 captureTransitioningViews(transitioningViews, child); 245 } 246 } 247 } else { 248 transitioningViews.add(view); 249 } 250 } 251 } 252 253 public static void findNamedViews(Map<String, View> namedViews, View view) { 254 if (view.getVisibility() == View.VISIBLE) { 255 String transitionName = view.getTransitionName(); 256 if (transitionName != null) { 257 namedViews.put(transitionName, view); 258 } 259 if (view instanceof ViewGroup) { 260 ViewGroup viewGroup = (ViewGroup) view; 261 int count = viewGroup.getChildCount(); 262 for (int i = 0; i < count; i++) { 263 View child = viewGroup.getChildAt(i); 264 findNamedViews(namedViews, child); 265 } 266 } 267 } 268 } 269 270 public static void cleanupTransitions(final View sceneRoot, final View nonExistentView, 271 Object enterTransitionObject, final ArrayList<View> enteringViews, 272 Object exitTransitionObject, final ArrayList<View> exitingViews, 273 Object sharedElementTransitionObject, final ArrayList<View> sharedElementTargets, 274 Object overallTransitionObject, final ArrayList<View> hiddenViews, 275 final Map<String, View> renamedViews) { 276 final Transition enterTransition = (Transition) enterTransitionObject; 277 final Transition exitTransition = (Transition) exitTransitionObject; 278 final Transition sharedElementTransition = (Transition) sharedElementTransitionObject; 279 final Transition overallTransition = (Transition) overallTransitionObject; 280 if (overallTransition != null) { 281 sceneRoot.getViewTreeObserver().addOnPreDrawListener( 282 new ViewTreeObserver.OnPreDrawListener() { 283 public boolean onPreDraw() { 284 sceneRoot.getViewTreeObserver().removeOnPreDrawListener(this); 285 if (enterTransition != null) { 286 enterTransition.removeTarget(nonExistentView); 287 removeTargets(enterTransition, enteringViews); 288 } 289 if (exitTransition != null) { 290 removeTargets(exitTransition, exitingViews); 291 } 292 if (sharedElementTransition != null) { 293 removeTargets(sharedElementTransition, sharedElementTargets); 294 } 295 for (Map.Entry<String, View> entry : renamedViews.entrySet()) { 296 View view = entry.getValue(); 297 String name = entry.getKey(); 298 view.setTransitionName(name); 299 } 300 int numViews = hiddenViews.size(); 301 for (int i = 0; i < numViews; i++) { 302 overallTransition.excludeTarget(hiddenViews.get(i), false); 303 } 304 overallTransition.excludeTarget(nonExistentView, false); 305 return true; 306 } 307 }); 308 } 309 } 310 311 /** 312 * This method removes the views from transitions that target ONLY those views. 313 * The views list should match those added in addTargets and should contain 314 * one view that is not in the view hierarchy (state.nonExistentView). 315 */ 316 public static void removeTargets(Object transitionObject, ArrayList<View> views) { 317 Transition transition = (Transition) transitionObject; 318 if (transition instanceof TransitionSet) { 319 TransitionSet set = (TransitionSet) transition; 320 int numTransitions = set.getTransitionCount(); 321 for (int i = 0; i < numTransitions; i++) { 322 Transition child = set.getTransitionAt(i); 323 removeTargets(child, views); 324 } 325 } else if (!hasSimpleTarget(transition)) { 326 List<View> targets = transition.getTargets(); 327 if (targets != null && targets.size() == views.size() && 328 targets.containsAll(views)) { 329 // We have an exact match. We must have added these earlier in addTargets 330 for (int i = views.size() - 1; i >= 0; i--) { 331 transition.removeTarget(views.get(i)); 332 } 333 } 334 } 335 } 336 337 /** 338 * This method adds views as targets to the transition, but only if the transition 339 * doesn't already have a target. It is best for views to contain one View object 340 * that does not exist in the view hierarchy (state.nonExistentView) so that 341 * when they are removed later, a list match will suffice to remove the targets. 342 * Otherwise, if you happened to have targeted the exact views for the transition, 343 * the removeTargets call will remove them unexpectedly. 344 */ 345 public static void addTargets(Object transitionObject, ArrayList<View> views) { 346 Transition transition = (Transition) transitionObject; 347 if (transition instanceof TransitionSet) { 348 TransitionSet set = (TransitionSet) transition; 349 int numTransitions = set.getTransitionCount(); 350 for (int i = 0; i < numTransitions; i++) { 351 Transition child = set.getTransitionAt(i); 352 addTargets(child, views); 353 } 354 } else if (!hasSimpleTarget(transition)) { 355 List<View> targets = transition.getTargets(); 356 if (isNullOrEmpty(targets)) { 357 // We can just add the target views 358 int numViews = views.size(); 359 for (int i = 0; i < numViews; i++) { 360 transition.addTarget(views.get(i)); 361 } 362 } 363 } 364 } 365 366 private static boolean hasSimpleTarget(Transition transition) { 367 return !isNullOrEmpty(transition.getTargetIds()) || 368 !isNullOrEmpty(transition.getTargetNames()) || 369 !isNullOrEmpty(transition.getTargetTypes()); 370 } 371 372 private static boolean isNullOrEmpty(List list) { 373 return list == null || list.isEmpty(); 374 } 375 376 public interface ViewRetriever { 377 View getView(); 378 } 379 380 public static class EpicenterView { 381 public View epicenter; 382 } 383} 384