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