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