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