Home | History | Annotate | Download | only in app
      1 /*
      2  * Copyright (C) 2014 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 package android.app;
     17 
     18 import android.content.Context;
     19 import android.graphics.Matrix;
     20 import android.graphics.Rect;
     21 import android.graphics.RectF;
     22 import android.os.Bundle;
     23 import android.os.Handler;
     24 import android.os.Parcelable;
     25 import android.os.ResultReceiver;
     26 import android.transition.Transition;
     27 import android.transition.TransitionListenerAdapter;
     28 import android.transition.TransitionSet;
     29 import android.transition.Visibility;
     30 import android.util.ArrayMap;
     31 import android.util.ArraySet;
     32 import android.view.GhostView;
     33 import android.view.View;
     34 import android.view.ViewGroup;
     35 import android.view.ViewGroupOverlay;
     36 import android.view.ViewParent;
     37 import android.view.ViewRootImpl;
     38 import android.view.ViewTreeObserver;
     39 import android.view.Window;
     40 import android.widget.ImageView;
     41 
     42 import com.android.internal.view.OneShotPreDrawListener;
     43 
     44 import java.util.ArrayList;
     45 import java.util.Collection;
     46 
     47 /**
     48  * Base class for ExitTransitionCoordinator and EnterTransitionCoordinator, classes
     49  * that manage activity transitions and the communications coordinating them between
     50  * Activities. The ExitTransitionCoordinator is created in the
     51  * ActivityOptions#makeSceneTransitionAnimation. The EnterTransitionCoordinator
     52  * is created by ActivityOptions#createEnterActivityTransition by Activity when the window is
     53  * attached.
     54  *
     55  * Typical startActivity goes like this:
     56  * 1) ExitTransitionCoordinator created with ActivityOptions#makeSceneTransitionAnimation
     57  * 2) Activity#startActivity called and that calls startExit() through
     58  *    ActivityOptions#dispatchStartExit
     59  *    - Exit transition starts by setting transitioning Views to INVISIBLE
     60  * 3) Launched Activity starts, creating an EnterTransitionCoordinator.
     61  *    - The Window is made translucent
     62  *    - The Window background alpha is set to 0
     63  *    - The transitioning views are made INVISIBLE
     64  *    - MSG_SET_LISTENER is sent back to the ExitTransitionCoordinator.
     65  * 4) The shared element transition completes.
     66  *    - MSG_TAKE_SHARED_ELEMENTS is sent to the EnterTransitionCoordinator
     67  * 5) The MSG_TAKE_SHARED_ELEMENTS is received by the EnterTransitionCoordinator.
     68  *    - Shared elements are made VISIBLE
     69  *    - Shared elements positions and size are set to match the end state of the calling
     70  *      Activity.
     71  *    - The shared element transition is started
     72  *    - If the window allows overlapping transitions, the views transition is started by setting
     73  *      the entering Views to VISIBLE and the background alpha is animated to opaque.
     74  *    - MSG_HIDE_SHARED_ELEMENTS is sent to the ExitTransitionCoordinator
     75  * 6) MSG_HIDE_SHARED_ELEMENTS is received by the ExitTransitionCoordinator
     76  *    - The shared elements are made INVISIBLE
     77  * 7) The exit transition completes in the calling Activity.
     78  *    - MSG_EXIT_TRANSITION_COMPLETE is sent to the EnterTransitionCoordinator.
     79  * 8) The MSG_EXIT_TRANSITION_COMPLETE is received by the EnterTransitionCoordinator.
     80  *    - If the window doesn't allow overlapping enter transitions, the enter transition is started
     81  *      by setting entering views to VISIBLE and the background is animated to opaque.
     82  * 9) The background opacity animation completes.
     83  *    - The window is made opaque
     84  * 10) The calling Activity gets an onStop() call
     85  *    - onActivityStopped() is called and all exited Views are made VISIBLE.
     86  *
     87  * Typical finishAfterTransition goes like this:
     88  * 1) finishAfterTransition() creates an ExitTransitionCoordinator and calls startExit()
     89  *    - The Window start transitioning to Translucent with a new ActivityOptions.
     90  *    - If no background exists, a black background is substituted
     91  *    - The shared elements in the scene are matched against those shared elements
     92  *      that were sent by comparing the names.
     93  *    - The exit transition is started by setting Views to INVISIBLE.
     94  * 2) The ActivityOptions is received by the Activity and an EnterTransitionCoordinator is created.
     95  *    - All transitioning views are made VISIBLE to reverse what was done when onActivityStopped()
     96  *      was called
     97  * 3) The Window is made translucent and a callback is received
     98  *    - The background alpha is animated to 0
     99  * 4) The background alpha animation completes
    100  * 5) The shared element transition completes
    101  *    - After both 4 & 5 complete, MSG_TAKE_SHARED_ELEMENTS is sent to the
    102  *      EnterTransitionCoordinator
    103  * 6) MSG_TAKE_SHARED_ELEMENTS is received by EnterTransitionCoordinator
    104  *    - Shared elements are made VISIBLE
    105  *    - Shared elements positions and size are set to match the end state of the calling
    106  *      Activity.
    107  *    - The shared element transition is started
    108  *    - If the window allows overlapping transitions, the views transition is started by setting
    109  *      the entering Views to VISIBLE.
    110  *    - MSG_HIDE_SHARED_ELEMENTS is sent to the ExitTransitionCoordinator
    111  * 7) MSG_HIDE_SHARED_ELEMENTS is received by the ExitTransitionCoordinator
    112  *    - The shared elements are made INVISIBLE
    113  * 8) The exit transition completes in the finishing Activity.
    114  *    - MSG_EXIT_TRANSITION_COMPLETE is sent to the EnterTransitionCoordinator.
    115  *    - finish() is called on the exiting Activity
    116  * 9) The MSG_EXIT_TRANSITION_COMPLETE is received by the EnterTransitionCoordinator.
    117  *    - If the window doesn't allow overlapping enter transitions, the enter transition is started
    118  *      by setting entering views to VISIBLE.
    119  */
    120 abstract class ActivityTransitionCoordinator extends ResultReceiver {
    121     private static final String TAG = "ActivityTransitionCoordinator";
    122 
    123     /**
    124      * For Activity transitions, the called Activity's listener to receive calls
    125      * when transitions complete.
    126      */
    127     static final String KEY_REMOTE_RECEIVER = "android:remoteReceiver";
    128 
    129     protected static final String KEY_SCREEN_LEFT = "shared_element:screenLeft";
    130     protected static final String KEY_SCREEN_TOP = "shared_element:screenTop";
    131     protected static final String KEY_SCREEN_RIGHT = "shared_element:screenRight";
    132     protected static final String KEY_SCREEN_BOTTOM= "shared_element:screenBottom";
    133     protected static final String KEY_TRANSLATION_Z = "shared_element:translationZ";
    134     protected static final String KEY_SNAPSHOT = "shared_element:bitmap";
    135     protected static final String KEY_SCALE_TYPE = "shared_element:scaleType";
    136     protected static final String KEY_IMAGE_MATRIX = "shared_element:imageMatrix";
    137     protected static final String KEY_ELEVATION = "shared_element:elevation";
    138 
    139     protected static final ImageView.ScaleType[] SCALE_TYPE_VALUES = ImageView.ScaleType.values();
    140 
    141     /**
    142      * Sent by the exiting coordinator (either EnterTransitionCoordinator
    143      * or ExitTransitionCoordinator) after the shared elements have
    144      * become stationary (shared element transition completes). This tells
    145      * the remote coordinator to take control of the shared elements and
    146      * that animations may begin. The remote Activity won't start entering
    147      * until this message is received, but may wait for
    148      * MSG_EXIT_TRANSITION_COMPLETE if allowOverlappingTransitions() is true.
    149      */
    150     public static final int MSG_SET_REMOTE_RECEIVER = 100;
    151 
    152     /**
    153      * Sent by the entering coordinator to tell the exiting coordinator
    154      * to hide its shared elements after it has started its shared
    155      * element transition. This is temporary until the
    156      * interlock of shared elements is figured out.
    157      */
    158     public static final int MSG_HIDE_SHARED_ELEMENTS = 101;
    159 
    160     /**
    161      * Sent by the exiting coordinator (either EnterTransitionCoordinator
    162      * or ExitTransitionCoordinator) after the shared elements have
    163      * become stationary (shared element transition completes). This tells
    164      * the remote coordinator to take control of the shared elements and
    165      * that animations may begin. The remote Activity won't start entering
    166      * until this message is received, but may wait for
    167      * MSG_EXIT_TRANSITION_COMPLETE if allowOverlappingTransitions() is true.
    168      */
    169     public static final int MSG_TAKE_SHARED_ELEMENTS = 103;
    170 
    171     /**
    172      * Sent by the exiting coordinator (either
    173      * EnterTransitionCoordinator or ExitTransitionCoordinator) after
    174      * the exiting Views have finished leaving the scene. This will
    175      * be ignored if allowOverlappingTransitions() is true on the
    176      * remote coordinator. If it is false, it will trigger the enter
    177      * transition to start.
    178      */
    179     public static final int MSG_EXIT_TRANSITION_COMPLETE = 104;
    180 
    181     /**
    182      * Sent by Activity#startActivity to begin the exit transition.
    183      */
    184     public static final int MSG_START_EXIT_TRANSITION = 105;
    185 
    186     /**
    187      * It took too long for a message from the entering Activity, so we canceled the transition.
    188      */
    189     public static final int MSG_CANCEL = 106;
    190 
    191     /**
    192      * When returning, this is the destination location for the shared element.
    193      */
    194     public static final int MSG_SHARED_ELEMENT_DESTINATION = 107;
    195 
    196     private Window mWindow;
    197     final protected ArrayList<String> mAllSharedElementNames;
    198     final protected ArrayList<View> mSharedElements = new ArrayList<View>();
    199     final protected ArrayList<String> mSharedElementNames = new ArrayList<String>();
    200     protected ArrayList<View> mTransitioningViews = new ArrayList<View>();
    201     protected SharedElementCallback mListener;
    202     protected ResultReceiver mResultReceiver;
    203     final private FixedEpicenterCallback mEpicenterCallback = new FixedEpicenterCallback();
    204     final protected boolean mIsReturning;
    205     private Runnable mPendingTransition;
    206     private boolean mIsStartingTransition;
    207     private ArrayList<GhostViewListeners> mGhostViewListeners =
    208             new ArrayList<GhostViewListeners>();
    209     private ArrayMap<View, Float> mOriginalAlphas = new ArrayMap<View, Float>();
    210     private ArrayList<Matrix> mSharedElementParentMatrices;
    211     private boolean mSharedElementTransitionComplete;
    212     private boolean mViewsTransitionComplete;
    213     private boolean mBackgroundAnimatorComplete;
    214     private ArrayList<View> mStrippedTransitioningViews = new ArrayList<>();
    215 
    216     public ActivityTransitionCoordinator(Window window,
    217             ArrayList<String> allSharedElementNames,
    218             SharedElementCallback listener, boolean isReturning) {
    219         super(new Handler());
    220         mWindow = window;
    221         mListener = listener;
    222         mAllSharedElementNames = allSharedElementNames;
    223         mIsReturning = isReturning;
    224     }
    225 
    226     protected void viewsReady(ArrayMap<String, View> sharedElements) {
    227         sharedElements.retainAll(mAllSharedElementNames);
    228         if (mListener != null) {
    229             mListener.onMapSharedElements(mAllSharedElementNames, sharedElements);
    230         }
    231         setSharedElements(sharedElements);
    232         if (getViewsTransition() != null && mTransitioningViews != null) {
    233             ViewGroup decorView = getDecor();
    234             if (decorView != null) {
    235                 decorView.captureTransitioningViews(mTransitioningViews);
    236             }
    237             mTransitioningViews.removeAll(mSharedElements);
    238         }
    239         setEpicenter();
    240     }
    241 
    242     /**
    243      * Iterates over the shared elements and adds them to the members in order.
    244      * Shared elements that are nested in other shared elements are placed after the
    245      * elements that they are nested in. This means that layout ordering can be done
    246      * from first to last.
    247      *
    248      * @param sharedElements The map of transition names to shared elements to set into
    249      *                       the member fields.
    250      */
    251     private void setSharedElements(ArrayMap<String, View> sharedElements) {
    252         boolean isFirstRun = true;
    253         while (!sharedElements.isEmpty()) {
    254             final int numSharedElements = sharedElements.size();
    255             for (int i = numSharedElements - 1; i >= 0; i--) {
    256                 final View view = sharedElements.valueAt(i);
    257                 final String name = sharedElements.keyAt(i);
    258                 if (isFirstRun && (view == null || !view.isAttachedToWindow() || name == null)) {
    259                     sharedElements.removeAt(i);
    260                 } else if (!isNested(view, sharedElements)) {
    261                     mSharedElementNames.add(name);
    262                     mSharedElements.add(view);
    263                     sharedElements.removeAt(i);
    264                 }
    265             }
    266             isFirstRun = false;
    267         }
    268     }
    269 
    270     /**
    271      * Returns true when view is nested in any of the values of sharedElements.
    272      */
    273     private static boolean isNested(View view, ArrayMap<String, View> sharedElements) {
    274         ViewParent parent = view.getParent();
    275         boolean isNested = false;
    276         while (parent instanceof View) {
    277             View parentView = (View) parent;
    278             if (sharedElements.containsValue(parentView)) {
    279                 isNested = true;
    280                 break;
    281             }
    282             parent = parentView.getParent();
    283         }
    284         return isNested;
    285     }
    286 
    287     protected void stripOffscreenViews() {
    288         if (mTransitioningViews == null) {
    289             return;
    290         }
    291         Rect r = new Rect();
    292         for (int i = mTransitioningViews.size() - 1; i >= 0; i--) {
    293             View view = mTransitioningViews.get(i);
    294             if (!view.getGlobalVisibleRect(r)) {
    295                 mTransitioningViews.remove(i);
    296                 mStrippedTransitioningViews.add(view);
    297             }
    298         }
    299     }
    300 
    301     protected Window getWindow() {
    302         return mWindow;
    303     }
    304 
    305     public ViewGroup getDecor() {
    306         return (mWindow == null) ? null : (ViewGroup) mWindow.getDecorView();
    307     }
    308 
    309     /**
    310      * Sets the transition epicenter to the position of the first shared element.
    311      */
    312     protected void setEpicenter() {
    313         View epicenter = null;
    314         if (!mAllSharedElementNames.isEmpty() && !mSharedElementNames.isEmpty()) {
    315             int index = mSharedElementNames.indexOf(mAllSharedElementNames.get(0));
    316             if (index >= 0) {
    317                 epicenter = mSharedElements.get(index);
    318             }
    319         }
    320         setEpicenter(epicenter);
    321     }
    322 
    323     private void setEpicenter(View view) {
    324         if (view == null) {
    325             mEpicenterCallback.setEpicenter(null);
    326         } else {
    327             Rect epicenter = new Rect();
    328             view.getBoundsOnScreen(epicenter);
    329             mEpicenterCallback.setEpicenter(epicenter);
    330         }
    331     }
    332 
    333     public ArrayList<String> getAcceptedNames() {
    334         return mSharedElementNames;
    335     }
    336 
    337     public ArrayList<String> getMappedNames() {
    338         ArrayList<String> names = new ArrayList<String>(mSharedElements.size());
    339         for (int i = 0; i < mSharedElements.size(); i++) {
    340             names.add(mSharedElements.get(i).getTransitionName());
    341         }
    342         return names;
    343     }
    344 
    345     public ArrayList<View> copyMappedViews() {
    346         return new ArrayList<View>(mSharedElements);
    347     }
    348 
    349     public ArrayList<String> getAllSharedElementNames() { return mAllSharedElementNames; }
    350 
    351     protected Transition setTargets(Transition transition, boolean add) {
    352         if (transition == null || (add &&
    353                 (mTransitioningViews == null || mTransitioningViews.isEmpty()))) {
    354             return null;
    355         }
    356         // Add the targets to a set containing transition so that transition
    357         // remains unaffected. We don't want to modify the targets of transition itself.
    358         TransitionSet set = new TransitionSet();
    359         if (mTransitioningViews != null) {
    360             for (int i = mTransitioningViews.size() - 1; i >= 0; i--) {
    361                 View view = mTransitioningViews.get(i);
    362                 if (add) {
    363                     set.addTarget(view);
    364                 } else {
    365                     set.excludeTarget(view, true);
    366                 }
    367             }
    368         }
    369         if (mStrippedTransitioningViews != null) {
    370             for (int i = mStrippedTransitioningViews.size() - 1; i >= 0; i--) {
    371                 View view = mStrippedTransitioningViews.get(i);
    372                 set.excludeTarget(view, true);
    373             }
    374         }
    375         // By adding the transition after addTarget, we prevent addTarget from
    376         // affecting transition.
    377         set.addTransition(transition);
    378 
    379         if (!add && mTransitioningViews != null && !mTransitioningViews.isEmpty()) {
    380             // Allow children of excluded transitioning views, but not the views themselves
    381             set = new TransitionSet().addTransition(set);
    382         }
    383 
    384         return set;
    385     }
    386 
    387     protected Transition configureTransition(Transition transition,
    388             boolean includeTransitioningViews) {
    389         if (transition != null) {
    390             transition = transition.clone();
    391             transition.setEpicenterCallback(mEpicenterCallback);
    392             transition = setTargets(transition, includeTransitioningViews);
    393         }
    394         noLayoutSuppressionForVisibilityTransitions(transition);
    395         return transition;
    396     }
    397 
    398     /**
    399      * Looks through the transition to see which Views have been included and which have been
    400      * excluded. {@code views} will be modified to contain only those Views that are included
    401      * in the transition. If {@code transition} is a TransitionSet, it will search through all
    402      * contained Transitions to find targeted Views.
    403      *
    404      * @param transition The transition to look through for inclusion of Views
    405      * @param views The list of Views that are to be checked for inclusion. Will be modified
    406      *              to remove all excluded Views, possibly leaving an empty list.
    407      */
    408     protected static void removeExcludedViews(Transition transition, ArrayList<View> views) {
    409         ArraySet<View> included = new ArraySet<>();
    410         findIncludedViews(transition, views, included);
    411         views.clear();
    412         views.addAll(included);
    413     }
    414 
    415     /**
    416      * Looks through the transition to see which Views have been included. Only {@code views}
    417      * will be examined for inclusion. If {@code transition} is a TransitionSet, it will search
    418      * through all contained Transitions to find targeted Views.
    419      *
    420      * @param transition The transition to look through for inclusion of Views
    421      * @param views The list of Views that are to be checked for inclusion.
    422      * @param included Modified to contain all Views in views that have at least one Transition
    423      *                 that affects it.
    424      */
    425     private static void findIncludedViews(Transition transition, ArrayList<View> views,
    426             ArraySet<View> included) {
    427         if (transition instanceof TransitionSet) {
    428             TransitionSet set = (TransitionSet) transition;
    429             ArrayList<View> includedViews = new ArrayList<>();
    430             final int numViews = views.size();
    431             for (int i = 0; i < numViews; i++) {
    432                 final View view = views.get(i);
    433                 if (transition.isValidTarget(view)) {
    434                     includedViews.add(view);
    435                 }
    436             }
    437             final int count = set.getTransitionCount();
    438             for (int i = 0; i < count; i++) {
    439                 findIncludedViews(set.getTransitionAt(i), includedViews, included);
    440             }
    441         } else {
    442             final int numViews = views.size();
    443             for (int i = 0; i < numViews; i++) {
    444                 final View view = views.get(i);
    445                 if (transition.isValidTarget(view)) {
    446                     included.add(view);
    447                 }
    448             }
    449         }
    450     }
    451 
    452     protected static Transition mergeTransitions(Transition transition1, Transition transition2) {
    453         if (transition1 == null) {
    454             return transition2;
    455         } else if (transition2 == null) {
    456             return transition1;
    457         } else {
    458             TransitionSet transitionSet = new TransitionSet();
    459             transitionSet.addTransition(transition1);
    460             transitionSet.addTransition(transition2);
    461             return transitionSet;
    462         }
    463     }
    464 
    465     protected ArrayMap<String, View> mapSharedElements(ArrayList<String> accepted,
    466             ArrayList<View> localViews) {
    467         ArrayMap<String, View> sharedElements = new ArrayMap<String, View>();
    468         if (accepted != null) {
    469             for (int i = 0; i < accepted.size(); i++) {
    470                 sharedElements.put(accepted.get(i), localViews.get(i));
    471             }
    472         } else {
    473             ViewGroup decorView = getDecor();
    474             if (decorView != null) {
    475                 decorView.findNamedViews(sharedElements);
    476             }
    477         }
    478         return sharedElements;
    479     }
    480 
    481     protected void setResultReceiver(ResultReceiver resultReceiver) {
    482         mResultReceiver = resultReceiver;
    483     }
    484 
    485     protected abstract Transition getViewsTransition();
    486 
    487     private void setSharedElementState(View view, String name, Bundle transitionArgs,
    488             Matrix tempMatrix, RectF tempRect, int[] decorLoc) {
    489         Bundle sharedElementBundle = transitionArgs.getBundle(name);
    490         if (sharedElementBundle == null) {
    491             return;
    492         }
    493 
    494         if (view instanceof ImageView) {
    495             int scaleTypeInt = sharedElementBundle.getInt(KEY_SCALE_TYPE, -1);
    496             if (scaleTypeInt >= 0) {
    497                 ImageView imageView = (ImageView) view;
    498                 ImageView.ScaleType scaleType = SCALE_TYPE_VALUES[scaleTypeInt];
    499                 imageView.setScaleType(scaleType);
    500                 if (scaleType == ImageView.ScaleType.MATRIX) {
    501                     float[] matrixValues = sharedElementBundle.getFloatArray(KEY_IMAGE_MATRIX);
    502                     tempMatrix.setValues(matrixValues);
    503                     imageView.setImageMatrix(tempMatrix);
    504                 }
    505             }
    506         }
    507 
    508         float z = sharedElementBundle.getFloat(KEY_TRANSLATION_Z);
    509         view.setTranslationZ(z);
    510         float elevation = sharedElementBundle.getFloat(KEY_ELEVATION);
    511         view.setElevation(elevation);
    512 
    513         float left = sharedElementBundle.getFloat(KEY_SCREEN_LEFT);
    514         float top = sharedElementBundle.getFloat(KEY_SCREEN_TOP);
    515         float right = sharedElementBundle.getFloat(KEY_SCREEN_RIGHT);
    516         float bottom = sharedElementBundle.getFloat(KEY_SCREEN_BOTTOM);
    517 
    518         if (decorLoc != null) {
    519             left -= decorLoc[0];
    520             top -= decorLoc[1];
    521             right -= decorLoc[0];
    522             bottom -= decorLoc[1];
    523         } else {
    524             // Find the location in the view's parent
    525             getSharedElementParentMatrix(view, tempMatrix);
    526             tempRect.set(left, top, right, bottom);
    527             tempMatrix.mapRect(tempRect);
    528 
    529             float leftInParent = tempRect.left;
    530             float topInParent = tempRect.top;
    531 
    532             // Find the size of the view
    533             view.getInverseMatrix().mapRect(tempRect);
    534             float width = tempRect.width();
    535             float height = tempRect.height();
    536 
    537             // Now determine the offset due to view transform:
    538             view.setLeft(0);
    539             view.setTop(0);
    540             view.setRight(Math.round(width));
    541             view.setBottom(Math.round(height));
    542             tempRect.set(0, 0, width, height);
    543             view.getMatrix().mapRect(tempRect);
    544 
    545             left = leftInParent - tempRect.left;
    546             top = topInParent - tempRect.top;
    547             right = left + width;
    548             bottom = top + height;
    549         }
    550 
    551         int x = Math.round(left);
    552         int y = Math.round(top);
    553         int width = Math.round(right) - x;
    554         int height = Math.round(bottom) - y;
    555         int widthSpec = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY);
    556         int heightSpec = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY);
    557         view.measure(widthSpec, heightSpec);
    558 
    559         view.layout(x, y, x + width, y + height);
    560     }
    561 
    562     private void setSharedElementMatrices() {
    563         int numSharedElements = mSharedElements.size();
    564         if (numSharedElements > 0) {
    565             mSharedElementParentMatrices = new ArrayList<Matrix>(numSharedElements);
    566         }
    567         for (int i = 0; i < numSharedElements; i++) {
    568             View view = mSharedElements.get(i);
    569 
    570             // Find the location in the view's parent
    571             ViewGroup parent = (ViewGroup) view.getParent();
    572             Matrix matrix = new Matrix();
    573             if (parent != null) {
    574                 parent.transformMatrixToLocal(matrix);
    575                 matrix.postTranslate(parent.getScrollX(), parent.getScrollY());
    576             }
    577             mSharedElementParentMatrices.add(matrix);
    578         }
    579     }
    580 
    581     private void getSharedElementParentMatrix(View view, Matrix matrix) {
    582         final int index = mSharedElementParentMatrices == null ? -1
    583                 : mSharedElements.indexOf(view);
    584         if (index < 0) {
    585             matrix.reset();
    586             ViewParent viewParent = view.getParent();
    587             if (viewParent instanceof ViewGroup) {
    588                 // Find the location in the view's parent
    589                 ViewGroup parent = (ViewGroup) viewParent;
    590                 parent.transformMatrixToLocal(matrix);
    591                 matrix.postTranslate(parent.getScrollX(), parent.getScrollY());
    592             }
    593         } else {
    594             // The indices of mSharedElementParentMatrices matches the
    595             // mSharedElement matrices.
    596             Matrix parentMatrix = mSharedElementParentMatrices.get(index);
    597             matrix.set(parentMatrix);
    598         }
    599     }
    600 
    601     protected ArrayList<SharedElementOriginalState> setSharedElementState(
    602             Bundle sharedElementState, final ArrayList<View> snapshots) {
    603         ArrayList<SharedElementOriginalState> originalImageState =
    604                 new ArrayList<SharedElementOriginalState>();
    605         if (sharedElementState != null) {
    606             Matrix tempMatrix = new Matrix();
    607             RectF tempRect = new RectF();
    608             final int numSharedElements = mSharedElements.size();
    609             for (int i = 0; i < numSharedElements; i++) {
    610                 View sharedElement = mSharedElements.get(i);
    611                 String name = mSharedElementNames.get(i);
    612                 SharedElementOriginalState originalState = getOldSharedElementState(sharedElement,
    613                         name, sharedElementState);
    614                 originalImageState.add(originalState);
    615                 setSharedElementState(sharedElement, name, sharedElementState,
    616                         tempMatrix, tempRect, null);
    617             }
    618         }
    619         if (mListener != null) {
    620             mListener.onSharedElementStart(mSharedElementNames, mSharedElements, snapshots);
    621         }
    622         return originalImageState;
    623     }
    624 
    625     protected void notifySharedElementEnd(ArrayList<View> snapshots) {
    626         if (mListener != null) {
    627             mListener.onSharedElementEnd(mSharedElementNames, mSharedElements, snapshots);
    628         }
    629     }
    630 
    631     protected void scheduleSetSharedElementEnd(final ArrayList<View> snapshots) {
    632         final View decorView = getDecor();
    633         if (decorView != null) {
    634             OneShotPreDrawListener.add(decorView, () -> {
    635                 notifySharedElementEnd(snapshots);
    636             });
    637         }
    638     }
    639 
    640     private static SharedElementOriginalState getOldSharedElementState(View view, String name,
    641             Bundle transitionArgs) {
    642 
    643         SharedElementOriginalState state = new SharedElementOriginalState();
    644         state.mLeft = view.getLeft();
    645         state.mTop = view.getTop();
    646         state.mRight = view.getRight();
    647         state.mBottom = view.getBottom();
    648         state.mMeasuredWidth = view.getMeasuredWidth();
    649         state.mMeasuredHeight = view.getMeasuredHeight();
    650         state.mTranslationZ = view.getTranslationZ();
    651         state.mElevation = view.getElevation();
    652         if (!(view instanceof ImageView)) {
    653             return state;
    654         }
    655         Bundle bundle = transitionArgs.getBundle(name);
    656         if (bundle == null) {
    657             return state;
    658         }
    659         int scaleTypeInt = bundle.getInt(KEY_SCALE_TYPE, -1);
    660         if (scaleTypeInt < 0) {
    661             return state;
    662         }
    663 
    664         ImageView imageView = (ImageView) view;
    665         state.mScaleType = imageView.getScaleType();
    666         if (state.mScaleType == ImageView.ScaleType.MATRIX) {
    667             state.mMatrix = new Matrix(imageView.getImageMatrix());
    668         }
    669         return state;
    670     }
    671 
    672     protected ArrayList<View> createSnapshots(Bundle state, Collection<String> names) {
    673         int numSharedElements = names.size();
    674         ArrayList<View> snapshots = new ArrayList<View>(numSharedElements);
    675         if (numSharedElements == 0) {
    676             return snapshots;
    677         }
    678         Context context = getWindow().getContext();
    679         int[] decorLoc = new int[2];
    680         ViewGroup decorView = getDecor();
    681         if (decorView != null) {
    682             decorView.getLocationOnScreen(decorLoc);
    683         }
    684         Matrix tempMatrix = new Matrix();
    685         for (String name: names) {
    686             Bundle sharedElementBundle = state.getBundle(name);
    687             View snapshot = null;
    688             if (sharedElementBundle != null) {
    689                 Parcelable parcelable = sharedElementBundle.getParcelable(KEY_SNAPSHOT);
    690                 if (parcelable != null && mListener != null) {
    691                     snapshot = mListener.onCreateSnapshotView(context, parcelable);
    692                 }
    693                 if (snapshot != null) {
    694                     setSharedElementState(snapshot, name, state, tempMatrix, null, decorLoc);
    695                 }
    696             }
    697             // Even null snapshots are added so they remain in the same order as shared elements.
    698             snapshots.add(snapshot);
    699         }
    700         return snapshots;
    701     }
    702 
    703     protected static void setOriginalSharedElementState(ArrayList<View> sharedElements,
    704             ArrayList<SharedElementOriginalState> originalState) {
    705         for (int i = 0; i < originalState.size(); i++) {
    706             View view = sharedElements.get(i);
    707             SharedElementOriginalState state = originalState.get(i);
    708             if (view instanceof ImageView && state.mScaleType != null) {
    709                 ImageView imageView = (ImageView) view;
    710                 imageView.setScaleType(state.mScaleType);
    711                 if (state.mScaleType == ImageView.ScaleType.MATRIX) {
    712                   imageView.setImageMatrix(state.mMatrix);
    713                 }
    714             }
    715             view.setElevation(state.mElevation);
    716             view.setTranslationZ(state.mTranslationZ);
    717             int widthSpec = View.MeasureSpec.makeMeasureSpec(state.mMeasuredWidth,
    718                     View.MeasureSpec.EXACTLY);
    719             int heightSpec = View.MeasureSpec.makeMeasureSpec(state.mMeasuredHeight,
    720                     View.MeasureSpec.EXACTLY);
    721             view.measure(widthSpec, heightSpec);
    722             view.layout(state.mLeft, state.mTop, state.mRight, state.mBottom);
    723         }
    724     }
    725 
    726     protected Bundle captureSharedElementState() {
    727         Bundle bundle = new Bundle();
    728         RectF tempBounds = new RectF();
    729         Matrix tempMatrix = new Matrix();
    730         for (int i = 0; i < mSharedElements.size(); i++) {
    731             View sharedElement = mSharedElements.get(i);
    732             String name = mSharedElementNames.get(i);
    733             captureSharedElementState(sharedElement, name, bundle, tempMatrix, tempBounds);
    734         }
    735         return bundle;
    736     }
    737 
    738     protected void clearState() {
    739         // Clear the state so that we can't hold any references accidentally and leak memory.
    740         mWindow = null;
    741         mSharedElements.clear();
    742         mTransitioningViews = null;
    743         mStrippedTransitioningViews = null;
    744         mOriginalAlphas.clear();
    745         mResultReceiver = null;
    746         mPendingTransition = null;
    747         mListener = null;
    748         mSharedElementParentMatrices = null;
    749     }
    750 
    751     protected long getFadeDuration() {
    752         return getWindow().getTransitionBackgroundFadeDuration();
    753     }
    754 
    755     protected void hideViews(ArrayList<View> views) {
    756         int count = views.size();
    757         for (int i = 0; i < count; i++) {
    758             View view = views.get(i);
    759             if (!mOriginalAlphas.containsKey(view)) {
    760                 mOriginalAlphas.put(view, view.getAlpha());
    761             }
    762             view.setAlpha(0f);
    763         }
    764     }
    765 
    766     protected void showViews(ArrayList<View> views, boolean setTransitionAlpha) {
    767         int count = views.size();
    768         for (int i = 0; i < count; i++) {
    769             showView(views.get(i), setTransitionAlpha);
    770         }
    771     }
    772 
    773     private void showView(View view, boolean setTransitionAlpha) {
    774         Float alpha = mOriginalAlphas.remove(view);
    775         if (alpha != null) {
    776             view.setAlpha(alpha);
    777         }
    778         if (setTransitionAlpha) {
    779             view.setTransitionAlpha(1f);
    780         }
    781     }
    782 
    783     /**
    784      * Captures placement information for Views with a shared element name for
    785      * Activity Transitions.
    786      *
    787      * @param view           The View to capture the placement information for.
    788      * @param name           The shared element name in the target Activity to apply the placement
    789      *                       information for.
    790      * @param transitionArgs Bundle to store shared element placement information.
    791      * @param tempBounds     A temporary Rect for capturing the current location of views.
    792      */
    793     protected void captureSharedElementState(View view, String name, Bundle transitionArgs,
    794             Matrix tempMatrix, RectF tempBounds) {
    795         Bundle sharedElementBundle = new Bundle();
    796         tempMatrix.reset();
    797         view.transformMatrixToGlobal(tempMatrix);
    798         tempBounds.set(0, 0, view.getWidth(), view.getHeight());
    799         tempMatrix.mapRect(tempBounds);
    800 
    801         sharedElementBundle.putFloat(KEY_SCREEN_LEFT, tempBounds.left);
    802         sharedElementBundle.putFloat(KEY_SCREEN_RIGHT, tempBounds.right);
    803         sharedElementBundle.putFloat(KEY_SCREEN_TOP, tempBounds.top);
    804         sharedElementBundle.putFloat(KEY_SCREEN_BOTTOM, tempBounds.bottom);
    805         sharedElementBundle.putFloat(KEY_TRANSLATION_Z, view.getTranslationZ());
    806         sharedElementBundle.putFloat(KEY_ELEVATION, view.getElevation());
    807 
    808         Parcelable bitmap = null;
    809         if (mListener != null) {
    810             bitmap = mListener.onCaptureSharedElementSnapshot(view, tempMatrix, tempBounds);
    811         }
    812 
    813         if (bitmap != null) {
    814             sharedElementBundle.putParcelable(KEY_SNAPSHOT, bitmap);
    815         }
    816 
    817         if (view instanceof ImageView) {
    818             ImageView imageView = (ImageView) view;
    819             int scaleTypeInt = scaleTypeToInt(imageView.getScaleType());
    820             sharedElementBundle.putInt(KEY_SCALE_TYPE, scaleTypeInt);
    821             if (imageView.getScaleType() == ImageView.ScaleType.MATRIX) {
    822                 float[] matrix = new float[9];
    823                 imageView.getImageMatrix().getValues(matrix);
    824                 sharedElementBundle.putFloatArray(KEY_IMAGE_MATRIX, matrix);
    825             }
    826         }
    827 
    828         transitionArgs.putBundle(name, sharedElementBundle);
    829     }
    830 
    831 
    832     protected void startTransition(Runnable runnable) {
    833         if (mIsStartingTransition) {
    834             mPendingTransition = runnable;
    835         } else {
    836             mIsStartingTransition = true;
    837             runnable.run();
    838         }
    839     }
    840 
    841     protected void transitionStarted() {
    842         mIsStartingTransition = false;
    843     }
    844 
    845     /**
    846      * Cancels any pending transitions and returns true if there is a transition is in
    847      * the middle of starting.
    848      */
    849     protected boolean cancelPendingTransitions() {
    850         mPendingTransition = null;
    851         return mIsStartingTransition;
    852     }
    853 
    854     protected void moveSharedElementsToOverlay() {
    855         if (mWindow == null || !mWindow.getSharedElementsUseOverlay()) {
    856             return;
    857         }
    858         setSharedElementMatrices();
    859         int numSharedElements = mSharedElements.size();
    860         ViewGroup decor = getDecor();
    861         if (decor != null) {
    862             boolean moveWithParent = moveSharedElementWithParent();
    863             Matrix tempMatrix = new Matrix();
    864             for (int i = 0; i < numSharedElements; i++) {
    865                 View view = mSharedElements.get(i);
    866                 if (view.isAttachedToWindow()) {
    867                     tempMatrix.reset();
    868                     mSharedElementParentMatrices.get(i).invert(tempMatrix);
    869                     GhostView.addGhost(view, decor, tempMatrix);
    870                     ViewGroup parent = (ViewGroup) view.getParent();
    871                     if (moveWithParent && !isInTransitionGroup(parent, decor)) {
    872                         GhostViewListeners listener = new GhostViewListeners(view, parent, decor);
    873                         parent.getViewTreeObserver().addOnPreDrawListener(listener);
    874                         parent.addOnAttachStateChangeListener(listener);
    875                         mGhostViewListeners.add(listener);
    876                     }
    877                 }
    878             }
    879         }
    880     }
    881 
    882     protected boolean moveSharedElementWithParent() {
    883         return true;
    884     }
    885 
    886     public static boolean isInTransitionGroup(ViewParent viewParent, ViewGroup decor) {
    887         if (viewParent == decor || !(viewParent instanceof ViewGroup)) {
    888             return false;
    889         }
    890         ViewGroup parent = (ViewGroup) viewParent;
    891         if (parent.isTransitionGroup()) {
    892             return true;
    893         } else {
    894             return isInTransitionGroup(parent.getParent(), decor);
    895         }
    896     }
    897 
    898     protected void moveSharedElementsFromOverlay() {
    899         int numListeners = mGhostViewListeners.size();
    900         for (int i = 0; i < numListeners; i++) {
    901             GhostViewListeners listener = mGhostViewListeners.get(i);
    902             listener.removeListener();
    903         }
    904         mGhostViewListeners.clear();
    905 
    906         if (mWindow == null || !mWindow.getSharedElementsUseOverlay()) {
    907             return;
    908         }
    909         ViewGroup decor = getDecor();
    910         if (decor != null) {
    911             ViewGroupOverlay overlay = decor.getOverlay();
    912             int count = mSharedElements.size();
    913             for (int i = 0; i < count; i++) {
    914                 View sharedElement = mSharedElements.get(i);
    915                 GhostView.removeGhost(sharedElement);
    916             }
    917         }
    918     }
    919 
    920     protected void setGhostVisibility(int visibility) {
    921         int numSharedElements = mSharedElements.size();
    922         for (int i = 0; i < numSharedElements; i++) {
    923             GhostView ghostView = GhostView.getGhost(mSharedElements.get(i));
    924             if (ghostView != null) {
    925                 ghostView.setVisibility(visibility);
    926             }
    927         }
    928     }
    929 
    930     protected void scheduleGhostVisibilityChange(final int visibility) {
    931         final View decorView = getDecor();
    932         if (decorView != null) {
    933             OneShotPreDrawListener.add(decorView, () -> {
    934                 setGhostVisibility(visibility);
    935             });
    936         }
    937     }
    938 
    939     protected boolean isViewsTransitionComplete() {
    940         return mViewsTransitionComplete;
    941     }
    942 
    943     protected void viewsTransitionComplete() {
    944         mViewsTransitionComplete = true;
    945         startInputWhenTransitionsComplete();
    946     }
    947 
    948     protected void backgroundAnimatorComplete() {
    949         mBackgroundAnimatorComplete = true;
    950     }
    951 
    952     protected void sharedElementTransitionComplete() {
    953         mSharedElementTransitionComplete = true;
    954         startInputWhenTransitionsComplete();
    955     }
    956     private void startInputWhenTransitionsComplete() {
    957         if (mViewsTransitionComplete && mSharedElementTransitionComplete) {
    958             final View decor = getDecor();
    959             if (decor != null) {
    960                 final ViewRootImpl viewRoot = decor.getViewRootImpl();
    961                 if (viewRoot != null) {
    962                     viewRoot.setPausedForTransition(false);
    963                 }
    964             }
    965             onTransitionsComplete();
    966         }
    967     }
    968 
    969     protected void pauseInput() {
    970         final View decor = getDecor();
    971         final ViewRootImpl viewRoot = decor == null ? null : decor.getViewRootImpl();
    972         if (viewRoot != null) {
    973             viewRoot.setPausedForTransition(true);
    974         }
    975     }
    976 
    977     protected void onTransitionsComplete() {}
    978 
    979     protected class ContinueTransitionListener extends TransitionListenerAdapter {
    980         @Override
    981         public void onTransitionStart(Transition transition) {
    982             mIsStartingTransition = false;
    983             Runnable pending = mPendingTransition;
    984             mPendingTransition = null;
    985             if (pending != null) {
    986                 startTransition(pending);
    987             }
    988         }
    989 
    990         @Override
    991         public void onTransitionEnd(Transition transition) {
    992             transition.removeListener(this);
    993         }
    994     }
    995 
    996     private static int scaleTypeToInt(ImageView.ScaleType scaleType) {
    997         for (int i = 0; i < SCALE_TYPE_VALUES.length; i++) {
    998             if (scaleType == SCALE_TYPE_VALUES[i]) {
    999                 return i;
   1000             }
   1001         }
   1002         return -1;
   1003     }
   1004 
   1005     protected void setTransitioningViewsVisiblity(int visiblity, boolean invalidate) {
   1006         final int numElements = mTransitioningViews == null ? 0 : mTransitioningViews.size();
   1007         for (int i = 0; i < numElements; i++) {
   1008             final View view = mTransitioningViews.get(i);
   1009             if (invalidate) {
   1010                 // Allow the view to be invalidated by the visibility change
   1011                 view.setVisibility(visiblity);
   1012             } else {
   1013                 // Don't invalidate the view with the visibility change
   1014                 view.setTransitionVisibility(visiblity);
   1015             }
   1016         }
   1017     }
   1018 
   1019     /**
   1020      * Blocks suppressLayout from Visibility transitions. It is ok to suppress the layout,
   1021      * but we don't want to force the layout when suppressLayout becomes false. This leads
   1022      * to visual glitches.
   1023      */
   1024     private static void noLayoutSuppressionForVisibilityTransitions(Transition transition) {
   1025         if (transition instanceof Visibility) {
   1026             final Visibility visibility = (Visibility) transition;
   1027             visibility.setSuppressLayout(false);
   1028         } else if (transition instanceof TransitionSet) {
   1029             final TransitionSet set = (TransitionSet) transition;
   1030             final int count = set.getTransitionCount();
   1031             for (int i = 0; i < count; i++) {
   1032                 noLayoutSuppressionForVisibilityTransitions(set.getTransitionAt(i));
   1033             }
   1034         }
   1035     }
   1036 
   1037     public boolean isTransitionRunning() {
   1038         return !(mViewsTransitionComplete && mSharedElementTransitionComplete &&
   1039                 mBackgroundAnimatorComplete);
   1040     }
   1041 
   1042     private static class FixedEpicenterCallback extends Transition.EpicenterCallback {
   1043         private Rect mEpicenter;
   1044 
   1045         public void setEpicenter(Rect epicenter) { mEpicenter = epicenter; }
   1046 
   1047         @Override
   1048         public Rect onGetEpicenter(Transition transition) {
   1049             return mEpicenter;
   1050         }
   1051     }
   1052 
   1053     private static class GhostViewListeners implements ViewTreeObserver.OnPreDrawListener,
   1054             View.OnAttachStateChangeListener {
   1055         private View mView;
   1056         private ViewGroup mDecor;
   1057         private View mParent;
   1058         private Matrix mMatrix = new Matrix();
   1059         private ViewTreeObserver mViewTreeObserver;
   1060 
   1061         public GhostViewListeners(View view, View parent, ViewGroup decor) {
   1062             mView = view;
   1063             mParent = parent;
   1064             mDecor = decor;
   1065             mViewTreeObserver = parent.getViewTreeObserver();
   1066         }
   1067 
   1068         public View getView() {
   1069             return mView;
   1070         }
   1071 
   1072         @Override
   1073         public boolean onPreDraw() {
   1074             GhostView ghostView = GhostView.getGhost(mView);
   1075             if (ghostView == null || !mView.isAttachedToWindow()) {
   1076                 removeListener();
   1077             } else {
   1078                 GhostView.calculateMatrix(mView, mDecor, mMatrix);
   1079                 ghostView.setMatrix(mMatrix);
   1080             }
   1081             return true;
   1082         }
   1083 
   1084         public void removeListener() {
   1085             if (mViewTreeObserver.isAlive()) {
   1086                 mViewTreeObserver.removeOnPreDrawListener(this);
   1087             } else {
   1088                 mParent.getViewTreeObserver().removeOnPreDrawListener(this);
   1089             }
   1090             mParent.removeOnAttachStateChangeListener(this);
   1091         }
   1092 
   1093         @Override
   1094         public void onViewAttachedToWindow(View v) {
   1095             mViewTreeObserver = v.getViewTreeObserver();
   1096         }
   1097 
   1098         @Override
   1099         public void onViewDetachedFromWindow(View v) {
   1100             removeListener();
   1101         }
   1102     }
   1103 
   1104     static class SharedElementOriginalState {
   1105         int mLeft;
   1106         int mTop;
   1107         int mRight;
   1108         int mBottom;
   1109         int mMeasuredWidth;
   1110         int mMeasuredHeight;
   1111         ImageView.ScaleType mScaleType;
   1112         Matrix mMatrix;
   1113         float mTranslationZ;
   1114         float mElevation;
   1115     }
   1116 }
   1117