ExitTransitionCoordinator.java revision d80154fba1c23834b5139aa764f9426bbf38a721
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.Intent;
22import android.graphics.Bitmap;
23import android.graphics.Canvas;
24import android.graphics.drawable.ColorDrawable;
25import android.graphics.drawable.Drawable;
26import android.os.Bundle;
27import android.os.Handler;
28import android.os.Message;
29import android.transition.Transition;
30import android.transition.TransitionManager;
31import android.view.View;
32import android.widget.ImageView;
33
34import java.util.ArrayList;
35
36/**
37 * This ActivityTransitionCoordinator is created in ActivityOptions#makeSceneTransitionAnimation
38 * to govern the exit of the Scene and the shared elements when calling an Activity as well as
39 * the reentry of the Scene when coming back from the called Activity.
40 */
41class ExitTransitionCoordinator extends ActivityTransitionCoordinator {
42    private static final String TAG = "ExitTransitionCoordinator";
43    private static final long MAX_WAIT_MS = 1000;
44
45    private boolean mExitComplete;
46
47    private Bundle mSharedElementBundle;
48
49    private boolean mExitNotified;
50
51    private boolean mSharedElementNotified;
52
53    private Activity mActivity;
54
55    private boolean mIsBackgroundReady;
56
57    private boolean mIsCanceled;
58
59    private Handler mHandler;
60
61    private boolean mIsReturning;
62
63    private ObjectAnimator mBackgroundAnimator;
64
65    private boolean mIsHidden;
66
67    public ExitTransitionCoordinator(Activity activity, ArrayList<String> names,
68            ArrayList<String> accepted, ArrayList<String> mapped, boolean isReturning) {
69        super(activity.getWindow(), names, accepted, mapped, getListener(activity, isReturning));
70        mIsReturning = isReturning;
71        mIsBackgroundReady = !isReturning;
72        mActivity = activity;
73    }
74
75    private static SharedElementListener getListener(Activity activity, boolean isReturning) {
76        return isReturning ? activity.mEnterTransitionListener : activity.mExitTransitionListener;
77    }
78
79    @Override
80    protected void onReceiveResult(int resultCode, Bundle resultData) {
81        switch (resultCode) {
82            case MSG_SET_REMOTE_RECEIVER:
83                mResultReceiver = resultData.getParcelable(KEY_REMOTE_RECEIVER);
84                if (mIsCanceled) {
85                    mResultReceiver.send(MSG_CANCEL, null);
86                    mResultReceiver = null;
87                } else {
88                    if (mHandler != null) {
89                        mHandler.removeMessages(MSG_CANCEL);
90                    }
91                    notifyComplete();
92                }
93                break;
94            case MSG_HIDE_SHARED_ELEMENTS:
95                if (!mIsCanceled) {
96                    hideSharedElements();
97                }
98                break;
99            case MSG_START_EXIT_TRANSITION:
100                startExit();
101                break;
102            case MSG_ACTIVITY_STOPPED:
103                setViewVisibility(mTransitioningViews, View.VISIBLE);
104                setViewVisibility(mSharedElements, View.VISIBLE);
105                mIsHidden = true;
106                break;
107        }
108    }
109
110    private void hideSharedElements() {
111        setViewVisibility(mSharedElements, View.INVISIBLE);
112    }
113
114    public void startExit() {
115        beginTransition();
116        setViewVisibility(mTransitioningViews, View.INVISIBLE);
117    }
118
119    public void startExit(int resultCode, Intent data) {
120        mHandler = new Handler() {
121            @Override
122            public void handleMessage(Message msg) {
123                mIsCanceled = true;
124                mActivity.finish();
125                mActivity = null;
126            }
127        };
128        mHandler.sendEmptyMessageDelayed(MSG_CANCEL, MAX_WAIT_MS);
129        if (getDecor().getBackground() == null) {
130            ColorDrawable black = new ColorDrawable(0xFF000000);
131            black.setAlpha(0);
132            getWindow().setBackgroundDrawable(black);
133            black.setAlpha(255);
134        }
135        ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(mActivity, this,
136                mAllSharedElementNames, resultCode, data);
137        mActivity.convertToTranslucent(new Activity.TranslucentConversionListener() {
138            @Override
139            public void onTranslucentConversionComplete(boolean drawComplete) {
140                if (!mIsCanceled) {
141                    fadeOutBackground();
142                }
143            }
144        }, options);
145        startExit();
146    }
147
148    private void fadeOutBackground() {
149        if (mBackgroundAnimator == null) {
150            Drawable background = getDecor().getBackground();
151            mBackgroundAnimator = ObjectAnimator.ofInt(background, "alpha", 0);
152            mBackgroundAnimator.addListener(new AnimatorListenerAdapter() {
153                @Override
154                public void onAnimationEnd(Animator animation) {
155                    mBackgroundAnimator = null;
156                    if (!mIsCanceled) {
157                        mIsBackgroundReady = true;
158                        notifyComplete();
159                    }
160                }
161            });
162            mBackgroundAnimator.setDuration(FADE_BACKGROUND_DURATION_MS);
163            mBackgroundAnimator.start();
164        }
165    }
166
167    private void beginTransition() {
168        Transition sharedElementTransition = configureTransition(getSharedElementTransition());
169        Transition viewsTransition = configureTransition(getViewsTransition());
170        viewsTransition = addTargets(viewsTransition, mTransitioningViews);
171        if (sharedElementTransition == null || mSharedElements.isEmpty()) {
172            sharedElementTransitionComplete();
173            sharedElementTransition = null;
174        } else {
175            sharedElementTransition.addListener(new Transition.TransitionListenerAdapter() {
176                @Override
177                public void onTransitionEnd(Transition transition) {
178                    sharedElementTransitionComplete();
179                }
180            });
181        }
182        if (viewsTransition == null || mTransitioningViews.isEmpty()) {
183            exitTransitionComplete();
184            viewsTransition = null;
185        } else {
186            viewsTransition.addListener(new Transition.TransitionListenerAdapter() {
187                @Override
188                public void onTransitionEnd(Transition transition) {
189                    exitTransitionComplete();
190                    if (mIsHidden) {
191                        setViewVisibility(mTransitioningViews, View.VISIBLE);
192                    }
193                }
194            });
195        }
196
197        Transition transition = mergeTransitions(sharedElementTransition, viewsTransition);
198        TransitionManager.beginDelayedTransition(getDecor(), transition);
199        if (viewsTransition == null && sharedElementTransition != null) {
200            mSharedElements.get(0).requestLayout();
201        }
202    }
203
204    private void exitTransitionComplete() {
205        mExitComplete = true;
206        notifyComplete();
207    }
208
209    protected boolean isReadyToNotify() {
210        return mSharedElementBundle != null && mResultReceiver != null && mIsBackgroundReady;
211    }
212
213    private void sharedElementTransitionComplete() {
214        Bundle bundle = new Bundle();
215        int[] tempLoc = new int[2];
216        for (int i = 0; i < mSharedElementNames.size(); i++) {
217            View sharedElement = mSharedElements.get(i);
218            String name = mSharedElementNames.get(i);
219            captureSharedElementState(sharedElement, name, bundle, tempLoc);
220        }
221        mSharedElementBundle = bundle;
222        notifyComplete();
223    }
224
225    protected void notifyComplete() {
226        if (isReadyToNotify()) {
227            if (!mSharedElementNotified) {
228                mSharedElementNotified = true;
229                mResultReceiver.send(MSG_TAKE_SHARED_ELEMENTS, mSharedElementBundle);
230            }
231            if (!mExitNotified && mExitComplete) {
232                mExitNotified = true;
233                mResultReceiver.send(MSG_EXIT_TRANSITION_COMPLETE, null);
234                mResultReceiver = null; // done talking
235                if (mIsReturning) {
236                    mActivity.finish();
237                    mActivity.overridePendingTransition(0, 0);
238                }
239                mActivity = null;
240            }
241        }
242    }
243
244    @Override
245    protected Transition getViewsTransition() {
246        if (mIsReturning) {
247            return getWindow().getEnterTransition();
248        } else {
249            return getWindow().getExitTransition();
250        }
251    }
252
253    protected Transition getSharedElementTransition() {
254        if (mIsReturning) {
255            return getWindow().getSharedElementEnterTransition();
256        } else {
257            return getWindow().getSharedElementExitTransition();
258        }
259    }
260
261    /**
262     * Captures placement information for Views with a shared element name for
263     * Activity Transitions.
264     *
265     * @param view           The View to capture the placement information for.
266     * @param name           The shared element name in the target Activity to apply the placement
267     *                       information for.
268     * @param transitionArgs Bundle to store shared element placement information.
269     * @param tempLoc        A temporary int[2] for capturing the current location of views.
270     */
271    private static void captureSharedElementState(View view, String name, Bundle transitionArgs,
272            int[] tempLoc) {
273        Bundle sharedElementBundle = new Bundle();
274        view.getLocationOnScreen(tempLoc);
275        float scaleX = view.getScaleX();
276        sharedElementBundle.putInt(KEY_SCREEN_X, tempLoc[0]);
277        int width = Math.round(view.getWidth() * scaleX);
278        sharedElementBundle.putInt(KEY_WIDTH, width);
279
280        float scaleY = view.getScaleY();
281        sharedElementBundle.putInt(KEY_SCREEN_Y, tempLoc[1]);
282        int height = Math.round(view.getHeight() * scaleY);
283        sharedElementBundle.putInt(KEY_HEIGHT, height);
284
285        sharedElementBundle.putFloat(KEY_TRANSLATION_Z, view.getTranslationZ());
286
287        Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
288        Canvas canvas = new Canvas(bitmap);
289        view.draw(canvas);
290        sharedElementBundle.putParcelable(KEY_BITMAP, bitmap);
291
292        if (view instanceof ImageView) {
293            ImageView imageView = (ImageView) view;
294            int scaleTypeInt = scaleTypeToInt(imageView.getScaleType());
295            sharedElementBundle.putInt(KEY_SCALE_TYPE, scaleTypeInt);
296            if (imageView.getScaleType() == ImageView.ScaleType.MATRIX) {
297                float[] matrix = new float[9];
298                imageView.getImageMatrix().getValues(matrix);
299                sharedElementBundle.putFloatArray(KEY_IMAGE_MATRIX, matrix);
300            }
301        }
302
303        transitionArgs.putBundle(name, sharedElementBundle);
304    }
305
306    private static int scaleTypeToInt(ImageView.ScaleType scaleType) {
307        for (int i = 0; i < SCALE_TYPE_VALUES.length; i++) {
308            if (scaleType == SCALE_TYPE_VALUES[i]) {
309                return i;
310            }
311        }
312        return -1;
313    }
314}
315