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