EnterTransitionCoordinator.java revision 8881502da5b319e139ab85c1825d2d2d239356a1
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.Handler;
25import android.os.Message;
26import android.os.ResultReceiver;
27import android.transition.Transition;
28import android.transition.TransitionManager;
29import android.util.ArrayMap;
30import android.util.Pair;
31import android.view.View;
32import android.view.ViewGroup;
33import android.view.ViewGroupOverlay;
34import android.view.ViewTreeObserver;
35import android.widget.ImageView;
36
37import java.util.ArrayList;
38
39/**
40 * This ActivityTransitionCoordinator is created by the Activity to manage
41 * the enter scene and shared element transfer into the Scene, either during
42 * launch of an Activity or returning from a launched Activity.
43 */
44class EnterTransitionCoordinator extends ActivityTransitionCoordinator {
45    private static final String TAG = "EnterTransitionCoordinator";
46
47    private static final long MAX_WAIT_MS = 1000;
48
49    private boolean mSharedElementTransitionStarted;
50    private Activity mActivity;
51    private boolean mHasStopped;
52    private Handler mHandler;
53    private boolean mIsCanceled;
54    private ObjectAnimator mBackgroundAnimator;
55    private boolean mIsExitTransitionComplete;
56    private boolean mIsReadyForTransition;
57    private Bundle mSharedElementsBundle;
58    private boolean mWasOpaque;
59
60    public EnterTransitionCoordinator(Activity activity, ResultReceiver resultReceiver,
61            ArrayList<String> sharedElementNames, boolean isReturning) {
62        super(activity.getWindow(), sharedElementNames,
63                getListener(activity, isReturning), isReturning);
64        mActivity = activity;
65        setResultReceiver(resultReceiver);
66        prepareEnter();
67        Bundle resultReceiverBundle = new Bundle();
68        resultReceiverBundle.putParcelable(KEY_REMOTE_RECEIVER, this);
69        mResultReceiver.send(MSG_SET_REMOTE_RECEIVER, resultReceiverBundle);
70        getDecor().getViewTreeObserver().addOnPreDrawListener(
71                new ViewTreeObserver.OnPreDrawListener() {
72                    @Override
73                    public boolean onPreDraw() {
74                        if (mIsReadyForTransition) {
75                            getDecor().getViewTreeObserver().removeOnPreDrawListener(this);
76                        }
77                        return mIsReadyForTransition;
78                    }
79                });
80    }
81
82    public void viewInstancesReady(ArrayList<String> accepted, ArrayList<View> localViews) {
83        if (mIsReadyForTransition) {
84            return;
85        }
86        viewsReady(mapSharedElements(accepted, localViews));
87    }
88
89    public void namedViewsReady(ArrayList<String> accepted, ArrayList<String> localNames) {
90        if (mIsReadyForTransition) {
91            return;
92        }
93
94        viewsReady(mapNamedElements(accepted, localNames));
95    }
96
97    @Override
98    protected void viewsReady(ArrayMap<String, View> sharedElements) {
99        super.viewsReady(sharedElements);
100        mIsReadyForTransition = true;
101        if (mIsReturning) {
102            mHandler = new Handler() {
103                @Override
104                public void handleMessage(Message msg) {
105                    cancel();
106                }
107            };
108            mHandler.sendEmptyMessageDelayed(MSG_CANCEL, MAX_WAIT_MS);
109            send(MSG_SEND_SHARED_ELEMENT_DESTINATION, null);
110        }
111        setViewVisibility(mSharedElements, View.INVISIBLE);
112        if (getViewsTransition() != null) {
113            setViewVisibility(mTransitioningViews, View.INVISIBLE);
114        }
115        if (mSharedElementsBundle != null) {
116            onTakeSharedElements();
117        }
118    }
119
120    private ArrayMap<String, View> mapNamedElements(ArrayList<String> accepted,
121            ArrayList<String> localNames) {
122        ArrayMap<String, View> sharedElements = new ArrayMap<String, View>();
123        getDecor().findNamedViews(sharedElements);
124        if (accepted != null) {
125            for (int i = 0; i < localNames.size(); i++) {
126                String localName = localNames.get(i);
127                String acceptedName = accepted.get(i);
128                if (localName != null && !localName.equals(acceptedName)) {
129                    View view = sharedElements.remove(localName);
130                    if (view != null) {
131                        sharedElements.put(acceptedName, view);
132                    }
133                }
134            }
135        }
136        return sharedElements;
137    }
138
139    private void sendSharedElementDestination() {
140        ViewGroup decor = getDecor();
141        boolean allReady = !decor.isLayoutRequested();
142        if (allReady) {
143            for (int i = 0; i < mSharedElements.size(); i++) {
144                if (mSharedElements.get(i).isLayoutRequested()) {
145                    allReady = false;
146                    break;
147                }
148            }
149        }
150        if (allReady) {
151            Bundle state = captureSharedElementState();
152            mResultReceiver.send(MSG_SHARED_ELEMENT_DESTINATION, state);
153        } else {
154            getDecor().getViewTreeObserver()
155                    .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
156                        @Override
157                        public boolean onPreDraw() {
158                            getDecor().getViewTreeObserver().removeOnPreDrawListener(this);
159                            Bundle state = captureSharedElementState();
160                            mResultReceiver.send(MSG_SHARED_ELEMENT_DESTINATION, state);
161                            return true;
162                        }
163                    });
164        }
165        if (allowOverlappingTransitions()) {
166            startEnterTransitionOnly();
167        }
168    }
169
170    private static SharedElementListener getListener(Activity activity, boolean isReturning) {
171        return isReturning ? activity.mExitTransitionListener : activity.mEnterTransitionListener;
172    }
173
174    @Override
175    protected void onReceiveResult(int resultCode, Bundle resultData) {
176        switch (resultCode) {
177            case MSG_TAKE_SHARED_ELEMENTS:
178                if (!mIsCanceled) {
179                    if (mHandler != null) {
180                        mHandler.removeMessages(MSG_CANCEL);
181                    }
182                    mSharedElementsBundle = resultData;
183                    onTakeSharedElements();
184                }
185                break;
186            case MSG_EXIT_TRANSITION_COMPLETE:
187                if (!mIsCanceled) {
188                    mIsExitTransitionComplete = true;
189                    if (mSharedElementTransitionStarted) {
190                        onRemoteExitTransitionComplete();
191                    }
192                }
193                break;
194            case MSG_CANCEL:
195                cancel();
196                break;
197            case MSG_SEND_SHARED_ELEMENT_DESTINATION:
198                sendSharedElementDestination();
199                break;
200        }
201    }
202
203    private void cancel() {
204        if (!mIsCanceled) {
205            mIsCanceled = true;
206            if (getViewsTransition() == null) {
207                setViewVisibility(mSharedElements, View.VISIBLE);
208            } else {
209                mTransitioningViews.addAll(mSharedElements);
210            }
211            mSharedElementNames.clear();
212            mSharedElements.clear();
213            mAllSharedElementNames.clear();
214            startSharedElementTransition(null);
215            onRemoteExitTransitionComplete();
216        }
217    }
218
219    public boolean isReturning() {
220        return mIsReturning;
221    }
222
223    protected void prepareEnter() {
224        mActivity.overridePendingTransition(0, 0);
225        if (!mIsReturning) {
226            mWasOpaque = mActivity.convertToTranslucent(null, null);
227            Drawable background = getDecor().getBackground();
228            if (background != null) {
229                getWindow().setBackgroundDrawable(null);
230                background = background.mutate();
231                background.setAlpha(0);
232                getWindow().setBackgroundDrawable(background);
233            }
234        } else {
235            mActivity = null; // all done with it now.
236        }
237    }
238
239    @Override
240    protected Transition getViewsTransition() {
241        if (mIsReturning) {
242            return getWindow().getExitTransition();
243        } else {
244            return getWindow().getEnterTransition();
245        }
246    }
247
248    protected Transition getSharedElementTransition() {
249        if (mIsReturning) {
250            return getWindow().getSharedElementExitTransition();
251        } else {
252            return getWindow().getSharedElementEnterTransition();
253        }
254    }
255
256    private void startSharedElementTransition(Bundle sharedElementState) {
257        // Remove rejected shared elements
258        ArrayList<String> rejectedNames = new ArrayList<String>(mAllSharedElementNames);
259        rejectedNames.removeAll(mSharedElementNames);
260        ArrayList<View> rejectedSnapshots = createSnapshots(sharedElementState, rejectedNames);
261        mListener.handleRejectedSharedElements(rejectedSnapshots);
262        startRejectedAnimations(rejectedSnapshots);
263
264        // Now start shared element transition
265        ArrayList<View> sharedElementSnapshots = createSnapshots(sharedElementState,
266                mSharedElementNames);
267        setViewVisibility(mSharedElements, View.VISIBLE);
268        ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>> originalImageViewState =
269                setSharedElementState(sharedElementState, sharedElementSnapshots);
270        requestLayoutForSharedElements();
271
272        boolean startEnterTransition = allowOverlappingTransitions() && !mIsReturning;
273        boolean startSharedElementTransition = true;
274        Transition transition = beginTransition(startEnterTransition, startSharedElementTransition);
275
276        if (startEnterTransition) {
277            startEnterTransition(transition);
278        }
279
280        setOriginalImageViewState(originalImageViewState);
281
282        if (mResultReceiver != null) {
283            mResultReceiver.send(MSG_HIDE_SHARED_ELEMENTS, null);
284        }
285        mResultReceiver = null; // all done sending messages.
286    }
287
288    @Override
289    protected void stripOffscreenViews() {
290        setViewVisibility(mTransitioningViews, View.VISIBLE);
291        super.stripOffscreenViews();
292        setViewVisibility(mTransitioningViews, View.INVISIBLE);
293    }
294
295    private void onTakeSharedElements() {
296        if (!mIsReadyForTransition || mSharedElementsBundle == null) {
297            return;
298        }
299        final Bundle sharedElementState = mSharedElementsBundle;
300        mSharedElementsBundle = null;
301        getDecor().getViewTreeObserver()
302                .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
303                    @Override
304                    public boolean onPreDraw() {
305                        getDecor().getViewTreeObserver().removeOnPreDrawListener(this);
306                        startTransition(new Runnable() {
307                            @Override
308                            public void run() {
309                                startSharedElementTransition(sharedElementState);
310                            }
311                        });
312                        return false;
313                    }
314                });
315        getDecor().invalidate();
316    }
317
318    private void requestLayoutForSharedElements() {
319        int numSharedElements = mSharedElements.size();
320        for (int i = 0; i < numSharedElements; i++) {
321            mSharedElements.get(i).requestLayout();
322        }
323    }
324
325    private Transition beginTransition(boolean startEnterTransition,
326            boolean startSharedElementTransition) {
327        Transition sharedElementTransition = null;
328        if (startSharedElementTransition && !mSharedElementNames.isEmpty()) {
329            sharedElementTransition = configureTransition(getSharedElementTransition(), false);
330        }
331        Transition viewsTransition = null;
332        if (startEnterTransition && !mTransitioningViews.isEmpty()) {
333            viewsTransition = configureTransition(getViewsTransition(), true);
334            if (viewsTransition != null) {
335                stripOffscreenViews();
336            }
337        }
338
339        Transition transition = mergeTransitions(sharedElementTransition, viewsTransition);
340        if (startSharedElementTransition) {
341            if (transition == null) {
342                sharedElementTransitionStarted();
343            } else {
344                transition.addListener(new ContinueTransitionListener() {
345                    @Override
346                    public void onTransitionStart(Transition transition) {
347                        super.onTransitionStart(transition);
348                        transition.removeListener(this);
349                        sharedElementTransitionStarted();
350                    }
351                });
352            }
353        }
354        if (transition != null) {
355            if (sharedElementTransition == null) {
356                transition.addListener(new ContinueTransitionListener());
357            }
358            TransitionManager.beginDelayedTransition(getDecor(), transition);
359            if (startSharedElementTransition && !mSharedElementNames.isEmpty()) {
360                mSharedElements.get(0).invalidate();
361            } else if (startEnterTransition && !mTransitioningViews.isEmpty()) {
362                mTransitioningViews.get(0).invalidate();
363            }
364        } else {
365            transitionStarted();
366        }
367        return transition;
368    }
369
370    private void sharedElementTransitionStarted() {
371        mSharedElementTransitionStarted = true;
372        if (mIsExitTransitionComplete) {
373            send(MSG_EXIT_TRANSITION_COMPLETE, null);
374        }
375    }
376
377    private void startEnterTransition(Transition transition) {
378        setViewVisibility(mTransitioningViews, View.VISIBLE);
379        if (!mIsReturning) {
380            Drawable background = getDecor().getBackground();
381            if (background != null) {
382                background = background.mutate();
383                mBackgroundAnimator = ObjectAnimator.ofInt(background, "alpha", 255);
384                mBackgroundAnimator.setDuration(getFadeDuration());
385                mBackgroundAnimator.addListener(new AnimatorListenerAdapter() {
386                    @Override
387                    public void onAnimationEnd(Animator animation) {
388                        makeOpaque();
389                    }
390                });
391                mBackgroundAnimator.start();
392            } else if (transition != null) {
393                transition.addListener(new Transition.TransitionListenerAdapter() {
394                    @Override
395                    public void onTransitionEnd(Transition transition) {
396                        transition.removeListener(this);
397                        makeOpaque();
398                    }
399                });
400            } else {
401                makeOpaque();
402            }
403        }
404    }
405
406    public void stop() {
407        makeOpaque();
408        mHasStopped = true;
409        mIsCanceled = true;
410        mResultReceiver = null;
411        if (mBackgroundAnimator != null) {
412            mBackgroundAnimator.cancel();
413            mBackgroundAnimator = null;
414        }
415    }
416
417    private void makeOpaque() {
418        if (!mHasStopped && mActivity != null) {
419            if (mWasOpaque) {
420                mActivity.convertFromTranslucent();
421            }
422            mActivity = null;
423        }
424    }
425
426    private boolean allowOverlappingTransitions() {
427        return mIsReturning ? getWindow().getAllowExitTransitionOverlap()
428                : getWindow().getAllowEnterTransitionOverlap();
429    }
430
431    private void startRejectedAnimations(final ArrayList<View> rejectedSnapshots) {
432        if (rejectedSnapshots == null || rejectedSnapshots.isEmpty()) {
433            return;
434        }
435        ViewGroupOverlay overlay = getDecor().getOverlay();
436        ObjectAnimator animator = null;
437        int numRejected = rejectedSnapshots.size();
438        for (int i = 0; i < numRejected; i++) {
439            View snapshot = rejectedSnapshots.get(i);
440            overlay.add(snapshot);
441            animator = ObjectAnimator.ofFloat(snapshot, View.ALPHA, 1, 0);
442            animator.start();
443        }
444        animator.addListener(new AnimatorListenerAdapter() {
445            @Override
446            public void onAnimationEnd(Animator animation) {
447                ViewGroupOverlay overlay = getDecor().getOverlay();
448                int numRejected = rejectedSnapshots.size();
449                for (int i = 0; i < numRejected; i++) {
450                    overlay.remove(rejectedSnapshots.get(i));
451                }
452            }
453        });
454    }
455
456    protected void onRemoteExitTransitionComplete() {
457        if (!allowOverlappingTransitions()) {
458            startEnterTransitionOnly();
459        }
460    }
461
462    private void startEnterTransitionOnly() {
463        startTransition(new Runnable() {
464            @Override
465            public void run() {
466                boolean startEnterTransition = true;
467                boolean startSharedElementTransition = false;
468                Transition transition = beginTransition(startEnterTransition,
469                        startSharedElementTransition);
470                startEnterTransition(transition);
471            }
472        });
473    }
474}
475