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