FragmentTransitionCompat21.java revision 434607fedf2615b20b5669f240b46cb09a167caf
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.graphics.Rect; 20import android.support.annotation.RequiresApi; 21import android.transition.Transition; 22import android.transition.TransitionManager; 23import android.transition.TransitionSet; 24import android.view.View; 25import android.view.ViewGroup; 26 27import java.util.ArrayList; 28import java.util.List; 29import java.util.Map; 30 31@RequiresApi(21) 32class FragmentTransitionCompat21 { 33 34 /** 35 * Returns a clone of a transition or null if it is null 36 */ 37 public static Object cloneTransition(Object transition) { 38 Transition copy = null; 39 if (transition != null) { 40 copy = ((Transition) transition).clone(); 41 } 42 return copy; 43 } 44 45 /** 46 * Wraps a transition in a TransitionSet and returns the set. If transition is null, null is 47 * returned. 48 */ 49 public static Object wrapTransitionInSet(Object transition) { 50 if (transition == null) { 51 return null; 52 } 53 TransitionSet transitionSet = new TransitionSet(); 54 transitionSet.addTransition((Transition) transition); 55 return transitionSet; 56 } 57 58 /** 59 * Finds all children of the shared elements and sets the wrapping TransitionSet 60 * targets to point to those. It also limits transitions that have no targets to the 61 * specific shared elements. This allows developers to target child views of the 62 * shared elements specifically, but this doesn't happen by default. 63 */ 64 public static void setSharedElementTargets(Object transitionObj, 65 View nonExistentView, ArrayList<View> sharedViews) { 66 TransitionSet transition = (TransitionSet) transitionObj; 67 final List<View> views = transition.getTargets(); 68 views.clear(); 69 final int count = sharedViews.size(); 70 for (int i = 0; i < count; i++) { 71 final View view = sharedViews.get(i); 72 bfsAddViewChildren(views, view); 73 } 74 views.add(nonExistentView); 75 sharedViews.add(nonExistentView); 76 addTargets(transition, sharedViews); 77 } 78 79 /** 80 * Uses a breadth-first scheme to add startView and all of its children to views. 81 * It won't add a child if it is already in views. 82 */ 83 private static void bfsAddViewChildren(final List<View> views, final View startView) { 84 final int startIndex = views.size(); 85 if (containedBeforeIndex(views, startView, startIndex)) { 86 return; // This child is already in the list, so all its children are also. 87 } 88 views.add(startView); 89 for (int index = startIndex; index < views.size(); index++) { 90 final View view = views.get(index); 91 if (view instanceof ViewGroup) { 92 ViewGroup viewGroup = (ViewGroup) view; 93 final int childCount = viewGroup.getChildCount(); 94 for (int childIndex = 0; childIndex < childCount; childIndex++) { 95 final View child = viewGroup.getChildAt(childIndex); 96 if (!containedBeforeIndex(views, child, startIndex)) { 97 views.add(child); 98 } 99 } 100 } 101 } 102 } 103 104 /** 105 * Does a linear search through views for view, limited to maxIndex. 106 */ 107 private static boolean containedBeforeIndex(final List<View> views, final View view, 108 final int maxIndex) { 109 for (int i = 0; i < maxIndex; i++) { 110 if (views.get(i) == view) { 111 return true; 112 } 113 } 114 return false; 115 } 116 117 /** 118 * Sets a transition epicenter to the rectangle of a given View. 119 */ 120 public static void setEpicenter(Object transitionObj, View view) { 121 if (view != null) { 122 Transition transition = (Transition) transitionObj; 123 final Rect epicenter = new Rect(); 124 getBoundsOnScreen(view, epicenter); 125 126 transition.setEpicenterCallback(new Transition.EpicenterCallback() { 127 @Override 128 public Rect onGetEpicenter(Transition transition) { 129 return epicenter; 130 } 131 }); 132 } 133 } 134 135 /** 136 * Replacement for view.getBoundsOnScreen because that is not public. This returns a rect 137 * containing the bounds relative to the screen that the view is in. 138 */ 139 public static void getBoundsOnScreen(View view, Rect epicenter) { 140 int[] loc = new int[2]; 141 view.getLocationOnScreen(loc); 142 epicenter.set(loc[0], loc[1], loc[0] + view.getWidth(), loc[1] + view.getHeight()); 143 } 144 145 /** 146 * This method adds views as targets to the transition, but only if the transition 147 * doesn't already have a target. It is best for views to contain one View object 148 * that does not exist in the view hierarchy (state.nonExistentView) so that 149 * when they are removed later, a list match will suffice to remove the targets. 150 * Otherwise, if you happened to have targeted the exact views for the transition, 151 * the replaceTargets call will remove them unexpectedly. 152 */ 153 public static void addTargets(Object transitionObj, ArrayList<View> views) { 154 Transition transition = (Transition) transitionObj; 155 if (transition == null) { 156 return; 157 } 158 if (transition instanceof TransitionSet) { 159 TransitionSet set = (TransitionSet) transition; 160 int numTransitions = set.getTransitionCount(); 161 for (int i = 0; i < numTransitions; i++) { 162 Transition child = set.getTransitionAt(i); 163 addTargets(child, views); 164 } 165 } else if (!hasSimpleTarget(transition)) { 166 List<View> targets = transition.getTargets(); 167 if (isNullOrEmpty(targets)) { 168 // We can just add the target views 169 int numViews = views.size(); 170 for (int i = 0; i < numViews; i++) { 171 transition.addTarget(views.get(i)); 172 } 173 } 174 } 175 } 176 177 /** 178 * Returns true if there are any targets based on ID, transition or type. 179 */ 180 private static boolean hasSimpleTarget(Transition transition) { 181 return !isNullOrEmpty(transition.getTargetIds()) 182 || !isNullOrEmpty(transition.getTargetNames()) 183 || !isNullOrEmpty(transition.getTargetTypes()); 184 } 185 186 /** 187 * Simple utility to detect if a list is null or has no elements. 188 */ 189 private static boolean isNullOrEmpty(List list) { 190 return list == null || list.isEmpty(); 191 } 192 193 /** 194 * Creates a TransitionSet that plays all passed transitions together. Any null 195 * transitions passed will not be added to the set. If all are null, then an empty 196 * TransitionSet will be returned. 197 */ 198 public static Object mergeTransitionsTogether(Object transition1, Object transition2, 199 Object transition3) { 200 TransitionSet transitionSet = new TransitionSet(); 201 if (transition1 != null) { 202 transitionSet.addTransition((Transition) transition1); 203 } 204 if (transition2 != null) { 205 transitionSet.addTransition((Transition) transition2); 206 } 207 if (transition3 != null) { 208 transitionSet.addTransition((Transition) transition3); 209 } 210 return transitionSet; 211 } 212 213 /** 214 * After the transition completes, the fragment's view is set to GONE and the exiting 215 * views are set to VISIBLE. 216 */ 217 public static void scheduleHideFragmentView(Object exitTransitionObj, final View fragmentView, 218 final ArrayList<View> exitingViews) { 219 Transition exitTransition = (Transition) exitTransitionObj; 220 exitTransition.addListener(new Transition.TransitionListener() { 221 @Override 222 public void onTransitionStart(Transition transition) { 223 } 224 225 @Override 226 public void onTransitionEnd(Transition transition) { 227 transition.removeListener(this); 228 fragmentView.setVisibility(View.GONE); 229 final int numViews = exitingViews.size(); 230 for (int i = 0; i < numViews; i++) { 231 exitingViews.get(i).setVisibility(View.VISIBLE); 232 } 233 } 234 235 @Override 236 public void onTransitionCancel(Transition transition) { 237 } 238 239 @Override 240 public void onTransitionPause(Transition transition) { 241 } 242 243 @Override 244 public void onTransitionResume(Transition transition) { 245 } 246 }); 247 } 248 249 /** 250 * Combines enter, exit, and shared element transition so that they play in the proper 251 * sequence. First the exit transition plays along with the shared element transition. 252 * When the exit transition completes, the enter transition starts. The shared element 253 * transition can continue running while the enter transition plays. 254 * 255 * @return A TransitionSet with all of enter, exit, and shared element transitions in 256 * it (modulo null values), ordered such that they play in the proper sequence. 257 */ 258 public static Object mergeTransitionsInSequence(Object exitTransitionObj, 259 Object enterTransitionObj, Object sharedElementTransitionObj) { 260 // First do exit, then enter, but allow shared element transition to happen 261 // during both. 262 Transition staggered = null; 263 final Transition exitTransition = (Transition) exitTransitionObj; 264 final Transition enterTransition = (Transition) enterTransitionObj; 265 final Transition sharedElementTransition = (Transition) sharedElementTransitionObj; 266 if (exitTransition != null && enterTransition != null) { 267 staggered = new TransitionSet() 268 .addTransition(exitTransition) 269 .addTransition(enterTransition) 270 .setOrdering(TransitionSet.ORDERING_SEQUENTIAL); 271 } else if (exitTransition != null) { 272 staggered = exitTransition; 273 } else if (enterTransition != null) { 274 staggered = enterTransition; 275 } 276 if (sharedElementTransition != null) { 277 TransitionSet together = new TransitionSet(); 278 if (staggered != null) { 279 together.addTransition(staggered); 280 } 281 together.addTransition(sharedElementTransition); 282 return together; 283 } else { 284 return staggered; 285 } 286 } 287 288 /** 289 * Calls {@link TransitionManager#beginDelayedTransition(ViewGroup, Transition)}. 290 */ 291 public static void beginDelayedTransition(ViewGroup sceneRoot, Object transition) { 292 TransitionManager.beginDelayedTransition(sceneRoot, (Transition) transition); 293 } 294 295 /** 296 * Prepares for setting the shared element names by gathering the names of the incoming 297 * shared elements and clearing them. {@link #setNameOverridesOptimized(View, ArrayList, 298 * ArrayList, ArrayList, Map)} must be called after this to complete setting the shared element 299 * name overrides. This must be called before 300 * {@link #beginDelayedTransition(ViewGroup, Object)}. 301 */ 302 public static ArrayList<String> prepareSetNameOverridesOptimized( 303 final ArrayList<View> sharedElementsIn) { 304 final ArrayList<String> names = new ArrayList<>(); 305 final int numSharedElements = sharedElementsIn.size(); 306 for (int i = 0; i < numSharedElements; i++) { 307 final View view = sharedElementsIn.get(i); 308 names.add(view.getTransitionName()); 309 view.setTransitionName(null); 310 } 311 return names; 312 } 313 314 /** 315 * Changes the shared element names for the incoming shared eleemnts to match those of the 316 * outgoing shared elements. This also temporarily clears the shared element names of the 317 * outgoing shared elements. Must be called after 318 * {@link #beginDelayedTransition(ViewGroup, Object)}. 319 */ 320 public static void setNameOverridesOptimized(final View sceneRoot, 321 final ArrayList<View> sharedElementsOut, final ArrayList<View> sharedElementsIn, 322 final ArrayList<String> inNames, final Map<String, String> nameOverrides) { 323 final int numSharedElements = sharedElementsIn.size(); 324 final ArrayList<String> outNames = new ArrayList<>(); 325 326 for (int i = 0; i < numSharedElements; i++) { 327 final View view = sharedElementsOut.get(i); 328 final String name = view.getTransitionName(); 329 outNames.add(name); 330 if (name == null) { 331 continue; 332 } 333 view.setTransitionName(null); 334 final String inName = nameOverrides.get(name); 335 for (int j = 0; j < numSharedElements; j++) { 336 if (inName.equals(inNames.get(j))) { 337 sharedElementsIn.get(j).setTransitionName(name); 338 break; 339 } 340 } 341 } 342 343 OneShotPreDrawListener.add(sceneRoot, new Runnable() { 344 @Override 345 public void run() { 346 for (int i = 0; i < numSharedElements; i++) { 347 sharedElementsIn.get(i).setTransitionName(inNames.get(i)); 348 sharedElementsOut.get(i).setTransitionName(outNames.get(i)); 349 } 350 } 351 }); 352 } 353 354 /** 355 * Gets the Views in the hierarchy affected by entering and exiting Activity Scene transitions. 356 * @param transitioningViews This View will be added to transitioningViews if it is VISIBLE and 357 * a normal View or a ViewGroup with 358 * {@link android.view.ViewGroup#isTransitionGroup()} true. 359 * @param view The base of the view hierarchy to look in. 360 */ 361 public static void captureTransitioningViews(ArrayList<View> transitioningViews, View view) { 362 if (view.getVisibility() == View.VISIBLE) { 363 if (view instanceof ViewGroup) { 364 ViewGroup viewGroup = (ViewGroup) view; 365 if (viewGroup.isTransitionGroup()) { 366 transitioningViews.add(viewGroup); 367 } else { 368 int count = viewGroup.getChildCount(); 369 for (int i = 0; i < count; i++) { 370 View child = viewGroup.getChildAt(i); 371 captureTransitioningViews(transitioningViews, child); 372 } 373 } 374 } else { 375 transitioningViews.add(view); 376 } 377 } 378 } 379 380 /** 381 * Finds all views that have transition names in the hierarchy under the given view and 382 * stores them in {@code namedViews} map with the name as the key. 383 */ 384 public static void findNamedViews(Map<String, View> namedViews, View view) { 385 if (view.getVisibility() == View.VISIBLE) { 386 String transitionName = view.getTransitionName(); 387 if (transitionName != null) { 388 namedViews.put(transitionName, view); 389 } 390 if (view instanceof ViewGroup) { 391 ViewGroup viewGroup = (ViewGroup) view; 392 int count = viewGroup.getChildCount(); 393 for (int i = 0; i < count; i++) { 394 View child = viewGroup.getChildAt(i); 395 findNamedViews(namedViews, child); 396 } 397 } 398 } 399 } 400 401 public static void setNameOverridesUnoptimized(final View sceneRoot, 402 final ArrayList<View> sharedElementsIn, final Map<String, String> nameOverrides) { 403 OneShotPreDrawListener.add(sceneRoot, new Runnable() { 404 @Override 405 public void run() { 406 final int numSharedElements = sharedElementsIn.size(); 407 for (int i = 0; i < numSharedElements; i++) { 408 View view = sharedElementsIn.get(i); 409 String name = view.getTransitionName(); 410 if (name != null) { 411 String inName = findKeyForValue(nameOverrides, name); 412 view.setTransitionName(inName); 413 } 414 } 415 } 416 }); 417 } 418 419 /** 420 * Utility to find the String key in {@code map} that maps to {@code value}. 421 */ 422 private static String findKeyForValue(Map<String, String> map, String value) { 423 for (Map.Entry<String, String> entry : map.entrySet()) { 424 if (value.equals(entry.getValue())) { 425 return entry.getKey(); 426 } 427 } 428 return null; 429 } 430 431 /** 432 * After the transition has started, remove all targets that we added to the transitions 433 * so that the transitions are left in a clean state. 434 */ 435 public static void scheduleRemoveTargets(final Object overallTransitionObj, 436 final Object enterTransition, final ArrayList<View> enteringViews, 437 final Object exitTransition, final ArrayList<View> exitingViews, 438 final Object sharedElementTransition, final ArrayList<View> sharedElementsIn) { 439 final Transition overallTransition = (Transition) overallTransitionObj; 440 overallTransition.addListener(new Transition.TransitionListener() { 441 @Override 442 public void onTransitionStart(Transition transition) { 443 if (enterTransition != null) { 444 replaceTargets(enterTransition, enteringViews, null); 445 } 446 if (exitTransition != null) { 447 replaceTargets(exitTransition, exitingViews, null); 448 } 449 if (sharedElementTransition != null) { 450 replaceTargets(sharedElementTransition, sharedElementsIn, null); 451 } 452 } 453 454 @Override 455 public void onTransitionEnd(Transition transition) { 456 } 457 458 @Override 459 public void onTransitionCancel(Transition transition) { 460 } 461 462 @Override 463 public void onTransitionPause(Transition transition) { 464 } 465 466 @Override 467 public void onTransitionResume(Transition transition) { 468 } 469 }); 470 } 471 472 /** 473 * Swap the targets for the shared element transition from those Views in sharedElementsOut 474 * to those in sharedElementsIn 475 */ 476 public static void swapSharedElementTargets(Object sharedElementTransitionObj, 477 ArrayList<View> sharedElementsOut, ArrayList<View> sharedElementsIn) { 478 TransitionSet sharedElementTransition = (TransitionSet) sharedElementTransitionObj; 479 if (sharedElementTransition != null) { 480 sharedElementTransition.getTargets().clear(); 481 sharedElementTransition.getTargets().addAll(sharedElementsIn); 482 replaceTargets(sharedElementTransition, sharedElementsOut, sharedElementsIn); 483 } 484 } 485 486 487 /** 488 * This method removes the views from transitions that target ONLY those views and 489 * replaces them with the new targets list. 490 * The views list should match those added in addTargets and should contain 491 * one view that is not in the view hierarchy (state.nonExistentView). 492 */ 493 public static void replaceTargets(Object transitionObj, ArrayList<View> oldTargets, 494 ArrayList<View> newTargets) { 495 Transition transition = (Transition) transitionObj; 496 if (transition instanceof TransitionSet) { 497 TransitionSet set = (TransitionSet) transition; 498 int numTransitions = set.getTransitionCount(); 499 for (int i = 0; i < numTransitions; i++) { 500 Transition child = set.getTransitionAt(i); 501 replaceTargets(child, oldTargets, newTargets); 502 } 503 } else if (!hasSimpleTarget(transition)) { 504 List<View> targets = transition.getTargets(); 505 if (targets != null && targets.size() == oldTargets.size() 506 && targets.containsAll(oldTargets)) { 507 // We have an exact match. We must have added these earlier in addTargets 508 final int targetCount = newTargets == null ? 0 : newTargets.size(); 509 for (int i = 0; i < targetCount; i++) { 510 transition.addTarget(newTargets.get(i)); 511 } 512 for (int i = oldTargets.size() - 1; i >= 0; i--) { 513 transition.removeTarget(oldTargets.get(i)); 514 } 515 } 516 } 517 } 518 519 /** 520 * Adds a View target to a transition. If transitionObj is null, nothing is done. 521 */ 522 public static void addTarget(Object transitionObj, View view) { 523 if (transitionObj != null) { 524 Transition transition = (Transition) transitionObj; 525 transition.addTarget(view); 526 } 527 } 528 529 /** 530 * Remove a View target to a transition. If transitionObj is null, nothing is done. 531 */ 532 public static void removeTarget(Object transitionObj, View view) { 533 if (transitionObj != null) { 534 Transition transition = (Transition) transitionObj; 535 transition.removeTarget(view); 536 } 537 } 538 539 /** 540 * Sets the epicenter of a transition to a rect object. The object can be modified until 541 * the transition runs. 542 */ 543 public static void setEpicenter(Object transitionObj, final Rect epicenter) { 544 if (transitionObj != null) { 545 Transition transition = (Transition) transitionObj; 546 transition.setEpicenterCallback(new Transition.EpicenterCallback() { 547 @Override 548 public Rect onGetEpicenter(Transition transition) { 549 if (epicenter == null || epicenter.isEmpty()) { 550 return null; 551 } 552 return epicenter; 553 } 554 }); 555 } 556 } 557 558 public static void scheduleNameReset(final ViewGroup sceneRoot, 559 final ArrayList<View> sharedElementsIn, final Map<String, String> nameOverrides) { 560 OneShotPreDrawListener.add(sceneRoot, new Runnable() { 561 @Override 562 public void run() { 563 final int numSharedElements = sharedElementsIn.size(); 564 for (int i = 0; i < numSharedElements; i++) { 565 final View view = sharedElementsIn.get(i); 566 final String name = view.getTransitionName(); 567 final String inName = nameOverrides.get(name); 568 view.setTransitionName(inName); 569 } 570 } 571 }); 572 } 573} 574