EnterTransitionCoordinator.java revision 8df5a07d880691e420cb7c702e4d41260e85d1b8
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.drawable.ColorDrawable;
22import android.graphics.drawable.Drawable;
23import android.os.Bundle;
24import android.os.ResultReceiver;
25import android.transition.Transition;
26import android.view.View;
27import android.view.ViewTreeObserver;
28import android.view.Window;
29
30import java.util.ArrayList;
31
32/**
33 * This ActivityTransitionCoordinator is created by the Activity to manage
34 * the enter scene and shared element transfer as well as Activity#finishWithTransition
35 * exiting the Scene and transferring shared elements back to the called Activity.
36 */
37class EnterTransitionCoordinator extends ActivityTransitionCoordinator
38        implements ViewTreeObserver.OnPreDrawListener {
39    private static final String TAG = "EnterTransitionCoordinator";
40
41    // The background fade in/out duration. 150ms is pretty quick, but not abrupt.
42    private static final int FADE_BACKGROUND_DURATION_MS = 150;
43
44    /**
45     * The shared element names sent by the ExitTransitionCoordinator and may be
46     * shared when exiting back.
47     */
48    private ArrayList<String> mEnteringSharedElementNames;
49
50    /**
51     * The Activity that has created this coordinator. This is used solely to make the
52     * Window translucent/opaque.
53     */
54    private Activity mActivity;
55
56    /**
57     * True if the Window was opaque at the start and we should make it opaque again after
58     * enter transitions have completed.
59     */
60    private boolean mWasOpaque;
61
62    /**
63     * During exit, is the background alpha == 0?
64     */
65    private boolean mBackgroundFadedOut;
66
67    /**
68     * During exit, has the shared element transition completed?
69     */
70    private boolean mSharedElementTransitionComplete;
71
72    /**
73     * Has the exit started? We don't want to accidentally exit multiple times. e.g. when
74     * back is hit twice during the exit animation.
75     */
76    private boolean mExitTransitionStarted;
77
78    /**
79     * Has the exit transition ended?
80     */
81    private boolean mExitTransitionComplete;
82
83    /**
84     * We only want to make the Window transparent and set the background alpha once. After that,
85     * the Activity won't want the same enter transition.
86     */
87    private boolean mMadeReady;
88
89    /**
90     * True if Window.hasFeature(Window.FEATURE_CONTENT_TRANSITIONS) -- this means that
91     * enter and exit transitions should be active.
92     */
93    private boolean mSupportsTransition;
94
95    /**
96     * Background alpha animations may complete prior to receiving the callback for
97     * onTranslucentConversionComplete. If so, we need to immediately call to make the Window
98     * opaque.
99     */
100    private boolean mMakeOpaque;
101
102    public EnterTransitionCoordinator(Activity activity, ResultReceiver resultReceiver) {
103        super(activity.getWindow());
104        mActivity = activity;
105        setRemoteResultReceiver(resultReceiver);
106    }
107
108    public void readyToEnter() {
109        if (!mMadeReady) {
110            mMadeReady = true;
111            mSupportsTransition = getWindow().hasFeature(Window.FEATURE_CONTENT_TRANSITIONS);
112            if (mSupportsTransition) {
113                Window window = getWindow();
114                window.getDecorView().getViewTreeObserver().addOnPreDrawListener(this);
115                mActivity.overridePendingTransition(0, 0);
116                mActivity.convertToTranslucent(new Activity.TranslucentConversionListener() {
117                    @Override
118                    public void onTranslucentConversionComplete(boolean drawComplete) {
119                        mWasOpaque = true;
120                        if (mMakeOpaque) {
121                            mActivity.convertFromTranslucent();
122                        }
123                    }
124                });
125                Drawable background = getDecor().getBackground();
126                if (background != null) {
127                    window.setBackgroundDrawable(null);
128                    background.setAlpha(0);
129                    window.setBackgroundDrawable(background);
130                }
131            }
132        }
133    }
134
135    @Override
136    protected void onTakeSharedElements(ArrayList<String> sharedElementNames, Bundle state) {
137        mEnteringSharedElementNames = new ArrayList<String>();
138        mEnteringSharedElementNames.addAll(sharedElementNames);
139        super.onTakeSharedElements(sharedElementNames, state);
140    }
141
142    @Override
143    protected void sharedElementTransitionComplete(Bundle bundle) {
144        notifySharedElementTransitionComplete(bundle);
145        exitAfterSharedElementTransition();
146    }
147
148    @Override
149    public boolean onPreDraw() {
150        getWindow().getDecorView().getViewTreeObserver().removeOnPreDrawListener(this);
151        setEnteringViews(readyEnteringViews());
152        notifySetListener();
153        onPrepareRestore();
154        return false;
155    }
156
157    @Override
158    public void startExit() {
159        if (!mExitTransitionStarted) {
160            mExitTransitionStarted = true;
161            startExitTransition(mEnteringSharedElementNames);
162        }
163    }
164
165    @Override
166    protected Transition getViewsTransition() {
167        if (!mSupportsTransition) {
168            return null;
169        }
170        return getWindow().getEnterTransition();
171    }
172
173    @Override
174    protected Transition getSharedElementTransition() {
175        if (!mSupportsTransition) {
176            return null;
177        }
178        return getWindow().getSharedElementEnterTransition();
179    }
180
181    @Override
182    protected void onStartEnterTransition(Transition transition, ArrayList<View> enteringViews) {
183        Drawable background = getDecor().getBackground();
184        if (background != null) {
185            ObjectAnimator animator = ObjectAnimator.ofInt(background, "alpha", 255);
186            animator.setDuration(FADE_BACKGROUND_DURATION_MS);
187            animator.addListener(new AnimatorListenerAdapter() {
188                @Override
189                public void onAnimationEnd(Animator animation) {
190                    mMakeOpaque = true;
191                    if (mWasOpaque) {
192                        mActivity.convertFromTranslucent();
193                    }
194                }
195            });
196            animator.start();
197        } else if (mWasOpaque) {
198            transition.addListener(new Transition.TransitionListenerAdapter() {
199                @Override
200                public void onTransitionEnd(Transition transition) {
201                    mMakeOpaque = true;
202                    mActivity.convertFromTranslucent();
203                }
204            });
205        }
206        super.onStartEnterTransition(transition, enteringViews);
207    }
208
209    public ArrayList<View> readyEnteringViews() {
210        ArrayList<View> enteringViews = new ArrayList<View>();
211        getDecor().captureTransitioningViews(enteringViews);
212        if (getViewsTransition() != null) {
213            setViewVisibility(enteringViews, View.INVISIBLE);
214        }
215        return enteringViews;
216    }
217
218    @Override
219    protected void startExitTransition(ArrayList<String> sharedElements) {
220        mMakeOpaque = false;
221        notifyPrepareRestore();
222
223        if (getDecor().getBackground() == null) {
224            ColorDrawable black = new ColorDrawable(0xFF000000);
225            getWindow().setBackgroundDrawable(black);
226        }
227        if (mWasOpaque) {
228            mActivity.convertToTranslucent(new Activity.TranslucentConversionListener() {
229                @Override
230                public void onTranslucentConversionComplete(boolean drawComplete) {
231                    fadeOutBackground();
232                }
233            });
234        } else {
235            fadeOutBackground();
236        }
237
238        super.startExitTransition(sharedElements);
239    }
240
241    private void fadeOutBackground() {
242        ObjectAnimator animator = ObjectAnimator.ofInt(getDecor().getBackground(),
243                "alpha", 0);
244        animator.addListener(new AnimatorListenerAdapter() {
245            @Override
246            public void onAnimationEnd(Animator animation) {
247                mBackgroundFadedOut = true;
248                if (mSharedElementTransitionComplete) {
249                    EnterTransitionCoordinator.super.onSharedElementTransitionEnd();
250                }
251            }
252        });
253        animator.setDuration(FADE_BACKGROUND_DURATION_MS);
254        animator.start();
255    }
256
257    @Override
258    protected void onExitTransitionEnd() {
259        mExitTransitionComplete = true;
260        exitAfterSharedElementTransition();
261        super.onExitTransitionEnd();
262    }
263
264    @Override
265    protected void onSharedElementTransitionEnd() {
266        mSharedElementTransitionComplete = true;
267        if (mBackgroundFadedOut) {
268            super.onSharedElementTransitionEnd();
269        }
270    }
271
272    @Override
273    protected boolean allowOverlappingTransitions() {
274        return getWindow().getAllowEnterTransitionOverlap();
275    }
276
277    private void exitAfterSharedElementTransition() {
278        if (mSharedElementTransitionComplete && mExitTransitionComplete && mBackgroundFadedOut) {
279            mActivity.finish();
280            if (mSupportsTransition) {
281                mActivity.overridePendingTransition(0, 0);
282            }
283            notifyExitTransitionComplete();
284            clearConnections();
285        }
286    }
287}
288