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