ExitTransitionCoordinator.java revision 4dc668cd36fbb46b0c2cc196ae4b7105009ec089
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.app.SharedElementCallback.OnSharedElementsReadyListener; 22import android.content.Intent; 23import android.graphics.Color; 24import android.graphics.Matrix; 25import android.graphics.RectF; 26import android.graphics.drawable.ColorDrawable; 27import android.graphics.drawable.Drawable; 28import android.os.Bundle; 29import android.os.Handler; 30import android.os.Message; 31import android.os.ResultReceiver; 32import android.transition.Transition; 33import android.transition.TransitionManager; 34import android.view.View; 35import android.view.ViewGroup; 36import android.view.ViewTreeObserver; 37 38import java.util.ArrayList; 39 40/** 41 * This ActivityTransitionCoordinator is created in ActivityOptions#makeSceneTransitionAnimation 42 * to govern the exit of the Scene and the shared elements when calling an Activity as well as 43 * the reentry of the Scene when coming back from the called Activity. 44 */ 45class ExitTransitionCoordinator extends ActivityTransitionCoordinator { 46 private static final String TAG = "ExitTransitionCoordinator"; 47 private static final long MAX_WAIT_MS = 1000; 48 49 private boolean mExitComplete; 50 51 private Bundle mSharedElementBundle; 52 53 private boolean mExitNotified; 54 55 private boolean mSharedElementNotified; 56 57 private Activity mActivity; 58 59 private boolean mIsBackgroundReady; 60 61 private boolean mIsCanceled; 62 63 private Handler mHandler; 64 65 private ObjectAnimator mBackgroundAnimator; 66 67 private boolean mIsHidden; 68 69 private Bundle mExitSharedElementBundle; 70 71 private boolean mIsExitStarted; 72 73 private boolean mSharedElementsHidden; 74 75 public ExitTransitionCoordinator(Activity activity, ArrayList<String> names, 76 ArrayList<String> accepted, ArrayList<View> mapped, boolean isReturning) { 77 super(activity.getWindow(), names, getListener(activity, isReturning), isReturning); 78 viewsReady(mapSharedElements(accepted, mapped)); 79 stripOffscreenViews(); 80 mIsBackgroundReady = !isReturning; 81 mActivity = activity; 82 } 83 84 private static SharedElementCallback getListener(Activity activity, boolean isReturning) { 85 return isReturning ? activity.mEnterTransitionListener : activity.mExitTransitionListener; 86 } 87 88 @Override 89 protected void onReceiveResult(int resultCode, Bundle resultData) { 90 switch (resultCode) { 91 case MSG_SET_REMOTE_RECEIVER: 92 stopCancel(); 93 mResultReceiver = resultData.getParcelable(KEY_REMOTE_RECEIVER); 94 if (mIsCanceled) { 95 mResultReceiver.send(MSG_CANCEL, null); 96 mResultReceiver = null; 97 } else { 98 notifyComplete(); 99 } 100 break; 101 case MSG_HIDE_SHARED_ELEMENTS: 102 stopCancel(); 103 if (!mIsCanceled) { 104 hideSharedElements(); 105 } 106 break; 107 case MSG_START_EXIT_TRANSITION: 108 mHandler.removeMessages(MSG_CANCEL); 109 startExit(); 110 break; 111 case MSG_SHARED_ELEMENT_DESTINATION: 112 mExitSharedElementBundle = resultData; 113 sharedElementExitBack(); 114 break; 115 } 116 } 117 118 private void stopCancel() { 119 if (mHandler != null) { 120 mHandler.removeMessages(MSG_CANCEL); 121 } 122 } 123 124 private void delayCancel() { 125 if (mHandler != null) { 126 mHandler.sendEmptyMessageDelayed(MSG_CANCEL, MAX_WAIT_MS); 127 } 128 } 129 130 public void resetViews() { 131 if (mTransitioningViews != null) { 132 showViews(mTransitioningViews, true); 133 } 134 showViews(mSharedElements, true); 135 mIsHidden = true; 136 ViewGroup decorView = getDecor(); 137 if (!mIsReturning && decorView != null) { 138 decorView.suppressLayout(false); 139 } 140 moveSharedElementsFromOverlay(); 141 clearState(); 142 } 143 144 private void sharedElementExitBack() { 145 final ViewGroup decorView = getDecor(); 146 if (decorView != null) { 147 decorView.suppressLayout(true); 148 } 149 if (decorView != null && mExitSharedElementBundle != null && 150 !mExitSharedElementBundle.isEmpty() && 151 !mSharedElements.isEmpty() && getSharedElementTransition() != null) { 152 startTransition(new Runnable() { 153 public void run() { 154 startSharedElementExit(decorView); 155 } 156 }); 157 } else { 158 sharedElementTransitionComplete(); 159 } 160 } 161 162 private void startSharedElementExit(final ViewGroup decorView) { 163 Transition transition = getSharedElementExitTransition(); 164 transition.addListener(new Transition.TransitionListenerAdapter() { 165 @Override 166 public void onTransitionEnd(Transition transition) { 167 transition.removeListener(this); 168 if (mExitComplete) { 169 delayCancel(); 170 } 171 } 172 }); 173 final ArrayList<View> sharedElementSnapshots = createSnapshots(mExitSharedElementBundle, 174 mSharedElementNames); 175 decorView.getViewTreeObserver() 176 .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { 177 @Override 178 public boolean onPreDraw() { 179 decorView.getViewTreeObserver().removeOnPreDrawListener(this); 180 setSharedElementState(mExitSharedElementBundle, sharedElementSnapshots); 181 return true; 182 } 183 }); 184 setGhostVisibility(View.INVISIBLE); 185 scheduleGhostVisibilityChange(View.INVISIBLE); 186 if (mListener != null) { 187 mListener.onSharedElementEnd(mSharedElementNames, mSharedElements, 188 sharedElementSnapshots); 189 } 190 TransitionManager.beginDelayedTransition(decorView, transition); 191 scheduleGhostVisibilityChange(View.VISIBLE); 192 setGhostVisibility(View.VISIBLE); 193 decorView.invalidate(); 194 } 195 196 private void hideSharedElements() { 197 moveSharedElementsFromOverlay(); 198 if (!mIsHidden) { 199 hideViews(mSharedElements); 200 } 201 mSharedElementsHidden = true; 202 finishIfNecessary(); 203 } 204 205 public void startExit() { 206 if (!mIsExitStarted) { 207 mIsExitStarted = true; 208 ViewGroup decorView = getDecor(); 209 if (decorView != null) { 210 decorView.suppressLayout(true); 211 } 212 moveSharedElementsToOverlay(); 213 startTransition(new Runnable() { 214 @Override 215 public void run() { 216 beginTransitions(); 217 } 218 }); 219 } 220 } 221 222 public void startExit(int resultCode, Intent data) { 223 if (!mIsExitStarted) { 224 mIsExitStarted = true; 225 ViewGroup decorView = getDecor(); 226 if (decorView != null) { 227 decorView.suppressLayout(true); 228 } 229 mHandler = new Handler() { 230 @Override 231 public void handleMessage(Message msg) { 232 mIsCanceled = true; 233 finish(); 234 } 235 }; 236 delayCancel(); 237 moveSharedElementsToOverlay(); 238 if (decorView != null && decorView.getBackground() == null) { 239 getWindow().setBackgroundDrawable(new ColorDrawable(Color.BLACK)); 240 } 241 ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(mActivity, this, 242 mAllSharedElementNames, resultCode, data); 243 mActivity.convertToTranslucent(new Activity.TranslucentConversionListener() { 244 @Override 245 public void onTranslucentConversionComplete(boolean drawComplete) { 246 if (!mIsCanceled) { 247 fadeOutBackground(); 248 } 249 } 250 }, options); 251 startTransition(new Runnable() { 252 @Override 253 public void run() { 254 startExitTransition(); 255 } 256 }); 257 } 258 } 259 260 public void stop() { 261 if (mIsReturning && mActivity != null) { 262 // Override the previous ActivityOptions. We don't want the 263 // activity to have options since we're essentially canceling the 264 // transition and finishing right now. 265 mActivity.convertToTranslucent(null, null); 266 finish(); 267 } 268 } 269 270 private void startExitTransition() { 271 Transition transition = getExitTransition(); 272 ViewGroup decorView = getDecor(); 273 if (transition != null && decorView != null && mTransitioningViews != null) { 274 TransitionManager.beginDelayedTransition(decorView, transition); 275 mTransitioningViews.get(0).invalidate(); 276 } else { 277 transitionStarted(); 278 } 279 } 280 281 private void fadeOutBackground() { 282 if (mBackgroundAnimator == null) { 283 ViewGroup decor = getDecor(); 284 Drawable background; 285 if (decor != null && (background = decor.getBackground()) != null) { 286 background = background.mutate(); 287 getWindow().setBackgroundDrawable(background); 288 mBackgroundAnimator = ObjectAnimator.ofInt(background, "alpha", 0); 289 mBackgroundAnimator.addListener(new AnimatorListenerAdapter() { 290 @Override 291 public void onAnimationEnd(Animator animation) { 292 mBackgroundAnimator = null; 293 if (!mIsCanceled) { 294 mIsBackgroundReady = true; 295 notifyComplete(); 296 } 297 } 298 }); 299 mBackgroundAnimator.setDuration(getFadeDuration()); 300 mBackgroundAnimator.start(); 301 } else { 302 mIsBackgroundReady = true; 303 } 304 } 305 } 306 307 private Transition getExitTransition() { 308 Transition viewsTransition = null; 309 if (mTransitioningViews != null && !mTransitioningViews.isEmpty()) { 310 viewsTransition = configureTransition(getViewsTransition(), true); 311 } 312 if (viewsTransition == null) { 313 exitTransitionComplete(); 314 } else { 315 final ArrayList<View> transitioningViews = mTransitioningViews; 316 viewsTransition.addListener(new ContinueTransitionListener() { 317 @Override 318 public void onTransitionEnd(Transition transition) { 319 transition.removeListener(this); 320 exitTransitionComplete(); 321 if (mIsHidden && transitioningViews != null) { 322 showViews(transitioningViews, true); 323 } 324 if (mSharedElementBundle != null) { 325 delayCancel(); 326 } 327 super.onTransitionEnd(transition); 328 } 329 }); 330 viewsTransition.forceVisibility(View.INVISIBLE, false); 331 } 332 return viewsTransition; 333 } 334 335 private Transition getSharedElementExitTransition() { 336 Transition sharedElementTransition = null; 337 if (!mSharedElements.isEmpty()) { 338 sharedElementTransition = configureTransition(getSharedElementTransition(), false); 339 } 340 if (sharedElementTransition == null) { 341 sharedElementTransitionComplete(); 342 } else { 343 sharedElementTransition.addListener(new ContinueTransitionListener() { 344 @Override 345 public void onTransitionEnd(Transition transition) { 346 transition.removeListener(this); 347 sharedElementTransitionComplete(); 348 if (mIsHidden) { 349 showViews(mSharedElements, true); 350 } 351 } 352 }); 353 mSharedElements.get(0).invalidate(); 354 } 355 return sharedElementTransition; 356 } 357 358 private void beginTransitions() { 359 Transition sharedElementTransition = getSharedElementExitTransition(); 360 Transition viewsTransition = getExitTransition(); 361 362 Transition transition = mergeTransitions(sharedElementTransition, viewsTransition); 363 ViewGroup decorView = getDecor(); 364 if (transition != null && decorView != null) { 365 setGhostVisibility(View.INVISIBLE); 366 scheduleGhostVisibilityChange(View.INVISIBLE); 367 TransitionManager.beginDelayedTransition(decorView, transition); 368 scheduleGhostVisibilityChange(View.VISIBLE); 369 setGhostVisibility(View.VISIBLE); 370 decorView.invalidate(); 371 } else { 372 transitionStarted(); 373 } 374 } 375 376 private void exitTransitionComplete() { 377 mExitComplete = true; 378 notifyComplete(); 379 } 380 381 protected boolean isReadyToNotify() { 382 return mSharedElementBundle != null && mResultReceiver != null && mIsBackgroundReady; 383 } 384 385 private void sharedElementTransitionComplete() { 386 mSharedElementBundle = mExitSharedElementBundle == null 387 ? captureSharedElementState() : captureExitSharedElementsState(); 388 notifyComplete(); 389 } 390 391 private Bundle captureExitSharedElementsState() { 392 Bundle bundle = new Bundle(); 393 RectF bounds = new RectF(); 394 Matrix matrix = new Matrix(); 395 for (int i = 0; i < mSharedElements.size(); i++) { 396 String name = mSharedElementNames.get(i); 397 Bundle sharedElementState = mExitSharedElementBundle.getBundle(name); 398 if (sharedElementState != null) { 399 bundle.putBundle(name, sharedElementState); 400 } else { 401 View view = mSharedElements.get(i); 402 captureSharedElementState(view, name, bundle, matrix, bounds); 403 } 404 } 405 return bundle; 406 } 407 408 protected void notifyComplete() { 409 if (isReadyToNotify()) { 410 if (!mSharedElementNotified) { 411 mSharedElementNotified = true; 412 delayCancel(); 413 if (mListener == null) { 414 mResultReceiver.send(MSG_TAKE_SHARED_ELEMENTS, mSharedElementBundle); 415 notifyExitComplete(); 416 } else { 417 final ResultReceiver resultReceiver = mResultReceiver; 418 final Bundle sharedElementBundle = mSharedElementBundle; 419 mListener.onSharedElementsArrived(mSharedElementNames, mSharedElements, 420 new OnSharedElementsReadyListener() { 421 @Override 422 public void onSharedElementsReady() { 423 resultReceiver.send(MSG_TAKE_SHARED_ELEMENTS, 424 sharedElementBundle); 425 notifyExitComplete(); 426 } 427 }); 428 } 429 } else { 430 notifyExitComplete(); 431 } 432 } 433 } 434 435 private void notifyExitComplete() { 436 if (!mExitNotified && mExitComplete) { 437 mExitNotified = true; 438 mResultReceiver.send(MSG_EXIT_TRANSITION_COMPLETE, null); 439 mResultReceiver = null; // done talking 440 ViewGroup decorView = getDecor(); 441 if (!mIsReturning && decorView != null) { 442 decorView.suppressLayout(false); 443 } 444 finishIfNecessary(); 445 } 446 } 447 448 private void finishIfNecessary() { 449 if (mIsReturning && mExitNotified && mActivity != null && (mSharedElements.isEmpty() || 450 mSharedElementsHidden)) { 451 finish(); 452 } 453 if (!mIsReturning && mExitNotified) { 454 mActivity = null; // don't need it anymore 455 } 456 } 457 458 private void finish() { 459 stopCancel(); 460 if (mActivity != null) { 461 mActivity.mActivityTransitionState.clear(); 462 mActivity.finish(); 463 mActivity.overridePendingTransition(0, 0); 464 mActivity = null; 465 } 466 // Clear the state so that we can't hold any references accidentally and leak memory. 467 mHandler = null; 468 mSharedElementBundle = null; 469 if (mBackgroundAnimator != null) { 470 mBackgroundAnimator.cancel(); 471 mBackgroundAnimator = null; 472 } 473 mExitSharedElementBundle = null; 474 clearState(); 475 } 476 477 @Override 478 protected boolean moveSharedElementWithParent() { 479 return !mIsReturning; 480 } 481 482 @Override 483 protected Transition getViewsTransition() { 484 if (mIsReturning) { 485 return getWindow().getReturnTransition(); 486 } else { 487 return getWindow().getExitTransition(); 488 } 489 } 490 491 protected Transition getSharedElementTransition() { 492 if (mIsReturning) { 493 return getWindow().getSharedElementReturnTransition(); 494 } else { 495 return getWindow().getSharedElementExitTransition(); 496 } 497 } 498} 499