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