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