EnterTransitionCoordinator.java revision 3cc716c445688228311bead8641c467e91effab9
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 viewsReady(ArrayList<String> accepted, ArrayList<String> localNames) {
83        if (mIsReadyForTransition) {
84            return;
85        }
86        super.viewsReady(accepted, localNames);
87
88        mIsReadyForTransition = true;
89        if (mIsReturning) {
90            mHandler = new Handler() {
91                @Override
92                public void handleMessage(Message msg) {
93                    cancel();
94                }
95            };
96            mHandler.sendEmptyMessageDelayed(MSG_CANCEL, MAX_WAIT_MS);
97            send(MSG_SEND_SHARED_ELEMENT_DESTINATION, null);
98        }
99        setViewVisibility(mSharedElements, View.INVISIBLE);
100        if (getViewsTransition() != null) {
101            setViewVisibility(mTransitioningViews, View.INVISIBLE);
102        }
103        if (mSharedElementsBundle != null) {
104            onTakeSharedElements();
105        }
106    }
107
108    private void sendSharedElementDestination() {
109        ViewGroup decor = getDecor();
110        boolean allReady = !decor.isLayoutRequested();
111        if (allReady) {
112            for (int i = 0; i < mSharedElements.size(); i++) {
113                if (mSharedElements.get(i).isLayoutRequested()) {
114                    allReady = false;
115                    break;
116                }
117            }
118        }
119        if (allReady) {
120            Bundle state = captureSharedElementState();
121            mResultReceiver.send(MSG_SHARED_ELEMENT_DESTINATION, state);
122        } else {
123            getDecor().getViewTreeObserver()
124                    .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
125                        @Override
126                        public boolean onPreDraw() {
127                            getDecor().getViewTreeObserver().removeOnPreDrawListener(this);
128                            Bundle state = captureSharedElementState();
129                            mResultReceiver.send(MSG_SHARED_ELEMENT_DESTINATION, state);
130                            return true;
131                        }
132                    });
133        }
134        if (allowOverlappingTransitions()) {
135            startEnterTransitionOnly();
136        }
137    }
138
139    private static SharedElementListener getListener(Activity activity, boolean isReturning) {
140        return isReturning ? activity.mExitTransitionListener : activity.mEnterTransitionListener;
141    }
142
143    @Override
144    protected void onReceiveResult(int resultCode, Bundle resultData) {
145        switch (resultCode) {
146            case MSG_TAKE_SHARED_ELEMENTS:
147                if (!mIsCanceled) {
148                    if (mHandler != null) {
149                        mHandler.removeMessages(MSG_CANCEL);
150                    }
151                    mSharedElementsBundle = resultData;
152                    onTakeSharedElements();
153                }
154                break;
155            case MSG_EXIT_TRANSITION_COMPLETE:
156                if (!mIsCanceled) {
157                    mIsExitTransitionComplete = true;
158                    if (mSharedElementTransitionStarted) {
159                        onRemoteExitTransitionComplete();
160                    }
161                }
162                break;
163            case MSG_CANCEL:
164                cancel();
165                break;
166            case MSG_SEND_SHARED_ELEMENT_DESTINATION:
167                sendSharedElementDestination();
168                break;
169        }
170    }
171
172    private void cancel() {
173        if (!mIsCanceled) {
174            mIsCanceled = true;
175            if (getViewsTransition() == null) {
176                setViewVisibility(mSharedElements, View.VISIBLE);
177            } else {
178                mTransitioningViews.addAll(mSharedElements);
179            }
180            mSharedElementNames.clear();
181            mSharedElements.clear();
182            mAllSharedElementNames.clear();
183            startSharedElementTransition(null);
184            onRemoteExitTransitionComplete();
185        }
186    }
187
188    public boolean isReturning() {
189        return mIsReturning;
190    }
191
192    protected void prepareEnter() {
193        mActivity.overridePendingTransition(0, 0);
194        if (!mIsReturning) {
195            mWasOpaque = mActivity.convertToTranslucent(null, null);
196            Drawable background = getDecor().getBackground();
197            if (background != null) {
198                getWindow().setBackgroundDrawable(null);
199                background = background.mutate();
200                background.setAlpha(0);
201                getWindow().setBackgroundDrawable(background);
202            }
203        } else {
204            mActivity = null; // all done with it now.
205        }
206    }
207
208    @Override
209    protected Transition getViewsTransition() {
210        if (mIsReturning) {
211            return getWindow().getExitTransition();
212        } else {
213            return getWindow().getEnterTransition();
214        }
215    }
216
217    protected Transition getSharedElementTransition() {
218        if (mIsReturning) {
219            return getWindow().getSharedElementExitTransition();
220        } else {
221            return getWindow().getSharedElementEnterTransition();
222        }
223    }
224
225    private void startSharedElementTransition(Bundle sharedElementState) {
226        setEpicenter();
227        // Remove rejected shared elements
228        ArrayList<String> rejectedNames = new ArrayList<String>(mAllSharedElementNames);
229        rejectedNames.removeAll(mSharedElementNames);
230        ArrayList<View> rejectedSnapshots = createSnapshots(sharedElementState, rejectedNames);
231        mListener.handleRejectedSharedElements(rejectedSnapshots);
232        startRejectedAnimations(rejectedSnapshots);
233
234        // Now start shared element transition
235        ArrayList<View> sharedElementSnapshots = createSnapshots(sharedElementState,
236                mSharedElementNames);
237        setViewVisibility(mSharedElements, View.VISIBLE);
238        ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>> originalImageViewState =
239                setSharedElementState(sharedElementState, sharedElementSnapshots);
240        requestLayoutForSharedElements();
241
242        boolean startEnterTransition = allowOverlappingTransitions() && !mIsReturning;
243        boolean startSharedElementTransition = true;
244        Transition transition = beginTransition(startEnterTransition, startSharedElementTransition);
245
246        if (startEnterTransition) {
247            startEnterTransition(transition);
248        }
249
250        setOriginalImageViewState(originalImageViewState);
251
252        if (mResultReceiver != null) {
253            mResultReceiver.send(MSG_HIDE_SHARED_ELEMENTS, null);
254        }
255        mResultReceiver = null; // all done sending messages.
256    }
257
258    private void onTakeSharedElements() {
259        if (!mIsReadyForTransition || mSharedElementsBundle == null) {
260            return;
261        }
262        final Bundle sharedElementState = mSharedElementsBundle;
263        mSharedElementsBundle = null;
264        getDecor().getViewTreeObserver()
265                .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
266                    @Override
267                    public boolean onPreDraw() {
268                        getDecor().getViewTreeObserver().removeOnPreDrawListener(this);
269                        startTransition(new Runnable() {
270                            @Override
271                            public void run() {
272                                startSharedElementTransition(sharedElementState);
273                            }
274                        });
275                        return false;
276                    }
277                });
278        getDecor().invalidate();
279    }
280
281    private void requestLayoutForSharedElements() {
282        int numSharedElements = mSharedElements.size();
283        for (int i = 0; i < numSharedElements; i++) {
284            mSharedElements.get(i).requestLayout();
285        }
286    }
287
288    private Transition beginTransition(boolean startEnterTransition,
289            boolean startSharedElementTransition) {
290        Transition sharedElementTransition = null;
291        if (startSharedElementTransition && !mSharedElementNames.isEmpty()) {
292            sharedElementTransition = configureTransition(getSharedElementTransition());
293        }
294        Transition viewsTransition = null;
295        if (startEnterTransition && !mTransitioningViews.isEmpty()) {
296            viewsTransition = configureTransition(getViewsTransition());
297            viewsTransition = addTargets(viewsTransition, mTransitioningViews);
298        }
299
300        Transition transition = mergeTransitions(sharedElementTransition, viewsTransition);
301        if (startSharedElementTransition) {
302            if (transition == null) {
303                sharedElementTransitionStarted();
304            } else {
305                transition.addListener(new ContinueTransitionListener() {
306                    @Override
307                    public void onTransitionStart(Transition transition) {
308                        super.onTransitionStart(transition);
309                        transition.removeListener(this);
310                        sharedElementTransitionStarted();
311                    }
312                });
313            }
314        }
315        if (transition != null) {
316            if (sharedElementTransition == null) {
317                transition.addListener(new ContinueTransitionListener());
318            }
319            TransitionManager.beginDelayedTransition(getDecor(), transition);
320            if (startSharedElementTransition && !mSharedElementNames.isEmpty()) {
321                mSharedElements.get(0).invalidate();
322            } else if (startEnterTransition && !mTransitioningViews.isEmpty()) {
323                mTransitioningViews.get(0).invalidate();
324            }
325        } else {
326            transitionStarted();
327        }
328        return transition;
329    }
330
331    private void sharedElementTransitionStarted() {
332        mSharedElementTransitionStarted = true;
333        if (mIsExitTransitionComplete) {
334            send(MSG_EXIT_TRANSITION_COMPLETE, null);
335        }
336    }
337
338    private void startEnterTransition(Transition transition) {
339        setViewVisibility(mTransitioningViews, View.VISIBLE);
340        if (!mIsReturning) {
341            Drawable background = getDecor().getBackground();
342            if (background != null) {
343                background = background.mutate();
344                mBackgroundAnimator = ObjectAnimator.ofInt(background, "alpha", 255);
345                mBackgroundAnimator.setDuration(getFadeDuration());
346                mBackgroundAnimator.addListener(new AnimatorListenerAdapter() {
347                    @Override
348                    public void onAnimationEnd(Animator animation) {
349                        makeOpaque();
350                    }
351                });
352                mBackgroundAnimator.start();
353            } else if (transition != null) {
354                transition.addListener(new Transition.TransitionListenerAdapter() {
355                    @Override
356                    public void onTransitionEnd(Transition transition) {
357                        transition.removeListener(this);
358                        makeOpaque();
359                    }
360                });
361            } else {
362                makeOpaque();
363            }
364        }
365    }
366
367    public void stop() {
368        makeOpaque();
369        mHasStopped = true;
370        mIsCanceled = true;
371        mResultReceiver = null;
372        if (mBackgroundAnimator != null) {
373            mBackgroundAnimator.cancel();
374            mBackgroundAnimator = null;
375        }
376    }
377
378    private void makeOpaque() {
379        if (!mHasStopped && mActivity != null) {
380            if (mWasOpaque) {
381                mActivity.convertFromTranslucent();
382            }
383            mActivity = null;
384        }
385    }
386
387    private boolean allowOverlappingTransitions() {
388        return mIsReturning ? getWindow().getAllowExitTransitionOverlap()
389                : getWindow().getAllowEnterTransitionOverlap();
390    }
391
392    private void startRejectedAnimations(final ArrayList<View> rejectedSnapshots) {
393        if (rejectedSnapshots == null || rejectedSnapshots.isEmpty()) {
394            return;
395        }
396        ViewGroupOverlay overlay = getDecor().getOverlay();
397        ObjectAnimator animator = null;
398        int numRejected = rejectedSnapshots.size();
399        for (int i = 0; i < numRejected; i++) {
400            View snapshot = rejectedSnapshots.get(i);
401            overlay.add(snapshot);
402            animator = ObjectAnimator.ofFloat(snapshot, View.ALPHA, 1, 0);
403            animator.start();
404        }
405        animator.addListener(new AnimatorListenerAdapter() {
406            @Override
407            public void onAnimationEnd(Animator animation) {
408                ViewGroupOverlay overlay = getDecor().getOverlay();
409                int numRejected = rejectedSnapshots.size();
410                for (int i = 0; i < numRejected; i++) {
411                    overlay.remove(rejectedSnapshots.get(i));
412                }
413            }
414        });
415    }
416
417    protected void onRemoteExitTransitionComplete() {
418        if (!allowOverlappingTransitions()) {
419            startEnterTransitionOnly();
420        }
421    }
422
423    private void startEnterTransitionOnly() {
424        startTransition(new Runnable() {
425            @Override
426            public void run() {
427                setEpicenter();
428                boolean startEnterTransition = true;
429                boolean startSharedElementTransition = false;
430                Transition transition = beginTransition(startEnterTransition,
431                        startSharedElementTransition);
432                startEnterTransition(transition);
433            }
434        });
435    }
436}
437