EnterTransitionCoordinator.java revision b694e080546316a27d22eba759027f6cb0a24705
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.animation.Animator;
19import android.animation.AnimatorListenerAdapter;
20import android.animation.ObjectAnimator;
21import android.graphics.Matrix;
22import android.graphics.drawable.Drawable;
23import android.os.Bundle;
24import android.os.ResultReceiver;
25import android.text.TextUtils;
26import android.transition.Transition;
27import android.transition.TransitionManager;
28import android.util.ArrayMap;
29import android.view.View;
30import android.view.ViewGroup;
31import android.view.ViewGroupOverlay;
32import android.view.ViewTreeObserver;
33
34import java.util.ArrayList;
35
36/**
37 * This ActivityTransitionCoordinator is created by the Activity to manage
38 * the enter scene and shared element transfer into the Scene, either during
39 * launch of an Activity or returning from a launched Activity.
40 */
41class EnterTransitionCoordinator extends ActivityTransitionCoordinator {
42    private static final String TAG = "EnterTransitionCoordinator";
43
44    private static final int MIN_ANIMATION_FRAMES = 2;
45
46    private boolean mSharedElementTransitionStarted;
47    private Activity mActivity;
48    private boolean mHasStopped;
49    private boolean mIsCanceled;
50    private ObjectAnimator mBackgroundAnimator;
51    private boolean mIsExitTransitionComplete;
52    private boolean mIsReadyForTransition;
53    private Bundle mSharedElementsBundle;
54    private boolean mWasOpaque;
55    private boolean mAreViewsReady;
56    private boolean mIsViewsTransitionStarted;
57    private boolean mIsViewsTransitionComplete;
58    private boolean mIsSharedElementTransitionComplete;
59    private ArrayList<Matrix> mSharedElementParentMatrices;
60    private Transition mEnterViewsTransition;
61
62    public EnterTransitionCoordinator(Activity activity, ResultReceiver resultReceiver,
63            ArrayList<String> sharedElementNames, boolean isReturning) {
64        super(activity.getWindow(), sharedElementNames,
65                getListener(activity, isReturning), isReturning);
66        mActivity = activity;
67        setResultReceiver(resultReceiver);
68        prepareEnter();
69        Bundle resultReceiverBundle = new Bundle();
70        resultReceiverBundle.putParcelable(KEY_REMOTE_RECEIVER, this);
71        mResultReceiver.send(MSG_SET_REMOTE_RECEIVER, resultReceiverBundle);
72        final View decorView = getDecor();
73        if (decorView != null) {
74            decorView.getViewTreeObserver().addOnPreDrawListener(
75                    new ViewTreeObserver.OnPreDrawListener() {
76                        @Override
77                        public boolean onPreDraw() {
78                            if (mIsReadyForTransition) {
79                                decorView.getViewTreeObserver().removeOnPreDrawListener(this);
80                            }
81                            return mIsReadyForTransition;
82                        }
83                    });
84        }
85    }
86
87    public void viewInstancesReady(ArrayList<String> accepted, ArrayList<String> localNames,
88            ArrayList<View> localViews) {
89        boolean remap = false;
90        for (int i = 0; i < localViews.size(); i++) {
91            View view = localViews.get(i);
92            if (!TextUtils.equals(view.getTransitionName(), localNames.get(i))
93                    || !view.isAttachedToWindow()) {
94                remap = true;
95                break;
96            }
97        }
98        if (remap) {
99            triggerViewsReady(mapNamedElements(accepted, localNames));
100        } else {
101            triggerViewsReady(mapSharedElements(accepted, localViews));
102        }
103    }
104
105    public void namedViewsReady(ArrayList<String> accepted, ArrayList<String> localNames) {
106        triggerViewsReady(mapNamedElements(accepted, localNames));
107    }
108
109    public Transition getEnterViewsTransition() {
110        return mEnterViewsTransition;
111    }
112
113    @Override
114    protected void viewsReady(ArrayMap<String, View> sharedElements) {
115        super.viewsReady(sharedElements);
116        mIsReadyForTransition = true;
117        hideViews(mSharedElements);
118        if (getViewsTransition() != null && mTransitioningViews != null) {
119            hideViews(mTransitioningViews);
120        }
121        if (mIsReturning) {
122            sendSharedElementDestination();
123        } else {
124            setSharedElementMatrices();
125            moveSharedElementsToOverlay();
126        }
127        if (mSharedElementsBundle != null) {
128            onTakeSharedElements();
129        }
130    }
131
132    private void triggerViewsReady(final ArrayMap<String, View> sharedElements) {
133        if (mAreViewsReady) {
134            return;
135        }
136        mAreViewsReady = true;
137        // Ensure the views have been laid out before capturing the views -- we need the epicenter.
138        if (sharedElements.isEmpty() || !sharedElements.valueAt(0).isLayoutRequested()) {
139            viewsReady(sharedElements);
140        } else {
141            final View sharedElement = sharedElements.valueAt(0);
142            sharedElement.getViewTreeObserver()
143                    .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
144                @Override
145                public boolean onPreDraw() {
146                    sharedElement.getViewTreeObserver().removeOnPreDrawListener(this);
147                    viewsReady(sharedElements);
148                    return true;
149                }
150            });
151        }
152    }
153
154    private ArrayMap<String, View> mapNamedElements(ArrayList<String> accepted,
155            ArrayList<String> localNames) {
156        ArrayMap<String, View> sharedElements = new ArrayMap<String, View>();
157        ViewGroup decorView = getDecor();
158        if (decorView != null) {
159            decorView.findNamedViews(sharedElements);
160        }
161        if (accepted != null) {
162            for (int i = 0; i < localNames.size(); i++) {
163                String localName = localNames.get(i);
164                String acceptedName = accepted.get(i);
165                if (localName != null && !localName.equals(acceptedName)) {
166                    View view = sharedElements.remove(localName);
167                    if (view != null) {
168                        sharedElements.put(acceptedName, view);
169                    }
170                }
171            }
172        }
173        return sharedElements;
174    }
175
176    private void sendSharedElementDestination() {
177        boolean allReady;
178        final View decorView = getDecor();
179        if (allowOverlappingTransitions() && getEnterViewsTransition() != null) {
180            allReady = false;
181        } else if (decorView == null) {
182            allReady = true;
183        } else {
184            allReady = !decorView.isLayoutRequested();
185            if (allReady) {
186                for (int i = 0; i < mSharedElements.size(); i++) {
187                    if (mSharedElements.get(i).isLayoutRequested()) {
188                        allReady = false;
189                        break;
190                    }
191                }
192            }
193        }
194        if (allReady) {
195            Bundle state = captureSharedElementState();
196            setSharedElementMatrices();
197            moveSharedElementsToOverlay();
198            mResultReceiver.send(MSG_SHARED_ELEMENT_DESTINATION, state);
199        } else if (decorView != null) {
200            decorView.getViewTreeObserver()
201                    .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
202                        @Override
203                        public boolean onPreDraw() {
204                            decorView.getViewTreeObserver().removeOnPreDrawListener(this);
205                            if (mResultReceiver != null) {
206                                Bundle state = captureSharedElementState();
207                                setSharedElementMatrices();
208                                moveSharedElementsToOverlay();
209                                mResultReceiver.send(MSG_SHARED_ELEMENT_DESTINATION, state);
210                            }
211                            return true;
212                        }
213                    });
214        }
215        if (allowOverlappingTransitions()) {
216            startEnterTransitionOnly();
217        }
218    }
219
220    private static SharedElementCallback getListener(Activity activity, boolean isReturning) {
221        return isReturning ? activity.mExitTransitionListener : activity.mEnterTransitionListener;
222    }
223
224    @Override
225    protected void onReceiveResult(int resultCode, Bundle resultData) {
226        switch (resultCode) {
227            case MSG_TAKE_SHARED_ELEMENTS:
228                if (!mIsCanceled) {
229                    mSharedElementsBundle = resultData;
230                    onTakeSharedElements();
231                }
232                break;
233            case MSG_EXIT_TRANSITION_COMPLETE:
234                if (!mIsCanceled) {
235                    mIsExitTransitionComplete = true;
236                    if (mSharedElementTransitionStarted) {
237                        onRemoteExitTransitionComplete();
238                    }
239                }
240                break;
241            case MSG_CANCEL:
242                cancel();
243                break;
244        }
245    }
246
247    private void cancel() {
248        if (!mIsCanceled) {
249            mIsCanceled = true;
250            if (getViewsTransition() == null || mIsViewsTransitionStarted) {
251                showViews(mSharedElements, true);
252            } else if (mTransitioningViews != null) {
253                mTransitioningViews.addAll(mSharedElements);
254            }
255            mSharedElementNames.clear();
256            mSharedElements.clear();
257            mAllSharedElementNames.clear();
258            startSharedElementTransition(null);
259            onRemoteExitTransitionComplete();
260        }
261    }
262
263    public boolean isReturning() {
264        return mIsReturning;
265    }
266
267    protected void prepareEnter() {
268        mActivity.overridePendingTransition(0, 0);
269        if (!mIsReturning) {
270            mWasOpaque = mActivity.convertToTranslucent(null, null);
271            Drawable background = getDecor().getBackground();
272            if (background != null) {
273                getWindow().setBackgroundDrawable(null);
274                background = background.mutate();
275                background.setAlpha(0);
276                getWindow().setBackgroundDrawable(background);
277            }
278        } else {
279            mActivity = null; // all done with it now.
280        }
281    }
282
283    @Override
284    protected Transition getViewsTransition() {
285        if (mIsReturning) {
286            return getWindow().getReenterTransition();
287        } else {
288            return getWindow().getEnterTransition();
289        }
290    }
291
292    protected Transition getSharedElementTransition() {
293        if (mIsReturning) {
294            return getWindow().getSharedElementReenterTransition();
295        } else {
296            return getWindow().getSharedElementEnterTransition();
297        }
298    }
299
300    private void startSharedElementTransition(Bundle sharedElementState) {
301        ViewGroup decorView = getDecor();
302        if (decorView == null) {
303            return;
304        }
305        // Remove rejected shared elements
306        ArrayList<String> rejectedNames = new ArrayList<String>(mAllSharedElementNames);
307        rejectedNames.removeAll(mSharedElementNames);
308        ArrayList<View> rejectedSnapshots = createSnapshots(sharedElementState, rejectedNames);
309        mListener.onRejectSharedElements(rejectedSnapshots);
310        startRejectedAnimations(rejectedSnapshots);
311
312        // Now start shared element transition
313        ArrayList<View> sharedElementSnapshots = createSnapshots(sharedElementState,
314                mSharedElementNames);
315        showViews(mSharedElements, true);
316        scheduleSetSharedElementEnd(sharedElementSnapshots);
317        ArrayList<SharedElementOriginalState> originalImageViewState =
318                setSharedElementState(sharedElementState, sharedElementSnapshots);
319        requestLayoutForSharedElements();
320
321        boolean startEnterTransition = allowOverlappingTransitions() && !mIsReturning;
322        boolean startSharedElementTransition = true;
323        setGhostVisibility(View.INVISIBLE);
324        scheduleGhostVisibilityChange(View.INVISIBLE);
325        Transition transition = beginTransition(decorView, startEnterTransition,
326                startSharedElementTransition);
327        scheduleGhostVisibilityChange(View.VISIBLE);
328        setGhostVisibility(View.VISIBLE);
329
330        if (startEnterTransition) {
331            startEnterTransition(transition);
332        }
333
334        setOriginalSharedElementState(mSharedElements, originalImageViewState);
335
336        if (mResultReceiver != null) {
337            // We can't trust that the view will disappear on the same frame that the shared
338            // element appears here. Assure that we get at least 2 frames for double-buffering.
339            decorView.postOnAnimation(new Runnable() {
340                int mAnimations;
341
342                @Override
343                public void run() {
344                    if (mAnimations++ < MIN_ANIMATION_FRAMES) {
345                        View decorView = getDecor();
346                        if (decorView != null) {
347                            decorView.postOnAnimation(this);
348                        }
349                    } else if (mResultReceiver != null) {
350                        mResultReceiver.send(MSG_HIDE_SHARED_ELEMENTS, null);
351                        mResultReceiver = null; // all done sending messages.
352                    }
353                }
354            });
355        }
356    }
357
358    private void onTakeSharedElements() {
359        if (!mIsReadyForTransition || mSharedElementsBundle == null) {
360            return;
361        }
362        final Bundle sharedElementState = mSharedElementsBundle;
363        mSharedElementsBundle = null;
364        final View decorView = getDecor();
365        if (decorView != null) {
366            decorView.getViewTreeObserver()
367                    .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
368                        @Override
369                        public boolean onPreDraw() {
370                            decorView.getViewTreeObserver().removeOnPreDrawListener(this);
371                            startTransition(new Runnable() {
372                                @Override
373                                public void run() {
374                                    startSharedElementTransition(sharedElementState);
375                                }
376                            });
377                            return false;
378                        }
379                    });
380            decorView.invalidate();
381        }
382    }
383
384    private void requestLayoutForSharedElements() {
385        int numSharedElements = mSharedElements.size();
386        for (int i = 0; i < numSharedElements; i++) {
387            mSharedElements.get(i).requestLayout();
388        }
389    }
390
391    private Transition beginTransition(ViewGroup decorView, boolean startEnterTransition,
392            boolean startSharedElementTransition) {
393        Transition sharedElementTransition = null;
394        if (startSharedElementTransition) {
395            if (!mSharedElementNames.isEmpty()) {
396                sharedElementTransition = configureTransition(getSharedElementTransition(), false);
397            }
398            if (sharedElementTransition == null) {
399                sharedElementTransitionStarted();
400                sharedElementTransitionComplete();
401            } else {
402                sharedElementTransition.addListener(new Transition.TransitionListenerAdapter() {
403                    @Override
404                    public void onTransitionStart(Transition transition) {
405                        sharedElementTransitionStarted();
406                    }
407
408                    @Override
409                    public void onTransitionEnd(Transition transition) {
410                        transition.removeListener(this);
411                        sharedElementTransitionComplete();
412                    }
413                });
414            }
415        }
416        Transition viewsTransition = null;
417        if (startEnterTransition) {
418            mIsViewsTransitionStarted = true;
419            if (mTransitioningViews != null && !mTransitioningViews.isEmpty()) {
420                viewsTransition = configureTransition(getViewsTransition(), true);
421                if (viewsTransition != null && !mIsReturning) {
422                    stripOffscreenViews();
423                }
424            }
425            if (viewsTransition == null) {
426                viewTransitionComplete();
427            } else {
428                viewsTransition.forceVisibility(View.INVISIBLE, true);
429                final ArrayList<View> transitioningViews = mTransitioningViews;
430                viewsTransition.addListener(new ContinueTransitionListener() {
431                    @Override
432                    public void onTransitionStart(Transition transition) {
433                        mEnterViewsTransition = transition;
434                        if (transitioningViews != null) {
435                            showViews(transitioningViews, false);
436                        }
437                        super.onTransitionStart(transition);
438                    }
439
440                    @Override
441                    public void onTransitionEnd(Transition transition) {
442                        mEnterViewsTransition = null;
443                        transition.removeListener(this);
444                        viewTransitionComplete();
445                        super.onTransitionEnd(transition);
446                    }
447                });
448            }
449        }
450
451        Transition transition = mergeTransitions(sharedElementTransition, viewsTransition);
452        if (transition != null) {
453            transition.addListener(new ContinueTransitionListener());
454            TransitionManager.beginDelayedTransition(decorView, transition);
455            if (startSharedElementTransition && !mSharedElementNames.isEmpty()) {
456                mSharedElements.get(0).invalidate();
457            } else if (startEnterTransition && mTransitioningViews != null &&
458                    !mTransitioningViews.isEmpty()) {
459                mTransitioningViews.get(0).invalidate();
460            }
461        } else {
462            transitionStarted();
463        }
464        return transition;
465    }
466
467    private void viewTransitionComplete() {
468        mIsViewsTransitionComplete = true;
469        if (mIsSharedElementTransitionComplete) {
470            moveSharedElementsFromOverlay();
471        }
472    }
473
474    private void sharedElementTransitionComplete() {
475        mIsSharedElementTransitionComplete = true;
476        if (mIsViewsTransitionComplete) {
477            moveSharedElementsFromOverlay();
478        }
479    }
480
481    private void sharedElementTransitionStarted() {
482        mSharedElementTransitionStarted = true;
483        if (mIsExitTransitionComplete) {
484            send(MSG_EXIT_TRANSITION_COMPLETE, null);
485        }
486    }
487
488    private void startEnterTransition(Transition transition) {
489        ViewGroup decorView = getDecor();
490        if (!mIsReturning && decorView != null) {
491            Drawable background = decorView.getBackground();
492            if (background != null) {
493                background = background.mutate();
494                getWindow().setBackgroundDrawable(background);
495                mBackgroundAnimator = ObjectAnimator.ofInt(background, "alpha", 255);
496                mBackgroundAnimator.setDuration(getFadeDuration());
497                mBackgroundAnimator.addListener(new AnimatorListenerAdapter() {
498                    @Override
499                    public void onAnimationEnd(Animator animation) {
500                        makeOpaque();
501                    }
502                });
503                mBackgroundAnimator.start();
504            } else if (transition != null) {
505                transition.addListener(new Transition.TransitionListenerAdapter() {
506                    @Override
507                    public void onTransitionEnd(Transition transition) {
508                        transition.removeListener(this);
509                        makeOpaque();
510                    }
511                });
512            } else {
513                makeOpaque();
514            }
515        }
516    }
517
518    public void stop() {
519        makeOpaque();
520        mIsCanceled = true;
521        mResultReceiver = null;
522        if (mBackgroundAnimator != null) {
523            mBackgroundAnimator.end();
524            mBackgroundAnimator = null;
525        }
526        mActivity = null;
527        moveSharedElementsFromOverlay();
528        clearState();
529    }
530
531    public void cancelEnter() {
532        setGhostVisibility(View.INVISIBLE);
533        mHasStopped = true;
534        mIsCanceled = true;
535        mResultReceiver = null;
536        if (mBackgroundAnimator != null) {
537            mBackgroundAnimator.cancel();
538            mBackgroundAnimator = null;
539        }
540        mActivity = null;
541        clearState();
542    }
543
544    private void makeOpaque() {
545        if (!mHasStopped && mActivity != null) {
546            if (mWasOpaque) {
547                mActivity.convertFromTranslucent();
548            }
549            mActivity = null;
550        }
551    }
552
553    private boolean allowOverlappingTransitions() {
554        return mIsReturning ? getWindow().getAllowExitTransitionOverlap()
555                : getWindow().getAllowEnterTransitionOverlap();
556    }
557
558    private void startRejectedAnimations(final ArrayList<View> rejectedSnapshots) {
559        if (rejectedSnapshots == null || rejectedSnapshots.isEmpty()) {
560            return;
561        }
562        final ViewGroup decorView = getDecor();
563        if (decorView != null) {
564            ViewGroupOverlay overlay = decorView.getOverlay();
565            ObjectAnimator animator = null;
566            int numRejected = rejectedSnapshots.size();
567            for (int i = 0; i < numRejected; i++) {
568                View snapshot = rejectedSnapshots.get(i);
569                overlay.add(snapshot);
570                animator = ObjectAnimator.ofFloat(snapshot, View.ALPHA, 1, 0);
571                animator.start();
572            }
573            animator.addListener(new AnimatorListenerAdapter() {
574                @Override
575                public void onAnimationEnd(Animator animation) {
576                    ViewGroupOverlay overlay = decorView.getOverlay();
577                    int numRejected = rejectedSnapshots.size();
578                    for (int i = 0; i < numRejected; i++) {
579                        overlay.remove(rejectedSnapshots.get(i));
580                    }
581                }
582            });
583        }
584    }
585
586    protected void onRemoteExitTransitionComplete() {
587        if (!allowOverlappingTransitions()) {
588            startEnterTransitionOnly();
589        }
590    }
591
592    private void startEnterTransitionOnly() {
593        startTransition(new Runnable() {
594            @Override
595            public void run() {
596                boolean startEnterTransition = true;
597                boolean startSharedElementTransition = false;
598                ViewGroup decorView = getDecor();
599                if (decorView != null) {
600                    Transition transition = beginTransition(decorView, startEnterTransition,
601                            startSharedElementTransition);
602                    startEnterTransition(transition);
603                }
604            }
605        });
606    }
607
608    private void setSharedElementMatrices() {
609        int numSharedElements = mSharedElements.size();
610        if (numSharedElements > 0) {
611            mSharedElementParentMatrices = new ArrayList<Matrix>(numSharedElements);
612        }
613        for (int i = 0; i < numSharedElements; i++) {
614            View view = mSharedElements.get(i);
615
616            // Find the location in the view's parent
617            ViewGroup parent = (ViewGroup) view.getParent();
618            Matrix matrix = new Matrix();
619            parent.transformMatrixToLocal(matrix);
620
621            mSharedElementParentMatrices.add(matrix);
622        }
623    }
624
625    @Override
626    protected void getSharedElementParentMatrix(View view, Matrix matrix) {
627        int index = mSharedElementParentMatrices == null ? -1 : mSharedElements.indexOf(view);
628        if (index < 0) {
629            super.getSharedElementParentMatrix(view, matrix);
630        } else {
631            matrix.set(mSharedElementParentMatrices.get(index));
632        }
633    }
634}
635