EnterTransitionCoordinator.java revision 1a5b294d399c95e656c9ad1aebbc09d6c384be41
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 onRemoteSceneExitComplete() {
137        super.onRemoteSceneExitComplete();
138    }
139
140    @Override
141    protected void onTakeSharedElements(ArrayList<String> sharedElementNames, Bundle state) {
142        mEnteringSharedElementNames = new ArrayList<String>();
143        mEnteringSharedElementNames.addAll(sharedElementNames);
144        super.onTakeSharedElements(sharedElementNames, state);
145    }
146
147    @Override
148    protected void sharedElementTransitionComplete(Bundle bundle) {
149        notifySharedElementTransitionComplete(bundle);
150    }
151
152    @Override
153    public boolean onPreDraw() {
154        getWindow().getDecorView().getViewTreeObserver().removeOnPreDrawListener(this);
155        setEnteringViews(readyEnteringViews());
156        notifySetListener();
157        onPrepareRestore();
158        return false;
159    }
160
161    @Override
162    public void startExit() {
163        if (!mExitTransitionStarted) {
164            mExitTransitionStarted = true;
165            startExitTransition(mEnteringSharedElementNames);
166        }
167    }
168
169    @Override
170    protected Transition getViewsTransition() {
171        if (!mSupportsTransition) {
172            return null;
173        }
174        return getWindow().getEnterTransition();
175    }
176
177    @Override
178    protected Transition getSharedElementTransition() {
179        if (!mSupportsTransition) {
180            return null;
181        }
182        return getWindow().getSharedElementEnterTransition();
183    }
184
185    @Override
186    protected void onStartEnterTransition(Transition transition, ArrayList<View> enteringViews) {
187        Drawable background = getDecor().getBackground();
188        if (background != null) {
189            ObjectAnimator animator = ObjectAnimator.ofInt(background, "alpha", 255);
190            animator.setDuration(FADE_BACKGROUND_DURATION_MS);
191            animator.addListener(new AnimatorListenerAdapter() {
192                @Override
193                public void onAnimationEnd(Animator animation) {
194                    mMakeOpaque = true;
195                    if (mWasOpaque) {
196                        mActivity.convertFromTranslucent();
197                    }
198                }
199            });
200            animator.start();
201        } else if (mWasOpaque) {
202            transition.addListener(new Transition.TransitionListenerAdapter() {
203                @Override
204                public void onTransitionEnd(Transition transition) {
205                    mMakeOpaque = true;
206                    mActivity.convertFromTranslucent();
207                }
208            });
209        }
210        super.onStartEnterTransition(transition, enteringViews);
211    }
212
213    public ArrayList<View> readyEnteringViews() {
214        ArrayList<View> enteringViews = new ArrayList<View>();
215        getDecor().captureTransitioningViews(enteringViews);
216        if (getViewsTransition() != null) {
217            setViewVisibility(enteringViews, View.INVISIBLE);
218        }
219        return enteringViews;
220    }
221
222    @Override
223    protected void startExitTransition(ArrayList<String> sharedElements) {
224        mMakeOpaque = false;
225        notifyPrepareRestore();
226
227        if (getDecor().getBackground() == null) {
228            ColorDrawable black = new ColorDrawable(0xFF000000);
229            getWindow().setBackgroundDrawable(black);
230        }
231        if (mWasOpaque) {
232            mActivity.convertToTranslucent(new Activity.TranslucentConversionListener() {
233                @Override
234                public void onTranslucentConversionComplete(boolean drawComplete) {
235                    fadeOutBackground();
236                }
237            });
238        } else {
239            fadeOutBackground();
240        }
241
242        super.startExitTransition(sharedElements);
243    }
244
245    private void fadeOutBackground() {
246        ObjectAnimator animator = ObjectAnimator.ofInt(getDecor().getBackground(),
247                "alpha", 0);
248        animator.addListener(new AnimatorListenerAdapter() {
249            @Override
250            public void onAnimationEnd(Animator animation) {
251                mBackgroundFadedOut = true;
252                if (mSharedElementTransitionComplete) {
253                    EnterTransitionCoordinator.super.onSharedElementTransitionEnd();
254                }
255            }
256        });
257        animator.setDuration(FADE_BACKGROUND_DURATION_MS);
258        animator.start();
259    }
260
261    @Override
262    protected void onExitTransitionEnd() {
263        mExitTransitionComplete = true;
264        exitAfterSharedElementTransition();
265        super.onExitTransitionEnd();
266    }
267
268    @Override
269    protected void onSharedElementTransitionEnd() {
270        mSharedElementTransitionComplete = true;
271        if (mBackgroundFadedOut) {
272            super.onSharedElementTransitionEnd();
273        }
274    }
275
276    @Override
277    protected boolean allowOverlappingTransitions() {
278        return getWindow().getAllowEnterTransitionOverlap();
279    }
280
281    private void exitAfterSharedElementTransition() {
282        if (mSharedElementTransitionComplete && mExitTransitionComplete) {
283            mActivity.finish();
284            if (mSupportsTransition) {
285                mActivity.overridePendingTransition(0, 0);
286            }
287            notifyExitTransitionComplete();
288            clearConnections();
289        }
290    }
291}
292