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