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