2 * Copyright (C) 2014 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
18 import android.animation.Animator;
19 import android.animation.AnimatorListenerAdapter;
20 import android.animation.ObjectAnimator;
21 import android.app.SharedElementCallback.OnSharedElementsReadyListener;
22 import android.content.Intent;
23 import android.graphics.Color;
24 import android.graphics.Matrix;
25 import android.graphics.RectF;
26 import android.graphics.drawable.ColorDrawable;
27 import android.graphics.drawable.Drawable;
28 import android.os.Build.VERSION_CODES;
29 import android.os.Bundle;
30 import android.os.Handler;
31 import android.os.Message;
32 import android.os.ResultReceiver;
33 import android.transition.Transition;
34 import android.transition.TransitionManager;
35 import android.view.View;
36 import android.view.ViewGroup;
37 import android.view.ViewTreeObserver;
39 import java.util.ArrayList;
42 * This ActivityTransitionCoordinator is created in ActivityOptions#makeSceneTransitionAnimation
43 * to govern the exit of the Scene and the shared elements when calling an Activity as well as
44 * the reentry of the Scene when coming back from the called Activity.
46 class ExitTransitionCoordinator extends ActivityTransitionCoordinator {
47 private static final String TAG = "ExitTransitionCoordinator";
48 private static final long MAX_WAIT_MS = 1000;
50 private Bundle mSharedElementBundle;
51 private boolean mExitNotified;
52 private boolean mSharedElementNotified;
53 private Activity mActivity;
54 private boolean mIsBackgroundReady;
55 private boolean mIsCanceled;
56 private Handler mHandler;
57 private ObjectAnimator mBackgroundAnimator;
58 private boolean mIsHidden;
59 private Bundle mExitSharedElementBundle;
60 private boolean mIsExitStarted;
61 private boolean mSharedElementsHidden;
63 public ExitTransitionCoordinator(Activity activity, ArrayList<String> names,
64 ArrayList<String> accepted, ArrayList<View> mapped, boolean isReturning) {
65 super(activity.getWindow(), names, getListener(activity, isReturning), isReturning);
66 viewsReady(mapSharedElements(accepted, mapped));
67 stripOffscreenViews();
68 mIsBackgroundReady = !isReturning;
72 private static SharedElementCallback getListener(Activity activity, boolean isReturning) {
73 return isReturning ? activity.mEnterTransitionListener : activity.mExitTransitionListener;
77 protected void onReceiveResult(int resultCode, Bundle resultData) {
79 case MSG_SET_REMOTE_RECEIVER:
81 mResultReceiver = resultData.getParcelable(KEY_REMOTE_RECEIVER);
83 mResultReceiver.send(MSG_CANCEL, null);
84 mResultReceiver = null;
89 case MSG_HIDE_SHARED_ELEMENTS:
95 case MSG_START_EXIT_TRANSITION:
96 mHandler.removeMessages(MSG_CANCEL);
99 case MSG_SHARED_ELEMENT_DESTINATION:
100 mExitSharedElementBundle = resultData;
101 sharedElementExitBack();
110 private void stopCancel() {
111 if (mHandler != null) {
112 mHandler.removeMessages(MSG_CANCEL);
116 private void delayCancel() {
117 if (mHandler != null) {
118 mHandler.sendEmptyMessageDelayed(MSG_CANCEL, MAX_WAIT_MS);
122 public void resetViews() {
123 if (mTransitioningViews != null) {
124 showViews(mTransitioningViews, true);
125 setTransitioningViewsVisiblity(View.VISIBLE, true);
127 showViews(mSharedElements, true);
129 ViewGroup decorView = getDecor();
130 if (!mIsReturning && decorView != null) {
131 decorView.suppressLayout(false);
133 moveSharedElementsFromOverlay();
137 private void sharedElementExitBack() {
138 final ViewGroup decorView = getDecor();
139 if (decorView != null) {
140 decorView.suppressLayout(true);
142 if (decorView != null && mExitSharedElementBundle != null &&
143 !mExitSharedElementBundle.isEmpty() &&
144 !mSharedElements.isEmpty() && getSharedElementTransition() != null) {
145 startTransition(new Runnable() {
147 startSharedElementExit(decorView);
151 sharedElementTransitionComplete();
155 private void startSharedElementExit(final ViewGroup decorView) {
156 Transition transition = getSharedElementExitTransition();
157 transition.addListener(new Transition.TransitionListenerAdapter() {
159 public void onTransitionEnd(Transition transition) {
160 transition.removeListener(this);
161 if (isViewsTransitionComplete()) {
166 final ArrayList<View> sharedElementSnapshots = createSnapshots(mExitSharedElementBundle,
167 mSharedElementNames);
168 decorView.getViewTreeObserver()
169 .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
171 public boolean onPreDraw() {
172 decorView.getViewTreeObserver().removeOnPreDrawListener(this);
173 setSharedElementState(mExitSharedElementBundle, sharedElementSnapshots);
177 setGhostVisibility(View.INVISIBLE);
178 scheduleGhostVisibilityChange(View.INVISIBLE);
179 if (mListener != null) {
180 mListener.onSharedElementEnd(mSharedElementNames, mSharedElements,
181 sharedElementSnapshots);
183 TransitionManager.beginDelayedTransition(decorView, transition);
184 scheduleGhostVisibilityChange(View.VISIBLE);
185 setGhostVisibility(View.VISIBLE);
186 decorView.invalidate();
189 private void hideSharedElements() {
190 moveSharedElementsFromOverlay();
192 hideViews(mSharedElements);
194 mSharedElementsHidden = true;
198 public void startExit() {
199 if (!mIsExitStarted) {
200 mIsExitStarted = true;
202 ViewGroup decorView = getDecor();
203 if (decorView != null) {
204 decorView.suppressLayout(true);
206 moveSharedElementsToOverlay();
207 startTransition(new Runnable() {
216 public void startExit(int resultCode, Intent data) {
217 if (!mIsExitStarted) {
218 mIsExitStarted = true;
220 ViewGroup decorView = getDecor();
221 if (decorView != null) {
222 decorView.suppressLayout(true);
224 mHandler = new Handler() {
226 public void handleMessage(Message msg) {
232 moveSharedElementsToOverlay();
233 if (decorView != null && decorView.getBackground() == null) {
234 getWindow().setBackgroundDrawable(new ColorDrawable(Color.BLACK));
236 final boolean targetsM = decorView == null || decorView.getContext()
237 .getApplicationInfo().targetSdkVersion >= VERSION_CODES.M;
238 ArrayList<String> sharedElementNames = targetsM ? mSharedElementNames :
239 mAllSharedElementNames;
240 ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(mActivity, this,
241 sharedElementNames, resultCode, data);
242 mActivity.convertToTranslucent(new Activity.TranslucentConversionListener() {
244 public void onTranslucentConversionComplete(boolean drawComplete) {
250 startTransition(new Runnable() {
253 startExitTransition();
260 if (mIsReturning && mActivity != null) {
261 // Override the previous ActivityOptions. We don't want the
262 // activity to have options since we're essentially canceling the
263 // transition and finishing right now.
264 mActivity.convertToTranslucent(null, null);
269 private void startExitTransition() {
270 Transition transition = getExitTransition();
271 ViewGroup decorView = getDecor();
272 if (transition != null && decorView != null && mTransitioningViews != null) {
273 setTransitioningViewsVisiblity(View.VISIBLE, false);
274 TransitionManager.beginDelayedTransition(decorView, transition);
275 setTransitioningViewsVisiblity(View.INVISIBLE, false);
276 decorView.invalidate();
282 private void fadeOutBackground() {
283 if (mBackgroundAnimator == null) {
284 ViewGroup decor = getDecor();
286 if (decor != null && (background = decor.getBackground()) != null) {
287 background = background.mutate();
288 getWindow().setBackgroundDrawable(background);
289 mBackgroundAnimator = ObjectAnimator.ofInt(background, "alpha", 0);
290 mBackgroundAnimator.addListener(new AnimatorListenerAdapter() {
292 public void onAnimationEnd(Animator animation) {
293 mBackgroundAnimator = null;
295 mIsBackgroundReady = true;
300 mBackgroundAnimator.setDuration(getFadeDuration());
301 mBackgroundAnimator.start();
303 mIsBackgroundReady = true;
308 private Transition getExitTransition() {
309 Transition viewsTransition = null;
310 if (mTransitioningViews != null && !mTransitioningViews.isEmpty()) {
311 viewsTransition = configureTransition(getViewsTransition(), true);
313 if (viewsTransition == null) {
314 viewsTransitionComplete();
316 final ArrayList<View> transitioningViews = mTransitioningViews;
317 viewsTransition.addListener(new ContinueTransitionListener() {
319 public void onTransitionEnd(Transition transition) {
320 transition.removeListener(this);
321 viewsTransitionComplete();
322 if (mIsHidden && transitioningViews != null) {
323 showViews(transitioningViews, true);
324 setTransitioningViewsVisiblity(View.VISIBLE, true);
326 if (mSharedElementBundle != null) {
329 super.onTransitionEnd(transition);
333 return viewsTransition;
336 private Transition getSharedElementExitTransition() {
337 Transition sharedElementTransition = null;
338 if (!mSharedElements.isEmpty()) {
339 sharedElementTransition = configureTransition(getSharedElementTransition(), false);
341 if (sharedElementTransition == null) {
342 sharedElementTransitionComplete();
344 sharedElementTransition.addListener(new ContinueTransitionListener() {
346 public void onTransitionEnd(Transition transition) {
347 transition.removeListener(this);
348 sharedElementTransitionComplete();
350 showViews(mSharedElements, true);
354 mSharedElements.get(0).invalidate();
356 return sharedElementTransition;
359 private void beginTransitions() {
360 Transition sharedElementTransition = getSharedElementExitTransition();
361 Transition viewsTransition = getExitTransition();
363 Transition transition = mergeTransitions(sharedElementTransition, viewsTransition);
364 ViewGroup decorView = getDecor();
365 if (transition != null && decorView != null) {
366 setGhostVisibility(View.INVISIBLE);
367 scheduleGhostVisibilityChange(View.INVISIBLE);
368 if (viewsTransition != null) {
369 setTransitioningViewsVisiblity(View.VISIBLE, false);
371 TransitionManager.beginDelayedTransition(decorView, transition);
372 scheduleGhostVisibilityChange(View.VISIBLE);
373 setGhostVisibility(View.VISIBLE);
374 if (viewsTransition != null) {
375 setTransitioningViewsVisiblity(View.INVISIBLE, false);
377 decorView.invalidate();
383 protected boolean isReadyToNotify() {
384 return mSharedElementBundle != null && mResultReceiver != null && mIsBackgroundReady;
388 protected void sharedElementTransitionComplete() {
389 mSharedElementBundle = mExitSharedElementBundle == null
390 ? captureSharedElementState() : captureExitSharedElementsState();
391 super.sharedElementTransitionComplete();
394 private Bundle captureExitSharedElementsState() {
395 Bundle bundle = new Bundle();
396 RectF bounds = new RectF();
397 Matrix matrix = new Matrix();
398 for (int i = 0; i < mSharedElements.size(); i++) {
399 String name = mSharedElementNames.get(i);
400 Bundle sharedElementState = mExitSharedElementBundle.getBundle(name);
401 if (sharedElementState != null) {
402 bundle.putBundle(name, sharedElementState);
404 View view = mSharedElements.get(i);
405 captureSharedElementState(view, name, bundle, matrix, bounds);
412 protected void onTransitionsComplete() {
416 protected void notifyComplete() {
417 if (isReadyToNotify()) {
418 if (!mSharedElementNotified) {
419 mSharedElementNotified = true;
421 if (mListener == null) {
422 mResultReceiver.send(MSG_TAKE_SHARED_ELEMENTS, mSharedElementBundle);
423 notifyExitComplete();
425 final ResultReceiver resultReceiver = mResultReceiver;
426 final Bundle sharedElementBundle = mSharedElementBundle;
427 mListener.onSharedElementsArrived(mSharedElementNames, mSharedElements,
428 new OnSharedElementsReadyListener() {
430 public void onSharedElementsReady() {
431 resultReceiver.send(MSG_TAKE_SHARED_ELEMENTS,
432 sharedElementBundle);
433 notifyExitComplete();
438 notifyExitComplete();
443 private void notifyExitComplete() {
444 if (!mExitNotified && isViewsTransitionComplete()) {
445 mExitNotified = true;
446 mResultReceiver.send(MSG_EXIT_TRANSITION_COMPLETE, null);
447 mResultReceiver = null; // done talking
448 ViewGroup decorView = getDecor();
449 if (!mIsReturning && decorView != null) {
450 decorView.suppressLayout(false);
456 private void finishIfNecessary() {
457 if (mIsReturning && mExitNotified && mActivity != null && (mSharedElements.isEmpty() ||
458 mSharedElementsHidden)) {
461 if (!mIsReturning && mExitNotified) {
462 mActivity = null; // don't need it anymore
466 private void finish() {
468 if (mActivity != null) {
469 mActivity.mActivityTransitionState.clear();
471 mActivity.overridePendingTransition(0, 0);
474 // Clear the state so that we can't hold any references accidentally and leak memory.
479 protected void clearState() {
481 mSharedElementBundle = null;
482 if (mBackgroundAnimator != null) {
483 mBackgroundAnimator.cancel();
484 mBackgroundAnimator = null;
486 mExitSharedElementBundle = null;
491 protected boolean moveSharedElementWithParent() {
492 return !mIsReturning;
496 protected Transition getViewsTransition() {
498 return getWindow().getReturnTransition();
500 return getWindow().getExitTransition();
504 protected Transition getSharedElementTransition() {
506 return getWindow().getSharedElementReturnTransition();
508 return getWindow().getSharedElementExitTransition();