Home | History | Annotate | Download | only in app
      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 
     17 package androidx.fragment.app;
     18 
     19 import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
     20 
     21 import android.graphics.Rect;
     22 import android.view.View;
     23 import android.view.ViewGroup;
     24 
     25 import androidx.annotation.RestrictTo;
     26 import androidx.core.view.ViewCompat;
     27 import androidx.core.view.ViewGroupCompat;
     28 
     29 import java.util.ArrayList;
     30 import java.util.List;
     31 import java.util.Map;
     32 
     33 
     34 /**
     35  * @hide
     36  */
     37 @RestrictTo(LIBRARY_GROUP)
     38 public abstract class FragmentTransitionImpl {
     39 
     40     /**
     41      * Returns {@code true} if this implementation can handle the specified {@link transition}.
     42      */
     43     public abstract boolean canHandle(Object transition);
     44 
     45     /**
     46      * Returns a clone of a transition or null if it is null
     47      */
     48     public abstract Object cloneTransition(Object transition);
     49 
     50     /**
     51      * Wraps a transition in a TransitionSet and returns the set. If transition is null, null is
     52      * returned.
     53      */
     54     public abstract Object wrapTransitionInSet(Object transition);
     55 
     56     /**
     57      * Finds all children of the shared elements and sets the wrapping TransitionSet
     58      * targets to point to those. It also limits transitions that have no targets to the
     59      * specific shared elements. This allows developers to target child views of the
     60      * shared elements specifically, but this doesn't happen by default.
     61      */
     62     public abstract void setSharedElementTargets(Object transitionObj,
     63             View nonExistentView, ArrayList<View> sharedViews);
     64 
     65     /**
     66      * Sets a transition epicenter to the rectangle of a given View.
     67      */
     68     public abstract void setEpicenter(Object transitionObj, View view);
     69 
     70     /**
     71      * Replacement for view.getBoundsOnScreen because that is not public. This returns a rect
     72      * containing the bounds relative to the screen that the view is in.
     73      */
     74     protected void getBoundsOnScreen(View view, Rect epicenter) {
     75         int[] loc = new int[2];
     76         view.getLocationOnScreen(loc);
     77         epicenter.set(loc[0], loc[1], loc[0] + view.getWidth(), loc[1] + view.getHeight());
     78     }
     79 
     80     /**
     81      * This method adds views as targets to the transition, but only if the transition
     82      * doesn't already have a target. It is best for views to contain one View object
     83      * that does not exist in the view hierarchy (state.nonExistentView) so that
     84      * when they are removed later, a list match will suffice to remove the targets.
     85      * Otherwise, if you happened to have targeted the exact views for the transition,
     86      * the replaceTargets call will remove them unexpectedly.
     87      */
     88     public abstract void addTargets(Object transitionObj, ArrayList<View> views);
     89 
     90     /**
     91      * Creates a TransitionSet that plays all passed transitions together. Any null
     92      * transitions passed will not be added to the set. If all are null, then an empty
     93      * TransitionSet will be returned.
     94      */
     95     public abstract Object mergeTransitionsTogether(Object transition1, Object transition2,
     96             Object transition3);
     97 
     98     /**
     99      * After the transition completes, the fragment's view is set to GONE and the exiting
    100      * views are set to VISIBLE.
    101      */
    102     public abstract void scheduleHideFragmentView(Object exitTransitionObj, View fragmentView,
    103             ArrayList<View> exitingViews);
    104 
    105     /**
    106      * Combines enter, exit, and shared element transition so that they play in the proper
    107      * sequence. First the exit transition plays along with the shared element transition.
    108      * When the exit transition completes, the enter transition starts. The shared element
    109      * transition can continue running while the enter transition plays.
    110      *
    111      * @return A TransitionSet with all of enter, exit, and shared element transitions in
    112      * it (modulo null values), ordered such that they play in the proper sequence.
    113      */
    114     public abstract Object mergeTransitionsInSequence(Object exitTransitionObj,
    115             Object enterTransitionObj, Object sharedElementTransitionObj);
    116 
    117     /**
    118      * Calls {@code TransitionManager#beginDelayedTransition(ViewGroup, Transition)}.
    119      */
    120     public abstract void beginDelayedTransition(ViewGroup sceneRoot, Object transition);
    121 
    122     /**
    123      * Prepares for setting the shared element names by gathering the names of the incoming
    124      * shared elements and clearing them. {@link #setNameOverridesReordered(View, ArrayList,
    125      * ArrayList, ArrayList, Map)} must be called after this to complete setting the shared element
    126      * name overrides. This must be called before
    127      * {@link #beginDelayedTransition(ViewGroup, Object)}.
    128      */
    129     ArrayList<String> prepareSetNameOverridesReordered(ArrayList<View> sharedElementsIn) {
    130         final ArrayList<String> names = new ArrayList<>();
    131         final int numSharedElements = sharedElementsIn.size();
    132         for (int i = 0; i < numSharedElements; i++) {
    133             final View view = sharedElementsIn.get(i);
    134             names.add(ViewCompat.getTransitionName(view));
    135             ViewCompat.setTransitionName(view, null);
    136         }
    137         return names;
    138     }
    139 
    140     /**
    141      * Changes the shared element names for the incoming shared elements to match those of the
    142      * outgoing shared elements. This also temporarily clears the shared element names of the
    143      * outgoing shared elements. Must be called after
    144      * {@link #beginDelayedTransition(ViewGroup, Object)}.
    145      */
    146     void setNameOverridesReordered(final View sceneRoot,
    147             final ArrayList<View> sharedElementsOut, final ArrayList<View> sharedElementsIn,
    148             final ArrayList<String> inNames, final Map<String, String> nameOverrides) {
    149         final int numSharedElements = sharedElementsIn.size();
    150         final ArrayList<String> outNames = new ArrayList<>();
    151 
    152         for (int i = 0; i < numSharedElements; i++) {
    153             final View view = sharedElementsOut.get(i);
    154             final String name = ViewCompat.getTransitionName(view);
    155             outNames.add(name);
    156             if (name == null) {
    157                 continue;
    158             }
    159             ViewCompat.setTransitionName(view, null);
    160             final String inName = nameOverrides.get(name);
    161             for (int j = 0; j < numSharedElements; j++) {
    162                 if (inName.equals(inNames.get(j))) {
    163                     ViewCompat.setTransitionName(sharedElementsIn.get(j), name);
    164                     break;
    165                 }
    166             }
    167         }
    168 
    169         OneShotPreDrawListener.add(sceneRoot, new Runnable() {
    170             @Override
    171             public void run() {
    172                 for (int i = 0; i < numSharedElements; i++) {
    173                     ViewCompat.setTransitionName(sharedElementsIn.get(i), inNames.get(i));
    174                     ViewCompat.setTransitionName(sharedElementsOut.get(i), outNames.get(i));
    175                 }
    176             }
    177         });
    178     }
    179 
    180     /**
    181      * Gets the Views in the hierarchy affected by entering and exiting Activity Scene transitions.
    182      *
    183      * @param transitioningViews This View will be added to transitioningViews if it is VISIBLE and
    184      *                           a normal View or a ViewGroup with
    185      *                           {@link android.view.ViewGroup#isTransitionGroup()} true.
    186      * @param view               The base of the view hierarchy to look in.
    187      */
    188     void captureTransitioningViews(ArrayList<View> transitioningViews, View view) {
    189         if (view.getVisibility() == View.VISIBLE) {
    190             if (view instanceof ViewGroup) {
    191                 ViewGroup viewGroup = (ViewGroup) view;
    192                 if (ViewGroupCompat.isTransitionGroup(viewGroup)) {
    193                     transitioningViews.add(viewGroup);
    194                 } else {
    195                     int count = viewGroup.getChildCount();
    196                     for (int i = 0; i < count; i++) {
    197                         View child = viewGroup.getChildAt(i);
    198                         captureTransitioningViews(transitioningViews, child);
    199                     }
    200                 }
    201             } else {
    202                 transitioningViews.add(view);
    203             }
    204         }
    205     }
    206 
    207     /**
    208      * Finds all views that have transition names in the hierarchy under the given view and
    209      * stores them in {@code namedViews} map with the name as the key.
    210      */
    211     void findNamedViews(Map<String, View> namedViews, View view) {
    212         if (view.getVisibility() == View.VISIBLE) {
    213             String transitionName = ViewCompat.getTransitionName(view);
    214             if (transitionName != null) {
    215                 namedViews.put(transitionName, view);
    216             }
    217             if (view instanceof ViewGroup) {
    218                 ViewGroup viewGroup = (ViewGroup) view;
    219                 int count = viewGroup.getChildCount();
    220                 for (int i = 0; i < count; i++) {
    221                     View child = viewGroup.getChildAt(i);
    222                     findNamedViews(namedViews, child);
    223                 }
    224             }
    225         }
    226     }
    227 
    228     /**
    229      *Applies the prepared {@code nameOverrides} to the view hierarchy.
    230      */
    231     void setNameOverridesOrdered(final View sceneRoot,
    232             final ArrayList<View> sharedElementsIn, final Map<String, String> nameOverrides) {
    233         OneShotPreDrawListener.add(sceneRoot, new Runnable() {
    234             @Override
    235             public void run() {
    236                 final int numSharedElements = sharedElementsIn.size();
    237                 for (int i = 0; i < numSharedElements; i++) {
    238                     View view = sharedElementsIn.get(i);
    239                     String name = ViewCompat.getTransitionName(view);
    240                     if (name != null) {
    241                         String inName = findKeyForValue(nameOverrides, name);
    242                         ViewCompat.setTransitionName(view, inName);
    243                     }
    244                 }
    245             }
    246         });
    247     }
    248 
    249     /**
    250      * After the transition has started, remove all targets that we added to the transitions
    251      * so that the transitions are left in a clean state.
    252      */
    253     public abstract void scheduleRemoveTargets(Object overallTransitionObj,
    254             Object enterTransition, ArrayList<View> enteringViews,
    255             Object exitTransition, ArrayList<View> exitingViews,
    256             Object sharedElementTransition, ArrayList<View> sharedElementsIn);
    257 
    258     /**
    259      * Swap the targets for the shared element transition from those Views in sharedElementsOut
    260      * to those in sharedElementsIn
    261      */
    262     public abstract void swapSharedElementTargets(Object sharedElementTransitionObj,
    263             ArrayList<View> sharedElementsOut, ArrayList<View> sharedElementsIn);
    264 
    265     /**
    266      * This method removes the views from transitions that target ONLY those views and
    267      * replaces them with the new targets list.
    268      * The views list should match those added in addTargets and should contain
    269      * one view that is not in the view hierarchy (state.nonExistentView).
    270      */
    271     public abstract void replaceTargets(Object transitionObj, ArrayList<View> oldTargets,
    272             ArrayList<View> newTargets);
    273 
    274     /**
    275      * Adds a View target to a transition. If transitionObj is null, nothing is done.
    276      */
    277     public abstract void addTarget(Object transitionObj, View view);
    278 
    279     /**
    280      * Remove a View target to a transition. If transitionObj is null, nothing is done.
    281      */
    282     public abstract void removeTarget(Object transitionObj, View view);
    283 
    284     /**
    285      * Sets the epicenter of a transition to a rect object. The object can be modified until
    286      * the transition runs.
    287      */
    288     public abstract void setEpicenter(Object transitionObj, Rect epicenter);
    289 
    290     void scheduleNameReset(final ViewGroup sceneRoot,
    291             final ArrayList<View> sharedElementsIn, final Map<String, String> nameOverrides) {
    292         OneShotPreDrawListener.add(sceneRoot, new Runnable() {
    293             @Override
    294             public void run() {
    295                 final int numSharedElements = sharedElementsIn.size();
    296                 for (int i = 0; i < numSharedElements; i++) {
    297                     final View view = sharedElementsIn.get(i);
    298                     final String name = ViewCompat.getTransitionName(view);
    299                     final String inName = nameOverrides.get(name);
    300                     ViewCompat.setTransitionName(view, inName);
    301                 }
    302             }
    303         });
    304     }
    305 
    306     /**
    307      * Uses a breadth-first scheme to add startView and all of its children to views.
    308      * It won't add a child if it is already in views.
    309      */
    310     protected static void bfsAddViewChildren(final List<View> views, final View startView) {
    311         final int startIndex = views.size();
    312         if (containedBeforeIndex(views, startView, startIndex)) {
    313             return; // This child is already in the list, so all its children are also.
    314         }
    315         views.add(startView);
    316         for (int index = startIndex; index < views.size(); index++) {
    317             final View view = views.get(index);
    318             if (view instanceof ViewGroup) {
    319                 ViewGroup viewGroup = (ViewGroup) view;
    320                 final int childCount =  viewGroup.getChildCount();
    321                 for (int childIndex = 0; childIndex < childCount; childIndex++) {
    322                     final View child = viewGroup.getChildAt(childIndex);
    323                     if (!containedBeforeIndex(views, child, startIndex)) {
    324                         views.add(child);
    325                     }
    326                 }
    327             }
    328         }
    329     }
    330 
    331     /**
    332      * Does a linear search through views for view, limited to maxIndex.
    333      */
    334     private static boolean containedBeforeIndex(final List<View> views, final View view,
    335             final int maxIndex) {
    336         for (int i = 0; i < maxIndex; i++) {
    337             if (views.get(i) == view) {
    338                 return true;
    339             }
    340         }
    341         return false;
    342     }
    343 
    344     /**
    345      * Simple utility to detect if a list is null or has no elements.
    346      */
    347     protected static boolean isNullOrEmpty(List list) {
    348         return list == null || list.isEmpty();
    349     }
    350 
    351     /**
    352      * Utility to find the String key in {@code map} that maps to {@code value}.
    353      */
    354     @SuppressWarnings("WeakerAccess")
    355     static String findKeyForValue(Map<String, String> map, String value) {
    356         for (Map.Entry<String, String> entry : map.entrySet()) {
    357             if (value.equals(entry.getValue())) {
    358                 return entry.getKey();
    359             }
    360         }
    361         return null;
    362     }
    363 
    364 }
    365