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 
    210     public ActivityTransitionCoordinator(Window window,
    211             ArrayList<String> allSharedElementNames,
    212             SharedElementCallback listener, boolean isReturning) {
    213         super(new Handler());
    214         mWindow = window;
    215         mListener = listener;
    216         mAllSharedElementNames = allSharedElementNames;
    217         mIsReturning = isReturning;
    218     }
    219 
    220     protected void viewsReady(ArrayMap<String, View> sharedElements) {
    221         sharedElements.retainAll(mAllSharedElementNames);
    222         if (mListener != null) {
    223             mListener.onMapSharedElements(mAllSharedElementNames, sharedElements);
    224         }
    225         mSharedElementNames.addAll(sharedElements.keySet());
    226         mSharedElements.addAll(sharedElements.values());
    227         if (getViewsTransition() != null && mTransitioningViews != null) {
    228             ViewGroup decorView = getDecor();
    229             if (decorView != null) {
    230                 decorView.captureTransitioningViews(mTransitioningViews);
    231             }
    232             mTransitioningViews.removeAll(mSharedElements);
    233         }
    234         setEpicenter();
    235     }
    236 
    237     protected void stripOffscreenViews() {
    238         if (mTransitioningViews == null) {
    239             return;
    240         }
    241         Rect r = new Rect();
    242         for (int i = mTransitioningViews.size() - 1; i >= 0; i--) {
    243             View view = mTransitioningViews.get(i);
    244             if (!view.getGlobalVisibleRect(r)) {
    245                 mTransitioningViews.remove(i);
    246                 showView(view, true);
    247             }
    248         }
    249     }
    250 
    251     protected Window getWindow() {
    252         return mWindow;
    253     }
    254 
    255     public ViewGroup getDecor() {
    256         return (mWindow == null) ? null : (ViewGroup) mWindow.getDecorView();
    257     }
    258 
    259     /**
    260      * Sets the transition epicenter to the position of the first shared element.
    261      */
    262     protected void setEpicenter() {
    263         View epicenter = null;
    264         if (!mAllSharedElementNames.isEmpty() && !mSharedElementNames.isEmpty()) {
    265             int index = mSharedElementNames.indexOf(mAllSharedElementNames.get(0));
    266             if (index >= 0) {
    267                 epicenter = mSharedElements.get(index);
    268             }
    269         }
    270         setEpicenter(epicenter);
    271     }
    272 
    273     private void setEpicenter(View view) {
    274         if (view == null) {
    275             mEpicenterCallback.setEpicenter(null);
    276         } else {
    277             Rect epicenter = new Rect();
    278             view.getBoundsOnScreen(epicenter);
    279             mEpicenterCallback.setEpicenter(epicenter);
    280         }
    281     }
    282 
    283     public ArrayList<String> getAcceptedNames() {
    284         return mSharedElementNames;
    285     }
    286 
    287     public ArrayList<String> getMappedNames() {
    288         ArrayList<String> names = new ArrayList<String>(mSharedElements.size());
    289         for (int i = 0; i < mSharedElements.size(); i++) {
    290             names.add(mSharedElements.get(i).getTransitionName());
    291         }
    292         return names;
    293     }
    294 
    295     public ArrayList<View> copyMappedViews() {
    296         return new ArrayList<View>(mSharedElements);
    297     }
    298 
    299     public ArrayList<String> getAllSharedElementNames() { return mAllSharedElementNames; }
    300 
    301     protected Transition setTargets(Transition transition, boolean add) {
    302         if (transition == null || (add &&
    303                 (mTransitioningViews == null || mTransitioningViews.isEmpty()))) {
    304             return null;
    305         }
    306         // Add the targets to a set containing transition so that transition
    307         // remains unaffected. We don't want to modify the targets of transition itself.
    308         TransitionSet set = new TransitionSet();
    309         if (mTransitioningViews != null) {
    310             for (int i = mTransitioningViews.size() - 1; i >= 0; i--) {
    311                 View view = mTransitioningViews.get(i);
    312                 if (add) {
    313                     set.addTarget(view);
    314                 } else {
    315                     set.excludeTarget(view, true);
    316                 }
    317             }
    318         }
    319         // By adding the transition after addTarget, we prevent addTarget from
    320         // affecting transition.
    321         set.addTransition(transition);
    322 
    323         if (!add && mTransitioningViews != null && !mTransitioningViews.isEmpty()) {
    324             // Allow children of excluded transitioning views, but not the views themselves
    325             set = new TransitionSet().addTransition(set);
    326         }
    327 
    328         return set;
    329     }
    330 
    331     protected Transition configureTransition(Transition transition,
    332             boolean includeTransitioningViews) {
    333         if (transition != null) {
    334             transition = transition.clone();
    335             transition.setEpicenterCallback(mEpicenterCallback);
    336             transition = setTargets(transition, includeTransitioningViews);
    337         }
    338         return transition;
    339     }
    340 
    341     protected static Transition mergeTransitions(Transition transition1, Transition transition2) {
    342         if (transition1 == null) {
    343             return transition2;
    344         } else if (transition2 == null) {
    345             return transition1;
    346         } else {
    347             TransitionSet transitionSet = new TransitionSet();
    348             transitionSet.addTransition(transition1);
    349             transitionSet.addTransition(transition2);
    350             return transitionSet;
    351         }
    352     }
    353 
    354     protected ArrayMap<String, View> mapSharedElements(ArrayList<String> accepted,
    355             ArrayList<View> localViews) {
    356         ArrayMap<String, View> sharedElements = new ArrayMap<String, View>();
    357         if (accepted != null) {
    358             for (int i = 0; i < accepted.size(); i++) {
    359                 sharedElements.put(accepted.get(i), localViews.get(i));
    360             }
    361         } else {
    362             ViewGroup decorView = getDecor();
    363             if (decorView != null) {
    364                 decorView.findNamedViews(sharedElements);
    365             }
    366         }
    367         return sharedElements;
    368     }
    369 
    370     protected void setResultReceiver(ResultReceiver resultReceiver) {
    371         mResultReceiver = resultReceiver;
    372     }
    373 
    374     protected abstract Transition getViewsTransition();
    375 
    376     private void setSharedElementState(View view, String name, Bundle transitionArgs,
    377             Matrix tempMatrix, RectF tempRect, int[] decorLoc) {
    378         Bundle sharedElementBundle = transitionArgs.getBundle(name);
    379         if (sharedElementBundle == null) {
    380             return;
    381         }
    382 
    383         if (view instanceof ImageView) {
    384             int scaleTypeInt = sharedElementBundle.getInt(KEY_SCALE_TYPE, -1);
    385             if (scaleTypeInt >= 0) {
    386                 ImageView imageView = (ImageView) view;
    387                 ImageView.ScaleType scaleType = SCALE_TYPE_VALUES[scaleTypeInt];
    388                 imageView.setScaleType(scaleType);
    389                 if (scaleType == ImageView.ScaleType.MATRIX) {
    390                     float[] matrixValues = sharedElementBundle.getFloatArray(KEY_IMAGE_MATRIX);
    391                     tempMatrix.setValues(matrixValues);
    392                     imageView.setImageMatrix(tempMatrix);
    393                 }
    394             }
    395         }
    396 
    397         float z = sharedElementBundle.getFloat(KEY_TRANSLATION_Z);
    398         view.setTranslationZ(z);
    399         float elevation = sharedElementBundle.getFloat(KEY_ELEVATION);
    400         view.setElevation(elevation);
    401 
    402         float left = sharedElementBundle.getFloat(KEY_SCREEN_LEFT);
    403         float top = sharedElementBundle.getFloat(KEY_SCREEN_TOP);
    404         float right = sharedElementBundle.getFloat(KEY_SCREEN_RIGHT);
    405         float bottom = sharedElementBundle.getFloat(KEY_SCREEN_BOTTOM);
    406 
    407         if (decorLoc != null) {
    408             left -= decorLoc[0];
    409             top -= decorLoc[1];
    410             right -= decorLoc[0];
    411             bottom -= decorLoc[1];
    412         } else {
    413             // Find the location in the view's parent
    414             getSharedElementParentMatrix(view, tempMatrix);
    415             tempRect.set(left, top, right, bottom);
    416             tempMatrix.mapRect(tempRect);
    417 
    418             float leftInParent = tempRect.left;
    419             float topInParent = tempRect.top;
    420 
    421             // Find the size of the view
    422             view.getInverseMatrix().mapRect(tempRect);
    423             float width = tempRect.width();
    424             float height = tempRect.height();
    425 
    426             // Now determine the offset due to view transform:
    427             view.setLeft(0);
    428             view.setTop(0);
    429             view.setRight(Math.round(width));
    430             view.setBottom(Math.round(height));
    431             tempRect.set(0, 0, width, height);
    432             view.getMatrix().mapRect(tempRect);
    433 
    434             ViewGroup parent = (ViewGroup) view.getParent();
    435             left = leftInParent - tempRect.left + parent.getScrollX();
    436             top = topInParent - tempRect.top + parent.getScrollY();
    437             right = left + width;
    438             bottom = top + height;
    439         }
    440 
    441         int x = Math.round(left);
    442         int y = Math.round(top);
    443         int width = Math.round(right) - x;
    444         int height = Math.round(bottom) - y;
    445         int widthSpec = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY);
    446         int heightSpec = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY);
    447         view.measure(widthSpec, heightSpec);
    448 
    449         view.layout(x, y, x + width, y + height);
    450     }
    451 
    452     protected void getSharedElementParentMatrix(View view, Matrix matrix) {
    453         // Find the location in the view's parent
    454         ViewGroup parent = (ViewGroup) view.getParent();
    455         matrix.reset();
    456         parent.transformMatrixToLocal(matrix);
    457     }
    458 
    459     protected ArrayList<SharedElementOriginalState> setSharedElementState(
    460             Bundle sharedElementState, final ArrayList<View> snapshots) {
    461         ArrayList<SharedElementOriginalState> originalImageState =
    462                 new ArrayList<SharedElementOriginalState>();
    463         if (sharedElementState != null) {
    464             Matrix tempMatrix = new Matrix();
    465             RectF tempRect = new RectF();
    466             final int numSharedElements = mSharedElements.size();
    467             for (int i = 0; i < numSharedElements; i++) {
    468                 View sharedElement = mSharedElements.get(i);
    469                 String name = mSharedElementNames.get(i);
    470                 SharedElementOriginalState originalState = getOldSharedElementState(sharedElement,
    471                         name, sharedElementState);
    472                 originalImageState.add(originalState);
    473                 setSharedElementState(sharedElement, name, sharedElementState,
    474                         tempMatrix, tempRect, null);
    475             }
    476         }
    477         if (mListener != null) {
    478             mListener.onSharedElementStart(mSharedElementNames, mSharedElements, snapshots);
    479         }
    480         return originalImageState;
    481     }
    482 
    483     protected void notifySharedElementEnd(ArrayList<View> snapshots) {
    484         if (mListener != null) {
    485             mListener.onSharedElementEnd(mSharedElementNames, mSharedElements, snapshots);
    486         }
    487     }
    488 
    489     protected void scheduleSetSharedElementEnd(final ArrayList<View> snapshots) {
    490         final View decorView = getDecor();
    491         if (decorView != null) {
    492             decorView.getViewTreeObserver().addOnPreDrawListener(
    493                     new ViewTreeObserver.OnPreDrawListener() {
    494                         @Override
    495                         public boolean onPreDraw() {
    496                             decorView.getViewTreeObserver().removeOnPreDrawListener(this);
    497                             notifySharedElementEnd(snapshots);
    498                             return true;
    499                         }
    500                     }
    501             );
    502         }
    503     }
    504 
    505     private static SharedElementOriginalState getOldSharedElementState(View view, String name,
    506             Bundle transitionArgs) {
    507 
    508         SharedElementOriginalState state = new SharedElementOriginalState();
    509         state.mLeft = view.getLeft();
    510         state.mTop = view.getTop();
    511         state.mRight = view.getRight();
    512         state.mBottom = view.getBottom();
    513         state.mMeasuredWidth = view.getMeasuredWidth();
    514         state.mMeasuredHeight = view.getMeasuredHeight();
    515         state.mTranslationZ = view.getTranslationZ();
    516         state.mElevation = view.getElevation();
    517         if (!(view instanceof ImageView)) {
    518             return state;
    519         }
    520         Bundle bundle = transitionArgs.getBundle(name);
    521         if (bundle == null) {
    522             return state;
    523         }
    524         int scaleTypeInt = bundle.getInt(KEY_SCALE_TYPE, -1);
    525         if (scaleTypeInt < 0) {
    526             return state;
    527         }
    528 
    529         ImageView imageView = (ImageView) view;
    530         state.mScaleType = imageView.getScaleType();
    531         if (state.mScaleType == ImageView.ScaleType.MATRIX) {
    532             state.mMatrix = new Matrix(imageView.getImageMatrix());
    533         }
    534         return state;
    535     }
    536 
    537     protected ArrayList<View> createSnapshots(Bundle state, Collection<String> names) {
    538         int numSharedElements = names.size();
    539         if (numSharedElements == 0) {
    540             return null;
    541         }
    542         ArrayList<View> snapshots = new ArrayList<View>(numSharedElements);
    543         Context context = getWindow().getContext();
    544         int[] decorLoc = new int[2];
    545         ViewGroup decorView = getDecor();
    546         if (decorView != null) {
    547             decorView.getLocationOnScreen(decorLoc);
    548         }
    549         for (String name: names) {
    550             Bundle sharedElementBundle = state.getBundle(name);
    551             if (sharedElementBundle != null) {
    552                 Parcelable parcelable = sharedElementBundle.getParcelable(KEY_SNAPSHOT);
    553                 View snapshot = null;
    554                 if (parcelable != null && mListener != null) {
    555                     snapshot = mListener.onCreateSnapshotView(context, parcelable);
    556                 }
    557                 if (snapshot != null) {
    558                     setSharedElementState(snapshot, name, state, null, null, decorLoc);
    559                 }
    560                 snapshots.add(snapshot);
    561             }
    562         }
    563         return snapshots;
    564     }
    565 
    566     protected static void setOriginalSharedElementState(ArrayList<View> sharedElements,
    567             ArrayList<SharedElementOriginalState> originalState) {
    568         for (int i = 0; i < originalState.size(); i++) {
    569             View view = sharedElements.get(i);
    570             SharedElementOriginalState state = originalState.get(i);
    571             if (view instanceof ImageView && state.mScaleType != null) {
    572                 ImageView imageView = (ImageView) view;
    573                 imageView.setScaleType(state.mScaleType);
    574                 if (state.mScaleType == ImageView.ScaleType.MATRIX) {
    575                   imageView.setImageMatrix(state.mMatrix);
    576                 }
    577             }
    578             view.setElevation(state.mElevation);
    579             view.setTranslationZ(state.mTranslationZ);
    580             int widthSpec = View.MeasureSpec.makeMeasureSpec(state.mMeasuredWidth,
    581                     View.MeasureSpec.EXACTLY);
    582             int heightSpec = View.MeasureSpec.makeMeasureSpec(state.mMeasuredHeight,
    583                     View.MeasureSpec.EXACTLY);
    584             view.measure(widthSpec, heightSpec);
    585             view.layout(state.mLeft, state.mTop, state.mRight, state.mBottom);
    586         }
    587     }
    588 
    589     protected Bundle captureSharedElementState() {
    590         Bundle bundle = new Bundle();
    591         RectF tempBounds = new RectF();
    592         Matrix tempMatrix = new Matrix();
    593         for (int i = 0; i < mSharedElements.size(); i++) {
    594             View sharedElement = mSharedElements.get(i);
    595             String name = mSharedElementNames.get(i);
    596             captureSharedElementState(sharedElement, name, bundle, tempMatrix, tempBounds);
    597         }
    598         return bundle;
    599     }
    600 
    601     protected void clearState() {
    602         // Clear the state so that we can't hold any references accidentally and leak memory.
    603         mWindow = null;
    604         mSharedElements.clear();
    605         mTransitioningViews = null;
    606         mOriginalAlphas.clear();
    607         mResultReceiver = null;
    608         mPendingTransition = null;
    609         mListener = null;
    610     }
    611 
    612     protected long getFadeDuration() {
    613         return getWindow().getTransitionBackgroundFadeDuration();
    614     }
    615 
    616     protected void hideViews(ArrayList<View> views) {
    617         int count = views.size();
    618         for (int i = 0; i < count; i++) {
    619             View view = views.get(i);
    620             if (!mOriginalAlphas.containsKey(view)) {
    621                 mOriginalAlphas.put(view, view.getAlpha());
    622             }
    623             view.setAlpha(0f);
    624         }
    625     }
    626 
    627     protected void showViews(ArrayList<View> views, boolean setTransitionAlpha) {
    628         int count = views.size();
    629         for (int i = 0; i < count; i++) {
    630             showView(views.get(i), setTransitionAlpha);
    631         }
    632     }
    633 
    634     private void showView(View view, boolean setTransitionAlpha) {
    635         Float alpha = mOriginalAlphas.remove(view);
    636         if (alpha != null) {
    637             view.setAlpha(alpha);
    638         }
    639         if (setTransitionAlpha) {
    640             view.setTransitionAlpha(1f);
    641         }
    642     }
    643 
    644     /**
    645      * Captures placement information for Views with a shared element name for
    646      * Activity Transitions.
    647      *
    648      * @param view           The View to capture the placement information for.
    649      * @param name           The shared element name in the target Activity to apply the placement
    650      *                       information for.
    651      * @param transitionArgs Bundle to store shared element placement information.
    652      * @param tempBounds     A temporary Rect for capturing the current location of views.
    653      */
    654     protected void captureSharedElementState(View view, String name, Bundle transitionArgs,
    655             Matrix tempMatrix, RectF tempBounds) {
    656         Bundle sharedElementBundle = new Bundle();
    657         tempMatrix.reset();
    658         view.transformMatrixToGlobal(tempMatrix);
    659         tempBounds.set(0, 0, view.getWidth(), view.getHeight());
    660         tempMatrix.mapRect(tempBounds);
    661 
    662         sharedElementBundle.putFloat(KEY_SCREEN_LEFT, tempBounds.left);
    663         sharedElementBundle.putFloat(KEY_SCREEN_RIGHT, tempBounds.right);
    664         sharedElementBundle.putFloat(KEY_SCREEN_TOP, tempBounds.top);
    665         sharedElementBundle.putFloat(KEY_SCREEN_BOTTOM, tempBounds.bottom);
    666         sharedElementBundle.putFloat(KEY_TRANSLATION_Z, view.getTranslationZ());
    667         sharedElementBundle.putFloat(KEY_ELEVATION, view.getElevation());
    668 
    669         Parcelable bitmap = null;
    670         if (mListener != null) {
    671             bitmap = mListener.onCaptureSharedElementSnapshot(view, tempMatrix, tempBounds);
    672         }
    673 
    674         if (bitmap != null) {
    675             sharedElementBundle.putParcelable(KEY_SNAPSHOT, bitmap);
    676         }
    677 
    678         if (view instanceof ImageView) {
    679             ImageView imageView = (ImageView) view;
    680             int scaleTypeInt = scaleTypeToInt(imageView.getScaleType());
    681             sharedElementBundle.putInt(KEY_SCALE_TYPE, scaleTypeInt);
    682             if (imageView.getScaleType() == ImageView.ScaleType.MATRIX) {
    683                 float[] matrix = new float[9];
    684                 imageView.getImageMatrix().getValues(matrix);
    685                 sharedElementBundle.putFloatArray(KEY_IMAGE_MATRIX, matrix);
    686             }
    687         }
    688 
    689         transitionArgs.putBundle(name, sharedElementBundle);
    690     }
    691 
    692 
    693     protected void startTransition(Runnable runnable) {
    694         if (mIsStartingTransition) {
    695             mPendingTransition = runnable;
    696         } else {
    697             mIsStartingTransition = true;
    698             runnable.run();
    699         }
    700     }
    701 
    702     protected void transitionStarted() {
    703         mIsStartingTransition = false;
    704     }
    705 
    706     protected void moveSharedElementsToOverlay() {
    707         if (!mWindow.getSharedElementsUseOverlay()) {
    708             return;
    709         }
    710         int numSharedElements = mSharedElements.size();
    711         ViewGroup decor = getDecor();
    712         if (decor != null) {
    713             boolean moveWithParent = moveSharedElementWithParent();
    714             for (int i = 0; i < numSharedElements; i++) {
    715                 View view = mSharedElements.get(i);
    716                 GhostView.addGhost(view, decor);
    717                 ViewGroup parent = (ViewGroup) view.getParent();
    718                 if (moveWithParent && !isInTransitionGroup(parent, decor)) {
    719                     GhostViewListeners listener = new GhostViewListeners(view, parent, decor);
    720                     parent.getViewTreeObserver().addOnPreDrawListener(listener);
    721                     mGhostViewListeners.add(listener);
    722                 }
    723             }
    724         }
    725     }
    726 
    727     protected boolean moveSharedElementWithParent() {
    728         return true;
    729     }
    730 
    731     public static boolean isInTransitionGroup(ViewParent viewParent, ViewGroup decor) {
    732         if (viewParent == decor || !(viewParent instanceof ViewGroup)) {
    733             return false;
    734         }
    735         ViewGroup parent = (ViewGroup) viewParent;
    736         if (parent.isTransitionGroup()) {
    737             return true;
    738         } else {
    739             return isInTransitionGroup(parent.getParent(), decor);
    740         }
    741     }
    742 
    743     protected void moveSharedElementsFromOverlay() {
    744         int numListeners = mGhostViewListeners.size();
    745         for (int i = 0; i < numListeners; i++) {
    746             GhostViewListeners listener = mGhostViewListeners.get(i);
    747             ViewGroup parent = (ViewGroup) listener.getView().getParent();
    748             parent.getViewTreeObserver().removeOnPreDrawListener(listener);
    749         }
    750         mGhostViewListeners.clear();
    751 
    752         if (mWindow == null || !mWindow.getSharedElementsUseOverlay()) {
    753             return;
    754         }
    755         ViewGroup decor = getDecor();
    756         if (decor != null) {
    757             ViewGroupOverlay overlay = decor.getOverlay();
    758             int count = mSharedElements.size();
    759             for (int i = 0; i < count; i++) {
    760                 View sharedElement = mSharedElements.get(i);
    761                 GhostView.removeGhost(sharedElement);
    762             }
    763         }
    764     }
    765 
    766     protected void setGhostVisibility(int visibility) {
    767         int numSharedElements = mSharedElements.size();
    768         for (int i = 0; i < numSharedElements; i++) {
    769             GhostView ghostView = GhostView.getGhost(mSharedElements.get(i));
    770             if (ghostView != null) {
    771                 ghostView.setVisibility(visibility);
    772             }
    773         }
    774     }
    775 
    776     protected void scheduleGhostVisibilityChange(final int visibility) {
    777         final View decorView = getDecor();
    778         if (decorView != null) {
    779             decorView.getViewTreeObserver()
    780                     .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
    781                         @Override
    782                         public boolean onPreDraw() {
    783                             decorView.getViewTreeObserver().removeOnPreDrawListener(this);
    784                             setGhostVisibility(visibility);
    785                             return true;
    786                         }
    787                     });
    788         }
    789     }
    790 
    791     protected class ContinueTransitionListener extends Transition.TransitionListenerAdapter {
    792         @Override
    793         public void onTransitionStart(Transition transition) {
    794             mIsStartingTransition = false;
    795             Runnable pending = mPendingTransition;
    796             mPendingTransition = null;
    797             if (pending != null) {
    798                 startTransition(pending);
    799             }
    800         }
    801     }
    802 
    803     private static int scaleTypeToInt(ImageView.ScaleType scaleType) {
    804         for (int i = 0; i < SCALE_TYPE_VALUES.length; i++) {
    805             if (scaleType == SCALE_TYPE_VALUES[i]) {
    806                 return i;
    807             }
    808         }
    809         return -1;
    810     }
    811 
    812     private static class FixedEpicenterCallback extends Transition.EpicenterCallback {
    813         private Rect mEpicenter;
    814 
    815         public void setEpicenter(Rect epicenter) { mEpicenter = epicenter; }
    816 
    817         @Override
    818         public Rect onGetEpicenter(Transition transition) {
    819             return mEpicenter;
    820         }
    821     }
    822 
    823     private static class GhostViewListeners implements ViewTreeObserver.OnPreDrawListener {
    824         private View mView;
    825         private ViewGroup mDecor;
    826         private View mParent;
    827         private Matrix mMatrix = new Matrix();
    828 
    829         public GhostViewListeners(View view, View parent, ViewGroup decor) {
    830             mView = view;
    831             mParent = parent;
    832             mDecor = decor;
    833         }
    834 
    835         public View getView() {
    836             return mView;
    837         }
    838 
    839         @Override
    840         public boolean onPreDraw() {
    841             GhostView ghostView = GhostView.getGhost(mView);
    842             if (ghostView == null) {
    843                 mParent.getViewTreeObserver().removeOnPreDrawListener(this);
    844             } else {
    845                 GhostView.calculateMatrix(mView, mDecor, mMatrix);
    846                 ghostView.setMatrix(mMatrix);
    847             }
    848             return true;
    849         }
    850     }
    851 
    852     static class SharedElementOriginalState {
    853         int mLeft;
    854         int mTop;
    855         int mRight;
    856         int mBottom;
    857         int mMeasuredWidth;
    858         int mMeasuredHeight;
    859         ImageView.ScaleType mScaleType;
    860         Matrix mMatrix;
    861         float mTranslationZ;
    862         float mElevation;
    863     }
    864 }
    865