1 /* 2 * Copyright 2018 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 package androidx.fragment.app; 17 18 import android.graphics.Rect; 19 import android.os.Build; 20 import android.util.SparseArray; 21 import android.view.View; 22 import android.view.ViewGroup; 23 24 import androidx.collection.ArrayMap; 25 import androidx.core.app.SharedElementCallback; 26 import androidx.core.view.ViewCompat; 27 28 import java.util.ArrayList; 29 import java.util.Collection; 30 import java.util.List; 31 import java.util.Map; 32 33 /** 34 * Contains the Fragment Transition functionality for both ordered and reordered 35 * Fragment Transactions. With reordered fragment transactions, all Views have been 36 * added to the View hierarchy prior to calling startTransitions. With ordered 37 * fragment transactions, Views will be removed and added after calling startTransitions. 38 */ 39 class FragmentTransition { 40 /** 41 * The inverse of all BackStackRecord operation commands. This assumes that 42 * REPLACE operations have already been replaced by add/remove operations. 43 */ 44 private static final int[] INVERSE_OPS = { 45 BackStackRecord.OP_NULL, // inverse of OP_NULL (error) 46 BackStackRecord.OP_REMOVE, // inverse of OP_ADD 47 BackStackRecord.OP_NULL, // inverse of OP_REPLACE (error) 48 BackStackRecord.OP_ADD, // inverse of OP_REMOVE 49 BackStackRecord.OP_SHOW, // inverse of OP_HIDE 50 BackStackRecord.OP_HIDE, // inverse of OP_SHOW 51 BackStackRecord.OP_ATTACH, // inverse of OP_DETACH 52 BackStackRecord.OP_DETACH, // inverse of OP_ATTACH 53 BackStackRecord.OP_UNSET_PRIMARY_NAV, // inverse of OP_SET_PRIMARY_NAV 54 BackStackRecord.OP_SET_PRIMARY_NAV, // inverse of OP_UNSET_PRIMARY_NAV 55 }; 56 57 private static final FragmentTransitionImpl PLATFORM_IMPL = Build.VERSION.SDK_INT >= 21 58 ? new FragmentTransitionCompat21() 59 : null; 60 61 private static final FragmentTransitionImpl SUPPORT_IMPL = resolveSupportImpl(); 62 63 private static FragmentTransitionImpl resolveSupportImpl() { 64 try { 65 @SuppressWarnings("unchecked") 66 Class<FragmentTransitionImpl> impl = (Class<FragmentTransitionImpl>) Class.forName( 67 "androidx.transition.FragmentTransitionSupport"); 68 return impl.getDeclaredConstructor().newInstance(); 69 } catch (Exception ignored) { 70 // support-transition is not loaded; ignore 71 } 72 return null; 73 } 74 75 /** 76 * The main entry point for Fragment Transitions, this starts the transitions 77 * set on the leaving Fragment's {@link Fragment#getExitTransition()}, the 78 * entering Fragment's {@link Fragment#getEnterTransition()} and 79 * {@link Fragment#getSharedElementEnterTransition()}. When popping, 80 * the leaving Fragment's {@link Fragment#getReturnTransition()} and 81 * {@link Fragment#getSharedElementReturnTransition()} and the entering 82 * {@link Fragment#getReenterTransition()} will be run. 83 * <p> 84 * With reordered Fragment Transitions, all Views have been added to the 85 * View hierarchy prior to calling this method. The incoming Fragment's Views 86 * will be INVISIBLE. With ordered Fragment Transitions, this method 87 * is called before any change has been made to the hierarchy. That means 88 * that the added Fragments have not created their Views yet and the hierarchy 89 * is unknown. 90 * 91 * @param fragmentManager The executing FragmentManagerImpl 92 * @param records The list of transactions being executed. 93 * @param isRecordPop For each transaction, whether it is a pop transaction or not. 94 * @param startIndex The first index into records and isRecordPop to execute as 95 * part of this transition. 96 * @param endIndex One past the last index into records and isRecordPop to execute 97 * as part of this transition. 98 * @param isReordered true if this is a reordered transaction, meaning that the 99 * Views of incoming fragments have been added. false if the 100 * transaction has yet to be run and Views haven't been created. 101 */ 102 static void startTransitions(FragmentManagerImpl fragmentManager, 103 ArrayList<BackStackRecord> records, ArrayList<Boolean> isRecordPop, 104 int startIndex, int endIndex, boolean isReordered) { 105 if (fragmentManager.mCurState < Fragment.CREATED) { 106 return; 107 } 108 109 SparseArray<FragmentContainerTransition> transitioningFragments = 110 new SparseArray<>(); 111 for (int i = startIndex; i < endIndex; i++) { 112 final BackStackRecord record = records.get(i); 113 final boolean isPop = isRecordPop.get(i); 114 if (isPop) { 115 calculatePopFragments(record, transitioningFragments, isReordered); 116 } else { 117 calculateFragments(record, transitioningFragments, isReordered); 118 } 119 } 120 121 if (transitioningFragments.size() != 0) { 122 final View nonExistentView = new View(fragmentManager.mHost.getContext()); 123 final int numContainers = transitioningFragments.size(); 124 for (int i = 0; i < numContainers; i++) { 125 int containerId = transitioningFragments.keyAt(i); 126 ArrayMap<String, String> nameOverrides = calculateNameOverrides(containerId, 127 records, isRecordPop, startIndex, endIndex); 128 129 FragmentContainerTransition containerTransition = 130 transitioningFragments.valueAt(i); 131 132 if (isReordered) { 133 configureTransitionsReordered(fragmentManager, containerId, 134 containerTransition, nonExistentView, nameOverrides); 135 } else { 136 configureTransitionsOrdered(fragmentManager, containerId, 137 containerTransition, nonExistentView, nameOverrides); 138 } 139 } 140 } 141 } 142 143 /** 144 * Iterates through the transactions that affect a given fragment container 145 * and tracks the shared element names across transactions. This is most useful 146 * in pop transactions where the names of shared elements are known. 147 * 148 * @param containerId The container ID that is executing the transition. 149 * @param records The list of transactions being executed. 150 * @param isRecordPop For each transaction, whether it is a pop transaction or not. 151 * @param startIndex The first index into records and isRecordPop to execute as 152 * part of this transition. 153 * @param endIndex One past the last index into records and isRecordPop to execute 154 * as part of this transition. 155 * @return A map from the initial shared element name to the final shared element name 156 * before any onMapSharedElements is run. 157 */ 158 private static ArrayMap<String, String> calculateNameOverrides(int containerId, 159 ArrayList<BackStackRecord> records, ArrayList<Boolean> isRecordPop, 160 int startIndex, int endIndex) { 161 ArrayMap<String, String> nameOverrides = new ArrayMap<>(); 162 for (int recordNum = endIndex - 1; recordNum >= startIndex; recordNum--) { 163 final BackStackRecord record = records.get(recordNum); 164 if (!record.interactsWith(containerId)) { 165 continue; 166 } 167 final boolean isPop = isRecordPop.get(recordNum); 168 if (record.mSharedElementSourceNames != null) { 169 final int numSharedElements = record.mSharedElementSourceNames.size(); 170 final ArrayList<String> sources; 171 final ArrayList<String> targets; 172 if (isPop) { 173 targets = record.mSharedElementSourceNames; 174 sources = record.mSharedElementTargetNames; 175 } else { 176 sources = record.mSharedElementSourceNames; 177 targets = record.mSharedElementTargetNames; 178 } 179 for (int i = 0; i < numSharedElements; i++) { 180 String sourceName = sources.get(i); 181 String targetName = targets.get(i); 182 String previousTarget = nameOverrides.remove(targetName); 183 if (previousTarget != null) { 184 nameOverrides.put(sourceName, previousTarget); 185 } else { 186 nameOverrides.put(sourceName, targetName); 187 } 188 } 189 } 190 } 191 return nameOverrides; 192 } 193 194 /** 195 * Configures a transition for a single fragment container for which the transaction was 196 * reordered. That means that all Fragment Views have been added and incoming fragment 197 * Views are marked invisible. 198 * 199 * @param fragmentManager The executing FragmentManagerImpl 200 * @param containerId The container ID that is executing the transition. 201 * @param fragments A structure holding the transitioning fragments in this container. 202 * @param nonExistentView A View that does not exist in the hierarchy. This is used to 203 * prevent transitions from acting on other Views when there is no 204 * other target. 205 * @param nameOverrides A map of the shared element names from the starting fragment to 206 * the final fragment's Views as given in 207 * {@link FragmentTransaction#addSharedElement(View, String)}. 208 */ 209 private static void configureTransitionsReordered(FragmentManagerImpl fragmentManager, 210 int containerId, FragmentContainerTransition fragments, 211 View nonExistentView, ArrayMap<String, String> nameOverrides) { 212 ViewGroup sceneRoot = null; 213 if (fragmentManager.mContainer.onHasView()) { 214 sceneRoot = (ViewGroup) fragmentManager.mContainer.onFindViewById(containerId); 215 } 216 if (sceneRoot == null) { 217 return; 218 } 219 final Fragment inFragment = fragments.lastIn; 220 final Fragment outFragment = fragments.firstOut; 221 final FragmentTransitionImpl impl = chooseImpl(outFragment, inFragment); 222 if (impl == null) { 223 return; 224 } 225 final boolean inIsPop = fragments.lastInIsPop; 226 final boolean outIsPop = fragments.firstOutIsPop; 227 228 ArrayList<View> sharedElementsIn = new ArrayList<>(); 229 ArrayList<View> sharedElementsOut = new ArrayList<>(); 230 Object enterTransition = getEnterTransition(impl, inFragment, inIsPop); 231 Object exitTransition = getExitTransition(impl, outFragment, outIsPop); 232 233 Object sharedElementTransition = configureSharedElementsReordered(impl, sceneRoot, 234 nonExistentView, nameOverrides, fragments, sharedElementsOut, sharedElementsIn, 235 enterTransition, exitTransition); 236 237 if (enterTransition == null && sharedElementTransition == null 238 && exitTransition == null) { 239 return; // no transitions! 240 } 241 242 ArrayList<View> exitingViews = configureEnteringExitingViews(impl, exitTransition, 243 outFragment, sharedElementsOut, nonExistentView); 244 245 ArrayList<View> enteringViews = configureEnteringExitingViews(impl, enterTransition, 246 inFragment, sharedElementsIn, nonExistentView); 247 248 setViewVisibility(enteringViews, View.INVISIBLE); 249 250 Object transition = mergeTransitions(impl, enterTransition, exitTransition, 251 sharedElementTransition, inFragment, inIsPop); 252 253 if (transition != null) { 254 replaceHide(impl, exitTransition, outFragment, exitingViews); 255 ArrayList<String> inNames = 256 impl.prepareSetNameOverridesReordered(sharedElementsIn); 257 impl.scheduleRemoveTargets(transition, 258 enterTransition, enteringViews, exitTransition, exitingViews, 259 sharedElementTransition, sharedElementsIn); 260 impl.beginDelayedTransition(sceneRoot, transition); 261 impl.setNameOverridesReordered(sceneRoot, sharedElementsOut, 262 sharedElementsIn, inNames, nameOverrides); 263 setViewVisibility(enteringViews, View.VISIBLE); 264 impl.swapSharedElementTargets(sharedElementTransition, 265 sharedElementsOut, sharedElementsIn); 266 } 267 } 268 269 /** 270 * Replace hide operations with visibility changes on the exiting views. Instead of making 271 * the entire fragment's view GONE, make each exiting view INVISIBLE. At the end of the 272 * transition, make the fragment's view GONE. 273 */ 274 private static void replaceHide(FragmentTransitionImpl impl, 275 Object exitTransition, Fragment exitingFragment, 276 final ArrayList<View> exitingViews) { 277 if (exitingFragment != null && exitTransition != null && exitingFragment.mAdded 278 && exitingFragment.mHidden && exitingFragment.mHiddenChanged) { 279 exitingFragment.setHideReplaced(true); 280 impl.scheduleHideFragmentView(exitTransition, 281 exitingFragment.getView(), exitingViews); 282 final ViewGroup container = exitingFragment.mContainer; 283 OneShotPreDrawListener.add(container, new Runnable() { 284 @Override 285 public void run() { 286 setViewVisibility(exitingViews, View.INVISIBLE); 287 } 288 }); 289 } 290 } 291 292 /** 293 * Configures a transition for a single fragment container for which the transaction was 294 * ordered. That means that the transaction has not been executed yet, so incoming 295 * Views are not yet known. 296 * 297 * @param fragmentManager The executing FragmentManagerImpl 298 * @param containerId The container ID that is executing the transition. 299 * @param fragments A structure holding the transitioning fragments in this container. 300 * @param nonExistentView A View that does not exist in the hierarchy. This is used to 301 * prevent transitions from acting on other Views when there is no 302 * other target. 303 * @param nameOverrides A map of the shared element names from the starting fragment to 304 * the final fragment's Views as given in 305 * {@link FragmentTransaction#addSharedElement(View, String)}. 306 */ 307 private static void configureTransitionsOrdered(FragmentManagerImpl fragmentManager, 308 int containerId, FragmentContainerTransition fragments, 309 View nonExistentView, ArrayMap<String, String> nameOverrides) { 310 ViewGroup sceneRoot = null; 311 if (fragmentManager.mContainer.onHasView()) { 312 sceneRoot = (ViewGroup) fragmentManager.mContainer.onFindViewById(containerId); 313 } 314 if (sceneRoot == null) { 315 return; 316 } 317 final Fragment inFragment = fragments.lastIn; 318 final Fragment outFragment = fragments.firstOut; 319 final FragmentTransitionImpl impl = chooseImpl(outFragment, inFragment); 320 if (impl == null) { 321 return; 322 } 323 final boolean inIsPop = fragments.lastInIsPop; 324 final boolean outIsPop = fragments.firstOutIsPop; 325 326 Object enterTransition = getEnterTransition(impl, inFragment, inIsPop); 327 Object exitTransition = getExitTransition(impl, outFragment, outIsPop); 328 329 ArrayList<View> sharedElementsOut = new ArrayList<>(); 330 ArrayList<View> sharedElementsIn = new ArrayList<>(); 331 332 Object sharedElementTransition = configureSharedElementsOrdered(impl, sceneRoot, 333 nonExistentView, nameOverrides, fragments, sharedElementsOut, sharedElementsIn, 334 enterTransition, exitTransition); 335 336 if (enterTransition == null && sharedElementTransition == null 337 && exitTransition == null) { 338 return; // no transitions! 339 } 340 341 ArrayList<View> exitingViews = configureEnteringExitingViews(impl, exitTransition, 342 outFragment, sharedElementsOut, nonExistentView); 343 344 if (exitingViews == null || exitingViews.isEmpty()) { 345 exitTransition = null; 346 } 347 348 // Ensure the entering transition doesn't target anything until the views are made 349 // visible 350 impl.addTarget(enterTransition, nonExistentView); 351 352 Object transition = mergeTransitions(impl, enterTransition, exitTransition, 353 sharedElementTransition, inFragment, fragments.lastInIsPop); 354 355 if (transition != null) { 356 final ArrayList<View> enteringViews = new ArrayList<>(); 357 impl.scheduleRemoveTargets(transition, 358 enterTransition, enteringViews, exitTransition, exitingViews, 359 sharedElementTransition, sharedElementsIn); 360 scheduleTargetChange(impl, sceneRoot, inFragment, nonExistentView, sharedElementsIn, 361 enterTransition, enteringViews, exitTransition, exitingViews); 362 impl.setNameOverridesOrdered(sceneRoot, sharedElementsIn, nameOverrides); 363 364 impl.beginDelayedTransition(sceneRoot, transition); 365 impl.scheduleNameReset(sceneRoot, sharedElementsIn, nameOverrides); 366 } 367 } 368 369 /** 370 * This method is used for fragment transitions for ordrered transactions to change the 371 * enter and exit transition targets after the call to 372 * {@link FragmentTransitionCompat21#beginDelayedTransition(ViewGroup, Object)}. The exit 373 * transition must ensure that it does not target any Views and the enter transition must start 374 * targeting the Views of the incoming Fragment. 375 * 376 * @param sceneRoot The fragment container View 377 * @param inFragment The last fragment that is entering 378 * @param nonExistentView A view that does not exist in the hierarchy that is used as a 379 * transition target to ensure no View is targeted. 380 * @param sharedElementsIn The shared element Views of the incoming fragment 381 * @param enterTransition The enter transition of the incoming fragment 382 * @param enteringViews The entering Views of the incoming fragment 383 * @param exitTransition The exit transition of the outgoing fragment 384 * @param exitingViews The exiting views of the outgoing fragment 385 */ 386 private static void scheduleTargetChange(final FragmentTransitionImpl impl, 387 final ViewGroup sceneRoot, 388 final Fragment inFragment, final View nonExistentView, 389 final ArrayList<View> sharedElementsIn, 390 final Object enterTransition, final ArrayList<View> enteringViews, 391 final Object exitTransition, final ArrayList<View> exitingViews) { 392 OneShotPreDrawListener.add(sceneRoot, new Runnable() { 393 @Override 394 public void run() { 395 if (enterTransition != null) { 396 impl.removeTarget(enterTransition, 397 nonExistentView); 398 ArrayList<View> views = configureEnteringExitingViews(impl, 399 enterTransition, inFragment, sharedElementsIn, nonExistentView); 400 enteringViews.addAll(views); 401 } 402 403 if (exitingViews != null) { 404 if (exitTransition != null) { 405 ArrayList<View> tempExiting = new ArrayList<>(); 406 tempExiting.add(nonExistentView); 407 impl.replaceTargets(exitTransition, exitingViews, 408 tempExiting); 409 } 410 exitingViews.clear(); 411 exitingViews.add(nonExistentView); 412 } 413 } 414 }); 415 } 416 417 /** 418 * Chooses the appropriate implementation depending on the Transition instances hold by the 419 * Fragments. 420 */ 421 private static FragmentTransitionImpl chooseImpl(Fragment outFragment, Fragment inFragment) { 422 // Collect all transition instances 423 final ArrayList<Object> transitions = new ArrayList<>(); 424 if (outFragment != null) { 425 final Object exitTransition = outFragment.getExitTransition(); 426 if (exitTransition != null) { 427 transitions.add(exitTransition); 428 } 429 final Object returnTransition = outFragment.getReturnTransition(); 430 if (returnTransition != null) { 431 transitions.add(returnTransition); 432 } 433 final Object sharedReturnTransition = outFragment.getSharedElementReturnTransition(); 434 if (sharedReturnTransition != null) { 435 transitions.add(sharedReturnTransition); 436 } 437 } 438 if (inFragment != null) { 439 final Object enterTransition = inFragment.getEnterTransition(); 440 if (enterTransition != null) { 441 transitions.add(enterTransition); 442 } 443 final Object reenterTransition = inFragment.getReenterTransition(); 444 if (reenterTransition != null) { 445 transitions.add(reenterTransition); 446 } 447 final Object sharedEnterTransition = inFragment.getSharedElementEnterTransition(); 448 if (sharedEnterTransition != null) { 449 transitions.add(sharedEnterTransition); 450 } 451 } 452 if (transitions.isEmpty()) { 453 return null; // No transition to run 454 } 455 // Pick the implementation that can handle all the transitions 456 if (PLATFORM_IMPL != null && canHandleAll(PLATFORM_IMPL, transitions)) { 457 return PLATFORM_IMPL; 458 } 459 if (SUPPORT_IMPL != null && canHandleAll(SUPPORT_IMPL, transitions)) { 460 return SUPPORT_IMPL; 461 } 462 if (PLATFORM_IMPL != null || SUPPORT_IMPL != null) { 463 throw new IllegalArgumentException("Invalid Transition types"); 464 } 465 return null; 466 } 467 468 private static boolean canHandleAll(FragmentTransitionImpl impl, List<Object> transitions) { 469 for (int i = 0, size = transitions.size(); i < size; i++) { 470 if (!impl.canHandle(transitions.get(i))) { 471 return false; 472 } 473 } 474 return true; 475 } 476 477 /** 478 * Returns a TransitionSet containing the shared element transition. The wrapping TransitionSet 479 * targets all shared elements to ensure that no other Views are targeted. The shared element 480 * transition can then target any or all shared elements without worrying about accidentally 481 * targeting entering or exiting Views. 482 * 483 * @param inFragment The incoming fragment 484 * @param outFragment the outgoing fragment 485 * @param isPop True if this is a pop transaction or false if it is a normal (add) transaction. 486 * @return A TransitionSet wrapping the shared element transition or null if no such transition 487 * exists. 488 */ 489 private static Object getSharedElementTransition(FragmentTransitionImpl impl, 490 Fragment inFragment, Fragment outFragment, boolean isPop) { 491 if (inFragment == null || outFragment == null) { 492 return null; 493 } 494 Object transition = impl.cloneTransition(isPop 495 ? outFragment.getSharedElementReturnTransition() 496 : inFragment.getSharedElementEnterTransition()); 497 return impl.wrapTransitionInSet(transition); 498 } 499 500 /** 501 * Returns a clone of the enter transition or null if no such transition exists. 502 */ 503 private static Object getEnterTransition(FragmentTransitionImpl impl, 504 Fragment inFragment, boolean isPop) { 505 if (inFragment == null) { 506 return null; 507 } 508 return impl.cloneTransition(isPop 509 ? inFragment.getReenterTransition() 510 : inFragment.getEnterTransition()); 511 } 512 513 /** 514 * Returns a clone of the exit transition or null if no such transition exists. 515 */ 516 private static Object getExitTransition(FragmentTransitionImpl impl, 517 Fragment outFragment, boolean isPop) { 518 if (outFragment == null) { 519 return null; 520 } 521 return impl.cloneTransition(isPop 522 ? outFragment.getReturnTransition() 523 : outFragment.getExitTransition()); 524 } 525 526 /** 527 * Configures the shared elements of a reordered fragment transaction's transition. 528 * This retrieves the shared elements of the outgoing and incoming fragments, maps the 529 * views, and sets up the epicenter on the transitions. 530 * <p> 531 * The epicenter of exit and shared element transitions is the first shared element 532 * in the outgoing fragment. The epicenter of the entering transition is the first shared 533 * element in the incoming fragment. 534 * 535 * @param sceneRoot The fragment container View 536 * @param nonExistentView A View that does not exist in the hierarchy. This is used to 537 * prevent transitions from acting on other Views when there is no 538 * other target. 539 * @param nameOverrides A map of the shared element names from the starting fragment to 540 * the final fragment's Views as given in 541 * {@link FragmentTransaction#addSharedElement(View, String)}. 542 * @param fragments A structure holding the transitioning fragments in this container. 543 * @param sharedElementsOut A list modified to contain the shared elements in the outgoing 544 * fragment 545 * @param sharedElementsIn A list modified to contain the shared elements in the incoming 546 * fragment 547 * @param enterTransition The transition used for entering Views, modified by applying the 548 * epicenter 549 * @param exitTransition The transition used for exiting Views, modified by applying the 550 * epicenter 551 * @return The shared element transition or null if no shared elements exist 552 */ 553 private static Object configureSharedElementsReordered(final FragmentTransitionImpl impl, 554 final ViewGroup sceneRoot, 555 final View nonExistentView, final ArrayMap<String, String> nameOverrides, 556 final FragmentContainerTransition fragments, 557 final ArrayList<View> sharedElementsOut, 558 final ArrayList<View> sharedElementsIn, 559 final Object enterTransition, final Object exitTransition) { 560 final Fragment inFragment = fragments.lastIn; 561 final Fragment outFragment = fragments.firstOut; 562 if (inFragment != null) { 563 inFragment.getView().setVisibility(View.VISIBLE); 564 } 565 if (inFragment == null || outFragment == null) { 566 return null; // no shared element without a fragment 567 } 568 569 final boolean inIsPop = fragments.lastInIsPop; 570 Object sharedElementTransition = nameOverrides.isEmpty() ? null 571 : getSharedElementTransition(impl, inFragment, outFragment, inIsPop); 572 573 final ArrayMap<String, View> outSharedElements = captureOutSharedElements(impl, 574 nameOverrides, sharedElementTransition, fragments); 575 576 final ArrayMap<String, View> inSharedElements = captureInSharedElements(impl, 577 nameOverrides, sharedElementTransition, fragments); 578 579 if (nameOverrides.isEmpty()) { 580 sharedElementTransition = null; 581 if (outSharedElements != null) { 582 outSharedElements.clear(); 583 } 584 if (inSharedElements != null) { 585 inSharedElements.clear(); 586 } 587 } else { 588 addSharedElementsWithMatchingNames(sharedElementsOut, outSharedElements, 589 nameOverrides.keySet()); 590 addSharedElementsWithMatchingNames(sharedElementsIn, inSharedElements, 591 nameOverrides.values()); 592 } 593 594 if (enterTransition == null && exitTransition == null && sharedElementTransition == null) { 595 // don't call onSharedElementStart/End since there is no transition 596 return null; 597 } 598 599 callSharedElementStartEnd(inFragment, outFragment, inIsPop, outSharedElements, true); 600 601 final Rect epicenter; 602 final View epicenterView; 603 if (sharedElementTransition != null) { 604 sharedElementsIn.add(nonExistentView); 605 impl.setSharedElementTargets(sharedElementTransition, 606 nonExistentView, sharedElementsOut); 607 final boolean outIsPop = fragments.firstOutIsPop; 608 final BackStackRecord outTransaction = fragments.firstOutTransaction; 609 setOutEpicenter(impl, sharedElementTransition, exitTransition, outSharedElements, 610 outIsPop, outTransaction); 611 epicenter = new Rect(); 612 epicenterView = getInEpicenterView(inSharedElements, fragments, 613 enterTransition, inIsPop); 614 if (epicenterView != null) { 615 impl.setEpicenter(enterTransition, epicenter); 616 } 617 } else { 618 epicenter = null; 619 epicenterView = null; 620 } 621 622 OneShotPreDrawListener.add(sceneRoot, new Runnable() { 623 @Override 624 public void run() { 625 callSharedElementStartEnd(inFragment, outFragment, inIsPop, 626 inSharedElements, false); 627 if (epicenterView != null) { 628 impl.getBoundsOnScreen(epicenterView, epicenter); 629 } 630 } 631 }); 632 return sharedElementTransition; 633 } 634 635 /** 636 * Add Views from sharedElements into views that have the transitionName in the 637 * nameOverridesSet. 638 * 639 * @param views Views list to add shared elements to 640 * @param sharedElements List of shared elements 641 * @param nameOverridesSet The transition names for all views to be copied from 642 * sharedElements to views. 643 */ 644 private static void addSharedElementsWithMatchingNames(ArrayList<View> views, 645 ArrayMap<String, View> sharedElements, Collection<String> nameOverridesSet) { 646 for (int i = sharedElements.size() - 1; i >= 0; i--) { 647 View view = sharedElements.valueAt(i); 648 if (nameOverridesSet.contains(ViewCompat.getTransitionName(view))) { 649 views.add(view); 650 } 651 } 652 } 653 654 /** 655 * Configures the shared elements of an ordered fragment transaction's transition. 656 * This retrieves the shared elements of the incoming fragments, and schedules capturing 657 * the incoming fragment's shared elements. It also maps the views, and sets up the epicenter 658 * on the transitions. 659 * <p> 660 * The epicenter of exit and shared element transitions is the first shared element 661 * in the outgoing fragment. The epicenter of the entering transition is the first shared 662 * element in the incoming fragment. 663 * 664 * @param sceneRoot The fragment container View 665 * @param nonExistentView A View that does not exist in the hierarchy. This is used to 666 * prevent transitions from acting on other Views when there is no 667 * other target. 668 * @param nameOverrides A map of the shared element names from the starting fragment to 669 * the final fragment's Views as given in 670 * {@link FragmentTransaction#addSharedElement(View, String)}. 671 * @param fragments A structure holding the transitioning fragments in this container. 672 * @param sharedElementsOut A list modified to contain the shared elements in the outgoing 673 * fragment 674 * @param sharedElementsIn A list modified to contain the shared elements in the incoming 675 * fragment 676 * @param enterTransition The transition used for entering Views, modified by applying the 677 * epicenter 678 * @param exitTransition The transition used for exiting Views, modified by applying the 679 * epicenter 680 * @return The shared element transition or null if no shared elements exist 681 */ 682 private static Object configureSharedElementsOrdered(final FragmentTransitionImpl impl, 683 final ViewGroup sceneRoot, 684 final View nonExistentView, final ArrayMap<String, String> nameOverrides, 685 final FragmentContainerTransition fragments, 686 final ArrayList<View> sharedElementsOut, 687 final ArrayList<View> sharedElementsIn, 688 final Object enterTransition, final Object exitTransition) { 689 final Fragment inFragment = fragments.lastIn; 690 final Fragment outFragment = fragments.firstOut; 691 692 if (inFragment == null || outFragment == null) { 693 return null; // no transition 694 } 695 696 final boolean inIsPop = fragments.lastInIsPop; 697 Object sharedElementTransition = nameOverrides.isEmpty() ? null 698 : getSharedElementTransition(impl, inFragment, outFragment, inIsPop); 699 700 ArrayMap<String, View> outSharedElements = captureOutSharedElements(impl, nameOverrides, 701 sharedElementTransition, fragments); 702 703 if (nameOverrides.isEmpty()) { 704 sharedElementTransition = null; 705 } else { 706 sharedElementsOut.addAll(outSharedElements.values()); 707 } 708 709 if (enterTransition == null && exitTransition == null && sharedElementTransition == null) { 710 // don't call onSharedElementStart/End since there is no transition 711 return null; 712 } 713 714 callSharedElementStartEnd(inFragment, outFragment, inIsPop, outSharedElements, true); 715 716 final Rect inEpicenter; 717 if (sharedElementTransition != null) { 718 inEpicenter = new Rect(); 719 impl.setSharedElementTargets(sharedElementTransition, 720 nonExistentView, sharedElementsOut); 721 final boolean outIsPop = fragments.firstOutIsPop; 722 final BackStackRecord outTransaction = fragments.firstOutTransaction; 723 setOutEpicenter(impl, sharedElementTransition, exitTransition, outSharedElements, 724 outIsPop, outTransaction); 725 if (enterTransition != null) { 726 impl.setEpicenter(enterTransition, inEpicenter); 727 } 728 } else { 729 inEpicenter = null; 730 } 731 732 733 final Object finalSharedElementTransition = sharedElementTransition; 734 OneShotPreDrawListener.add(sceneRoot, new Runnable() { 735 @Override 736 public void run() { 737 ArrayMap<String, View> inSharedElements = captureInSharedElements(impl, 738 nameOverrides, finalSharedElementTransition, fragments); 739 740 if (inSharedElements != null) { 741 sharedElementsIn.addAll(inSharedElements.values()); 742 sharedElementsIn.add(nonExistentView); 743 } 744 745 callSharedElementStartEnd(inFragment, outFragment, inIsPop, 746 inSharedElements, false); 747 if (finalSharedElementTransition != null) { 748 impl.swapSharedElementTargets( 749 finalSharedElementTransition, sharedElementsOut, 750 sharedElementsIn); 751 752 final View inEpicenterView = getInEpicenterView(inSharedElements, 753 fragments, enterTransition, inIsPop); 754 if (inEpicenterView != null) { 755 impl.getBoundsOnScreen(inEpicenterView, 756 inEpicenter); 757 } 758 } 759 } 760 }); 761 762 return sharedElementTransition; 763 } 764 765 /** 766 * Finds the shared elements in the outgoing fragment. It also calls 767 * {@link SharedElementCallback#onMapSharedElements(List, Map)} to allow more control 768 * of the shared element mapping. {@code nameOverrides} is updated to match the 769 * actual transition name of the mapped shared elements. 770 * 771 * @param nameOverrides A map of the shared element names from the starting fragment to 772 * the final fragment's Views as given in 773 * {@link FragmentTransaction#addSharedElement(View, String)}. 774 * @param sharedElementTransition The shared element transition 775 * @param fragments A structure holding the transitioning fragments in this container. 776 * @return The mapping of shared element names to the Views in the hierarchy or null 777 * if there is no shared element transition. 778 */ 779 private static ArrayMap<String, View> captureOutSharedElements(FragmentTransitionImpl impl, 780 ArrayMap<String, String> nameOverrides, Object sharedElementTransition, 781 FragmentContainerTransition fragments) { 782 if (nameOverrides.isEmpty() || sharedElementTransition == null) { 783 nameOverrides.clear(); 784 return null; 785 } 786 final Fragment outFragment = fragments.firstOut; 787 final ArrayMap<String, View> outSharedElements = new ArrayMap<>(); 788 impl.findNamedViews(outSharedElements, outFragment.getView()); 789 790 final SharedElementCallback sharedElementCallback; 791 final ArrayList<String> names; 792 final BackStackRecord outTransaction = fragments.firstOutTransaction; 793 if (fragments.firstOutIsPop) { 794 sharedElementCallback = outFragment.getEnterTransitionCallback(); 795 names = outTransaction.mSharedElementTargetNames; 796 } else { 797 sharedElementCallback = outFragment.getExitTransitionCallback(); 798 names = outTransaction.mSharedElementSourceNames; 799 } 800 801 outSharedElements.retainAll(names); 802 if (sharedElementCallback != null) { 803 sharedElementCallback.onMapSharedElements(names, outSharedElements); 804 for (int i = names.size() - 1; i >= 0; i--) { 805 String name = names.get(i); 806 View view = outSharedElements.get(name); 807 if (view == null) { 808 nameOverrides.remove(name); 809 } else if (!name.equals(ViewCompat.getTransitionName(view))) { 810 String targetValue = nameOverrides.remove(name); 811 nameOverrides.put(ViewCompat.getTransitionName(view), targetValue); 812 } 813 } 814 } else { 815 nameOverrides.retainAll(outSharedElements.keySet()); 816 } 817 return outSharedElements; 818 } 819 820 /** 821 * Finds the shared elements in the incoming fragment. It also calls 822 * {@link SharedElementCallback#onMapSharedElements(List, Map)} to allow more control 823 * of the shared element mapping. {@code nameOverrides} is updated to match the 824 * actual transition name of the mapped shared elements. 825 * 826 * @param nameOverrides A map of the shared element names from the starting fragment to 827 * the final fragment's Views as given in 828 * {@link FragmentTransaction#addSharedElement(View, String)}. 829 * @param sharedElementTransition The shared element transition 830 * @param fragments A structure holding the transitioning fragments in this container. 831 * @return The mapping of shared element names to the Views in the hierarchy or null 832 * if there is no shared element transition. 833 */ 834 private static ArrayMap<String, View> captureInSharedElements(FragmentTransitionImpl impl, 835 ArrayMap<String, String> nameOverrides, Object sharedElementTransition, 836 FragmentContainerTransition fragments) { 837 Fragment inFragment = fragments.lastIn; 838 final View fragmentView = inFragment.getView(); 839 if (nameOverrides.isEmpty() || sharedElementTransition == null || fragmentView == null) { 840 nameOverrides.clear(); 841 return null; 842 } 843 final ArrayMap<String, View> inSharedElements = new ArrayMap<>(); 844 impl.findNamedViews(inSharedElements, fragmentView); 845 846 final SharedElementCallback sharedElementCallback; 847 final ArrayList<String> names; 848 final BackStackRecord inTransaction = fragments.lastInTransaction; 849 if (fragments.lastInIsPop) { 850 sharedElementCallback = inFragment.getExitTransitionCallback(); 851 names = inTransaction.mSharedElementSourceNames; 852 } else { 853 sharedElementCallback = inFragment.getEnterTransitionCallback(); 854 names = inTransaction.mSharedElementTargetNames; 855 } 856 857 if (names != null) { 858 inSharedElements.retainAll(names); 859 inSharedElements.retainAll(nameOverrides.values()); 860 } 861 if (sharedElementCallback != null) { 862 sharedElementCallback.onMapSharedElements(names, inSharedElements); 863 for (int i = names.size() - 1; i >= 0; i--) { 864 String name = names.get(i); 865 View view = inSharedElements.get(name); 866 if (view == null) { 867 String key = findKeyForValue(nameOverrides, name); 868 if (key != null) { 869 nameOverrides.remove(key); 870 } 871 } else if (!name.equals(ViewCompat.getTransitionName(view))) { 872 String key = findKeyForValue(nameOverrides, name); 873 if (key != null) { 874 nameOverrides.put(key, ViewCompat.getTransitionName(view)); 875 } 876 } 877 } 878 } else { 879 retainValues(nameOverrides, inSharedElements); 880 } 881 return inSharedElements; 882 } 883 884 /** 885 * Utility to find the String key in {@code map} that maps to {@code value}. 886 */ 887 private static String findKeyForValue(ArrayMap<String, String> map, String value) { 888 final int numElements = map.size(); 889 for (int i = 0; i < numElements; i++) { 890 if (value.equals(map.valueAt(i))) { 891 return map.keyAt(i); 892 } 893 } 894 return null; 895 } 896 897 /** 898 * Returns the View in the incoming Fragment that should be used as the epicenter. 899 * 900 * @param inSharedElements The mapping of shared element names to Views in the 901 * incoming fragment. 902 * @param fragments A structure holding the transitioning fragments in this container. 903 * @param enterTransition The transition used for the incoming Fragment's views 904 * @param inIsPop Is the incoming fragment being added as a pop transaction? 905 */ 906 private static View getInEpicenterView(ArrayMap<String, View> inSharedElements, 907 FragmentContainerTransition fragments, 908 Object enterTransition, boolean inIsPop) { 909 BackStackRecord inTransaction = fragments.lastInTransaction; 910 if (enterTransition != null && inSharedElements != null 911 && inTransaction.mSharedElementSourceNames != null 912 && !inTransaction.mSharedElementSourceNames.isEmpty()) { 913 final String targetName = inIsPop 914 ? inTransaction.mSharedElementSourceNames.get(0) 915 : inTransaction.mSharedElementTargetNames.get(0); 916 return inSharedElements.get(targetName); 917 } 918 return null; 919 } 920 921 /** 922 * Sets the epicenter for the exit transition. 923 * 924 * @param sharedElementTransition The shared element transition 925 * @param exitTransition The transition for the outgoing fragment's views 926 * @param outSharedElements Shared elements in the outgoing fragment 927 * @param outIsPop Is the outgoing fragment being removed as a pop transaction? 928 * @param outTransaction The transaction that caused the fragment to be removed. 929 */ 930 private static void setOutEpicenter(FragmentTransitionImpl impl, Object sharedElementTransition, 931 Object exitTransition, ArrayMap<String, View> outSharedElements, boolean outIsPop, 932 BackStackRecord outTransaction) { 933 if (outTransaction.mSharedElementSourceNames != null 934 && !outTransaction.mSharedElementSourceNames.isEmpty()) { 935 final String sourceName = outIsPop 936 ? outTransaction.mSharedElementTargetNames.get(0) 937 : outTransaction.mSharedElementSourceNames.get(0); 938 final View outEpicenterView = outSharedElements.get(sourceName); 939 impl.setEpicenter(sharedElementTransition, outEpicenterView); 940 941 if (exitTransition != null) { 942 impl.setEpicenter(exitTransition, outEpicenterView); 943 } 944 } 945 } 946 947 /** 948 * A utility to retain only the mappings in {@code nameOverrides} that have a value 949 * that has a key in {@code namedViews}. This is a useful equivalent to 950 * {@link ArrayMap#retainAll(Collection)} for values. 951 */ 952 private static void retainValues(ArrayMap<String, String> nameOverrides, 953 ArrayMap<String, View> namedViews) { 954 for (int i = nameOverrides.size() - 1; i >= 0; i--) { 955 final String targetName = nameOverrides.valueAt(i); 956 if (!namedViews.containsKey(targetName)) { 957 nameOverrides.removeAt(i); 958 } 959 } 960 } 961 962 /** 963 * Calls the {@link SharedElementCallback#onSharedElementStart(List, List, List)} or 964 * {@link SharedElementCallback#onSharedElementEnd(List, List, List)} on the appropriate 965 * incoming or outgoing fragment. 966 * 967 * @param inFragment The incoming fragment 968 * @param outFragment The outgoing fragment 969 * @param isPop Is the incoming fragment part of a pop transaction? 970 * @param sharedElements The shared element Views 971 * @param isStart Call the start or end call on the SharedElementCallback 972 */ 973 private static void callSharedElementStartEnd(Fragment inFragment, Fragment outFragment, 974 boolean isPop, ArrayMap<String, View> sharedElements, boolean isStart) { 975 SharedElementCallback sharedElementCallback = isPop 976 ? outFragment.getEnterTransitionCallback() 977 : inFragment.getEnterTransitionCallback(); 978 if (sharedElementCallback != null) { 979 ArrayList<View> views = new ArrayList<>(); 980 ArrayList<String> names = new ArrayList<>(); 981 final int count = sharedElements == null ? 0 : sharedElements.size(); 982 for (int i = 0; i < count; i++) { 983 names.add(sharedElements.keyAt(i)); 984 views.add(sharedElements.valueAt(i)); 985 } 986 if (isStart) { 987 sharedElementCallback.onSharedElementStart(names, views, null); 988 } else { 989 sharedElementCallback.onSharedElementEnd(names, views, null); 990 } 991 } 992 } 993 994 private static ArrayList<View> configureEnteringExitingViews(FragmentTransitionImpl impl, 995 Object transition, 996 Fragment fragment, ArrayList<View> sharedElements, View nonExistentView) { 997 ArrayList<View> viewList = null; 998 if (transition != null) { 999 viewList = new ArrayList<>(); 1000 View root = fragment.getView(); 1001 if (root != null) { 1002 impl.captureTransitioningViews(viewList, root); 1003 } 1004 if (sharedElements != null) { 1005 viewList.removeAll(sharedElements); 1006 } 1007 if (!viewList.isEmpty()) { 1008 viewList.add(nonExistentView); 1009 impl.addTargets(transition, viewList); 1010 } 1011 } 1012 return viewList; 1013 } 1014 1015 /** 1016 * Sets the visibility of all Views in {@code views} to {@code visibility}. 1017 */ 1018 private static void setViewVisibility(ArrayList<View> views, int visibility) { 1019 if (views == null) { 1020 return; 1021 } 1022 for (int i = views.size() - 1; i >= 0; i--) { 1023 final View view = views.get(i); 1024 view.setVisibility(visibility); 1025 } 1026 } 1027 1028 /** 1029 * Merges exit, shared element, and enter transitions so that they act together or 1030 * sequentially as defined in the fragments. 1031 */ 1032 private static Object mergeTransitions(FragmentTransitionImpl impl, Object enterTransition, 1033 Object exitTransition, Object sharedElementTransition, Fragment inFragment, 1034 boolean isPop) { 1035 boolean overlap = true; 1036 if (enterTransition != null && exitTransition != null && inFragment != null) { 1037 overlap = isPop ? inFragment.getAllowReturnTransitionOverlap() : 1038 inFragment.getAllowEnterTransitionOverlap(); 1039 } 1040 1041 // Wrap the transitions. Explicit targets like in enter and exit will cause the 1042 // views to be targeted regardless of excluded views. If that happens, then the 1043 // excluded fragments views (hidden fragments) will still be in the transition. 1044 1045 Object transition; 1046 if (overlap) { 1047 // Regular transition -- do it all together 1048 transition = impl.mergeTransitionsTogether(exitTransition, 1049 enterTransition, sharedElementTransition); 1050 } else { 1051 // First do exit, then enter, but allow shared element transition to happen 1052 // during both. 1053 transition = impl.mergeTransitionsInSequence(exitTransition, 1054 enterTransition, sharedElementTransition); 1055 } 1056 return transition; 1057 } 1058 1059 /** 1060 * Finds the first removed fragment and last added fragments when going forward. 1061 * If none of the fragments have transitions, then both lists will be empty. 1062 * 1063 * @param transitioningFragments Keyed on the container ID, the first fragments to be removed, 1064 * and last fragments to be added. This will be modified by 1065 * this method. 1066 */ 1067 public static void calculateFragments(BackStackRecord transaction, 1068 SparseArray<FragmentContainerTransition> transitioningFragments, 1069 boolean isReordered) { 1070 final int numOps = transaction.mOps.size(); 1071 for (int opNum = 0; opNum < numOps; opNum++) { 1072 final BackStackRecord.Op op = transaction.mOps.get(opNum); 1073 addToFirstInLastOut(transaction, op, transitioningFragments, false, isReordered); 1074 } 1075 } 1076 1077 /** 1078 * Finds the first removed fragment and last added fragments when popping the back stack. 1079 * If none of the fragments have transitions, then both lists will be empty. 1080 * 1081 * @param transitioningFragments Keyed on the container ID, the first fragments to be removed, 1082 * and last fragments to be added. This will be modified by 1083 * this method. 1084 */ 1085 public static void calculatePopFragments(BackStackRecord transaction, 1086 SparseArray<FragmentContainerTransition> transitioningFragments, boolean isReordered) { 1087 if (!transaction.mManager.mContainer.onHasView()) { 1088 return; // nothing to see, so no transitions 1089 } 1090 final int numOps = transaction.mOps.size(); 1091 for (int opNum = numOps - 1; opNum >= 0; opNum--) { 1092 final BackStackRecord.Op op = transaction.mOps.get(opNum); 1093 addToFirstInLastOut(transaction, op, transitioningFragments, true, isReordered); 1094 } 1095 } 1096 1097 static boolean supportsTransition() { 1098 return PLATFORM_IMPL != null || SUPPORT_IMPL != null; 1099 } 1100 1101 /** 1102 * Examines the {@code command} and may set the first out or last in fragment for the fragment's 1103 * container. 1104 * 1105 * @param transaction The executing transaction 1106 * @param op The operation being run. 1107 * @param transitioningFragments A structure holding the first in and last out fragments 1108 * for each fragment container. 1109 * @param isPop Is the operation a pop? 1110 * @param isReorderedTransaction True if the operations have been partially executed and the 1111 * added fragments have Views in the hierarchy or false if the 1112 * operations haven't been executed yet. 1113 */ 1114 @SuppressWarnings("ReferenceEquality") 1115 private static void addToFirstInLastOut(BackStackRecord transaction, BackStackRecord.Op op, 1116 SparseArray<FragmentContainerTransition> transitioningFragments, boolean isPop, 1117 boolean isReorderedTransaction) { 1118 final Fragment fragment = op.fragment; 1119 if (fragment == null) { 1120 return; // no fragment, no transition 1121 } 1122 final int containerId = fragment.mContainerId; 1123 if (containerId == 0) { 1124 return; // no container, no transition 1125 } 1126 final int command = isPop ? INVERSE_OPS[op.cmd] : op.cmd; 1127 boolean setLastIn = false; 1128 boolean wasRemoved = false; 1129 boolean setFirstOut = false; 1130 boolean wasAdded = false; 1131 switch (command) { 1132 case BackStackRecord.OP_SHOW: 1133 if (isReorderedTransaction) { 1134 setLastIn = fragment.mHiddenChanged && !fragment.mHidden && fragment.mAdded; 1135 } else { 1136 setLastIn = fragment.mHidden; 1137 } 1138 wasAdded = true; 1139 break; 1140 case BackStackRecord.OP_ADD: 1141 case BackStackRecord.OP_ATTACH: 1142 if (isReorderedTransaction) { 1143 setLastIn = fragment.mIsNewlyAdded; 1144 } else { 1145 setLastIn = !fragment.mAdded && !fragment.mHidden; 1146 } 1147 wasAdded = true; 1148 break; 1149 case BackStackRecord.OP_HIDE: 1150 if (isReorderedTransaction) { 1151 setFirstOut = fragment.mHiddenChanged && fragment.mAdded && fragment.mHidden; 1152 } else { 1153 setFirstOut = fragment.mAdded && !fragment.mHidden; 1154 } 1155 wasRemoved = true; 1156 break; 1157 case BackStackRecord.OP_REMOVE: 1158 case BackStackRecord.OP_DETACH: 1159 if (isReorderedTransaction) { 1160 setFirstOut = !fragment.mAdded && fragment.mView != null 1161 && fragment.mView.getVisibility() == View.VISIBLE 1162 && fragment.mPostponedAlpha >= 0; 1163 } else { 1164 setFirstOut = fragment.mAdded && !fragment.mHidden; 1165 } 1166 wasRemoved = true; 1167 break; 1168 } 1169 FragmentContainerTransition containerTransition = transitioningFragments.get(containerId); 1170 if (setLastIn) { 1171 containerTransition = 1172 ensureContainer(containerTransition, transitioningFragments, containerId); 1173 containerTransition.lastIn = fragment; 1174 containerTransition.lastInIsPop = isPop; 1175 containerTransition.lastInTransaction = transaction; 1176 } 1177 if (!isReorderedTransaction && wasAdded) { 1178 if (containerTransition != null && containerTransition.firstOut == fragment) { 1179 containerTransition.firstOut = null; 1180 } 1181 1182 /* 1183 * Ensure that fragments that are entering are at least at the CREATED state 1184 * so that they may load Transitions using TransitionInflater. 1185 */ 1186 FragmentManagerImpl manager = transaction.mManager; 1187 if (fragment.mState < Fragment.CREATED && manager.mCurState >= Fragment.CREATED 1188 && !transaction.mReorderingAllowed) { 1189 manager.makeActive(fragment); 1190 manager.moveToState(fragment, Fragment.CREATED, 0, 0, false); 1191 } 1192 } 1193 if (setFirstOut && (containerTransition == null || containerTransition.firstOut == null)) { 1194 containerTransition = 1195 ensureContainer(containerTransition, transitioningFragments, containerId); 1196 containerTransition.firstOut = fragment; 1197 containerTransition.firstOutIsPop = isPop; 1198 containerTransition.firstOutTransaction = transaction; 1199 } 1200 1201 if (!isReorderedTransaction && wasRemoved 1202 && (containerTransition != null && containerTransition.lastIn == fragment)) { 1203 containerTransition.lastIn = null; 1204 } 1205 } 1206 1207 /** 1208 * Ensures that a FragmentContainerTransition has been added to the SparseArray. If so, 1209 * it returns the existing one. If not, one is created and added to the SparseArray and 1210 * returned. 1211 */ 1212 private static FragmentContainerTransition ensureContainer( 1213 FragmentContainerTransition containerTransition, 1214 SparseArray<FragmentContainerTransition> transitioningFragments, int containerId) { 1215 if (containerTransition == null) { 1216 containerTransition = new FragmentContainerTransition(); 1217 transitioningFragments.put(containerId, containerTransition); 1218 } 1219 return containerTransition; 1220 } 1221 1222 /** 1223 * Tracks the last fragment added and first fragment removed for fragment transitions. 1224 * This also tracks which fragments are changed by push or pop transactions. 1225 */ 1226 static class FragmentContainerTransition { 1227 /** 1228 * The last fragment added/attached/shown in its container 1229 */ 1230 public Fragment lastIn; 1231 1232 /** 1233 * true when lastIn was added during a pop transaction or false if added with a push 1234 */ 1235 public boolean lastInIsPop; 1236 1237 /** 1238 * The transaction that included the last in fragment 1239 */ 1240 public BackStackRecord lastInTransaction; 1241 1242 /** 1243 * The first fragment with a View that was removed/detached/hidden in its container. 1244 */ 1245 public Fragment firstOut; 1246 1247 /** 1248 * true when firstOut was removed during a pop transaction or false otherwise 1249 */ 1250 public boolean firstOutIsPop; 1251 1252 /** 1253 * The transaction that included the first out fragment 1254 */ 1255 public BackStackRecord firstOutTransaction; 1256 } 1257 1258 private FragmentTransition() { 1259 } 1260 } 1261