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 */ 16 package android.app; 17 18 import android.animation.Animator; 19 import android.animation.AnimatorListenerAdapter; 20 import android.animation.ObjectAnimator; 21 import android.app.SharedElementCallback.OnSharedElementsReadyListener; 22 import android.graphics.Color; 23 import android.graphics.drawable.ColorDrawable; 24 import android.graphics.drawable.Drawable; 25 import android.os.Bundle; 26 import android.os.ResultReceiver; 27 import android.text.TextUtils; 28 import android.transition.Transition; 29 import android.transition.TransitionListenerAdapter; 30 import android.transition.TransitionManager; 31 import android.util.ArrayMap; 32 import android.view.View; 33 import android.view.ViewGroup; 34 import android.view.ViewGroupOverlay; 35 import android.view.ViewTreeObserver; 36 import android.view.Window; 37 import android.view.accessibility.AccessibilityEvent; 38 39 import com.android.internal.view.OneShotPreDrawListener; 40 41 import java.util.ArrayList; 42 43 /** 44 * This ActivityTransitionCoordinator is created by the Activity to manage 45 * the enter scene and shared element transfer into the Scene, either during 46 * launch of an Activity or returning from a launched Activity. 47 */ 48 class EnterTransitionCoordinator extends ActivityTransitionCoordinator { 49 private static final String TAG = "EnterTransitionCoordinator"; 50 51 private static final int MIN_ANIMATION_FRAMES = 2; 52 53 private boolean mSharedElementTransitionStarted; 54 private Activity mActivity; 55 private boolean mIsTaskRoot; 56 private boolean mHasStopped; 57 private boolean mIsCanceled; 58 private ObjectAnimator mBackgroundAnimator; 59 private boolean mIsExitTransitionComplete; 60 private boolean mIsReadyForTransition; 61 private Bundle mSharedElementsBundle; 62 private boolean mWasOpaque; 63 private boolean mAreViewsReady; 64 private boolean mIsViewsTransitionStarted; 65 private Transition mEnterViewsTransition; 66 private OneShotPreDrawListener mViewsReadyListener; 67 private final boolean mIsCrossTask; 68 private Drawable mReplacedBackground; 69 private ArrayList<String> mPendingExitNames; 70 private Runnable mOnTransitionComplete; 71 EnterTransitionCoordinator(Activity activity, ResultReceiver resultReceiver, ArrayList<String> sharedElementNames, boolean isReturning, boolean isCrossTask)72 EnterTransitionCoordinator(Activity activity, ResultReceiver resultReceiver, 73 ArrayList<String> sharedElementNames, boolean isReturning, boolean isCrossTask) { 74 super(activity.getWindow(), sharedElementNames, 75 getListener(activity, isReturning && !isCrossTask), isReturning); 76 mActivity = activity; 77 mIsCrossTask = isCrossTask; 78 setResultReceiver(resultReceiver); 79 prepareEnter(); 80 Bundle resultReceiverBundle = new Bundle(); 81 resultReceiverBundle.putParcelable(KEY_REMOTE_RECEIVER, this); 82 mResultReceiver.send(MSG_SET_REMOTE_RECEIVER, resultReceiverBundle); 83 final View decorView = getDecor(); 84 if (decorView != null) { 85 final ViewTreeObserver viewTreeObserver = decorView.getViewTreeObserver(); 86 viewTreeObserver.addOnPreDrawListener( 87 new ViewTreeObserver.OnPreDrawListener() { 88 @Override 89 public boolean onPreDraw() { 90 if (mIsReadyForTransition) { 91 if (viewTreeObserver.isAlive()) { 92 viewTreeObserver.removeOnPreDrawListener(this); 93 } else { 94 decorView.getViewTreeObserver().removeOnPreDrawListener(this); 95 } 96 } 97 return false; 98 } 99 }); 100 } 101 } 102 isCrossTask()103 boolean isCrossTask() { 104 return mIsCrossTask; 105 } 106 viewInstancesReady(ArrayList<String> accepted, ArrayList<String> localNames, ArrayList<View> localViews)107 public void viewInstancesReady(ArrayList<String> accepted, ArrayList<String> localNames, 108 ArrayList<View> localViews) { 109 boolean remap = false; 110 for (int i = 0; i < localViews.size(); i++) { 111 View view = localViews.get(i); 112 if (!TextUtils.equals(view.getTransitionName(), localNames.get(i)) 113 || !view.isAttachedToWindow()) { 114 remap = true; 115 break; 116 } 117 } 118 if (remap) { 119 triggerViewsReady(mapNamedElements(accepted, localNames)); 120 } else { 121 triggerViewsReady(mapSharedElements(accepted, localViews)); 122 } 123 } 124 namedViewsReady(ArrayList<String> accepted, ArrayList<String> localNames)125 public void namedViewsReady(ArrayList<String> accepted, ArrayList<String> localNames) { 126 triggerViewsReady(mapNamedElements(accepted, localNames)); 127 } 128 getEnterViewsTransition()129 public Transition getEnterViewsTransition() { 130 return mEnterViewsTransition; 131 } 132 133 @Override viewsReady(ArrayMap<String, View> sharedElements)134 protected void viewsReady(ArrayMap<String, View> sharedElements) { 135 super.viewsReady(sharedElements); 136 mIsReadyForTransition = true; 137 hideViews(mSharedElements); 138 Transition viewsTransition = getViewsTransition(); 139 if (viewsTransition != null && mTransitioningViews != null) { 140 removeExcludedViews(viewsTransition, mTransitioningViews); 141 stripOffscreenViews(); 142 hideViews(mTransitioningViews); 143 } 144 if (mIsReturning) { 145 sendSharedElementDestination(); 146 } else { 147 moveSharedElementsToOverlay(); 148 } 149 if (mSharedElementsBundle != null) { 150 onTakeSharedElements(); 151 } 152 } 153 triggerViewsReady(final ArrayMap<String, View> sharedElements)154 private void triggerViewsReady(final ArrayMap<String, View> sharedElements) { 155 if (mAreViewsReady) { 156 return; 157 } 158 mAreViewsReady = true; 159 final ViewGroup decor = getDecor(); 160 // Ensure the views have been laid out before capturing the views -- we need the epicenter. 161 if (decor == null || (decor.isAttachedToWindow() && 162 (sharedElements.isEmpty() || !sharedElements.valueAt(0).isLayoutRequested()))) { 163 viewsReady(sharedElements); 164 } else { 165 mViewsReadyListener = OneShotPreDrawListener.add(decor, () -> { 166 mViewsReadyListener = null; 167 viewsReady(sharedElements); 168 }); 169 decor.invalidate(); 170 } 171 } 172 mapNamedElements(ArrayList<String> accepted, ArrayList<String> localNames)173 private ArrayMap<String, View> mapNamedElements(ArrayList<String> accepted, 174 ArrayList<String> localNames) { 175 ArrayMap<String, View> sharedElements = new ArrayMap<String, View>(); 176 ViewGroup decorView = getDecor(); 177 if (decorView != null) { 178 decorView.findNamedViews(sharedElements); 179 } 180 if (accepted != null) { 181 for (int i = 0; i < localNames.size(); i++) { 182 String localName = localNames.get(i); 183 String acceptedName = accepted.get(i); 184 if (localName != null && !localName.equals(acceptedName)) { 185 View view = sharedElements.get(localName); 186 if (view != null) { 187 sharedElements.put(acceptedName, view); 188 } 189 } 190 } 191 } 192 return sharedElements; 193 } 194 sendSharedElementDestination()195 private void sendSharedElementDestination() { 196 boolean allReady; 197 final View decorView = getDecor(); 198 if (allowOverlappingTransitions() && getEnterViewsTransition() != null) { 199 allReady = false; 200 } else if (decorView == null) { 201 allReady = true; 202 } else { 203 allReady = !decorView.isLayoutRequested(); 204 if (allReady) { 205 for (int i = 0; i < mSharedElements.size(); i++) { 206 if (mSharedElements.get(i).isLayoutRequested()) { 207 allReady = false; 208 break; 209 } 210 } 211 } 212 } 213 if (allReady) { 214 Bundle state = captureSharedElementState(); 215 moveSharedElementsToOverlay(); 216 mResultReceiver.send(MSG_SHARED_ELEMENT_DESTINATION, state); 217 } else if (decorView != null) { 218 OneShotPreDrawListener.add(decorView, () -> { 219 if (mResultReceiver != null) { 220 Bundle state = captureSharedElementState(); 221 moveSharedElementsToOverlay(); 222 mResultReceiver.send(MSG_SHARED_ELEMENT_DESTINATION, state); 223 } 224 }); 225 } 226 if (allowOverlappingTransitions()) { 227 startEnterTransitionOnly(); 228 } 229 } 230 getListener(Activity activity, boolean isReturning)231 private static SharedElementCallback getListener(Activity activity, boolean isReturning) { 232 return isReturning ? activity.mExitTransitionListener : activity.mEnterTransitionListener; 233 } 234 235 @Override onReceiveResult(int resultCode, Bundle resultData)236 protected void onReceiveResult(int resultCode, Bundle resultData) { 237 switch (resultCode) { 238 case MSG_TAKE_SHARED_ELEMENTS: 239 if (!mIsCanceled) { 240 mSharedElementsBundle = resultData; 241 onTakeSharedElements(); 242 } 243 break; 244 case MSG_EXIT_TRANSITION_COMPLETE: 245 if (!mIsCanceled) { 246 mIsExitTransitionComplete = true; 247 if (mSharedElementTransitionStarted) { 248 onRemoteExitTransitionComplete(); 249 } 250 } 251 break; 252 case MSG_CANCEL: 253 cancel(); 254 break; 255 case MSG_ALLOW_RETURN_TRANSITION: 256 if (!mIsCanceled && !mIsTaskRoot) { 257 mPendingExitNames = mAllSharedElementNames; 258 } 259 break; 260 } 261 } 262 isWaitingForRemoteExit()263 public boolean isWaitingForRemoteExit() { 264 return mIsReturning && mResultReceiver != null; 265 } 266 getPendingExitSharedElementNames()267 public ArrayList<String> getPendingExitSharedElementNames() { 268 return mPendingExitNames; 269 } 270 271 /** 272 * This is called onResume. If an Activity is resuming and the transitions 273 * haven't started yet, force the views to appear. This is likely to be 274 * caused by the top Activity finishing before the transitions started. 275 * In that case, we can finish any transition that was started, but we 276 * should cancel any pending transition and just bring those Views visible. 277 */ forceViewsToAppear()278 public void forceViewsToAppear() { 279 if (!mIsReturning) { 280 return; 281 } 282 if (!mIsReadyForTransition) { 283 mIsReadyForTransition = true; 284 final ViewGroup decor = getDecor(); 285 if (decor != null && mViewsReadyListener != null) { 286 mViewsReadyListener.removeListener(); 287 mViewsReadyListener = null; 288 } 289 showViews(mTransitioningViews, true); 290 setTransitioningViewsVisiblity(View.VISIBLE, true); 291 mSharedElements.clear(); 292 mAllSharedElementNames.clear(); 293 mTransitioningViews.clear(); 294 mIsReadyForTransition = true; 295 viewsTransitionComplete(); 296 sharedElementTransitionComplete(); 297 } else { 298 if (!mSharedElementTransitionStarted) { 299 moveSharedElementsFromOverlay(); 300 mSharedElementTransitionStarted = true; 301 showViews(mSharedElements, true); 302 mSharedElements.clear(); 303 sharedElementTransitionComplete(); 304 } 305 if (!mIsViewsTransitionStarted) { 306 mIsViewsTransitionStarted = true; 307 showViews(mTransitioningViews, true); 308 setTransitioningViewsVisiblity(View.VISIBLE, true); 309 mTransitioningViews.clear(); 310 viewsTransitionComplete(); 311 } 312 cancelPendingTransitions(); 313 } 314 mAreViewsReady = true; 315 if (mResultReceiver != null) { 316 mResultReceiver.send(MSG_CANCEL, null); 317 mResultReceiver = null; 318 } 319 } 320 cancel()321 private void cancel() { 322 if (!mIsCanceled) { 323 mIsCanceled = true; 324 if (getViewsTransition() == null || mIsViewsTransitionStarted) { 325 showViews(mSharedElements, true); 326 } else if (mTransitioningViews != null) { 327 mTransitioningViews.addAll(mSharedElements); 328 } 329 moveSharedElementsFromOverlay(); 330 mSharedElementNames.clear(); 331 mSharedElements.clear(); 332 mAllSharedElementNames.clear(); 333 startSharedElementTransition(null); 334 onRemoteExitTransitionComplete(); 335 } 336 } 337 isReturning()338 public boolean isReturning() { 339 return mIsReturning; 340 } 341 prepareEnter()342 protected void prepareEnter() { 343 ViewGroup decorView = getDecor(); 344 if (mActivity == null || decorView == null) { 345 return; 346 } 347 348 mIsTaskRoot = mActivity.isTaskRoot(); 349 350 if (!isCrossTask()) { 351 mActivity.overridePendingTransition(0, 0); 352 } 353 if (!mIsReturning) { 354 mWasOpaque = mActivity.convertToTranslucent(null, null); 355 Drawable background = decorView.getBackground(); 356 if (background == null) { 357 background = new ColorDrawable(Color.TRANSPARENT); 358 mReplacedBackground = background; 359 } else { 360 getWindow().setBackgroundDrawable(null); 361 background = background.mutate(); 362 background.setAlpha(0); 363 } 364 getWindow().setBackgroundDrawable(background); 365 } else { 366 mActivity = null; // all done with it now. 367 } 368 } 369 370 @Override getViewsTransition()371 protected Transition getViewsTransition() { 372 Window window = getWindow(); 373 if (window == null) { 374 return null; 375 } 376 if (mIsReturning) { 377 return window.getReenterTransition(); 378 } else { 379 return window.getEnterTransition(); 380 } 381 } 382 getSharedElementTransition()383 protected Transition getSharedElementTransition() { 384 Window window = getWindow(); 385 if (window == null) { 386 return null; 387 } 388 if (mIsReturning) { 389 return window.getSharedElementReenterTransition(); 390 } else { 391 return window.getSharedElementEnterTransition(); 392 } 393 } 394 startSharedElementTransition(Bundle sharedElementState)395 private void startSharedElementTransition(Bundle sharedElementState) { 396 ViewGroup decorView = getDecor(); 397 if (decorView == null) { 398 return; 399 } 400 // Remove rejected shared elements 401 ArrayList<String> rejectedNames = new ArrayList<String>(mAllSharedElementNames); 402 rejectedNames.removeAll(mSharedElementNames); 403 ArrayList<View> rejectedSnapshots = createSnapshots(sharedElementState, rejectedNames); 404 if (mListener != null) { 405 mListener.onRejectSharedElements(rejectedSnapshots); 406 } 407 removeNullViews(rejectedSnapshots); 408 startRejectedAnimations(rejectedSnapshots); 409 410 // Now start shared element transition 411 ArrayList<View> sharedElementSnapshots = createSnapshots(sharedElementState, 412 mSharedElementNames); 413 showViews(mSharedElements, true); 414 scheduleSetSharedElementEnd(sharedElementSnapshots); 415 ArrayList<SharedElementOriginalState> originalImageViewState = 416 setSharedElementState(sharedElementState, sharedElementSnapshots); 417 requestLayoutForSharedElements(); 418 419 boolean startEnterTransition = allowOverlappingTransitions() && !mIsReturning; 420 boolean startSharedElementTransition = true; 421 setGhostVisibility(View.INVISIBLE); 422 scheduleGhostVisibilityChange(View.INVISIBLE); 423 pauseInput(); 424 Transition transition = beginTransition(decorView, startEnterTransition, 425 startSharedElementTransition); 426 scheduleGhostVisibilityChange(View.VISIBLE); 427 setGhostVisibility(View.VISIBLE); 428 429 if (startEnterTransition) { 430 startEnterTransition(transition); 431 } 432 433 setOriginalSharedElementState(mSharedElements, originalImageViewState); 434 435 if (mResultReceiver != null) { 436 // We can't trust that the view will disappear on the same frame that the shared 437 // element appears here. Assure that we get at least 2 frames for double-buffering. 438 decorView.postOnAnimation(new Runnable() { 439 int mAnimations; 440 441 @Override 442 public void run() { 443 if (mAnimations++ < MIN_ANIMATION_FRAMES) { 444 View decorView = getDecor(); 445 if (decorView != null) { 446 decorView.postOnAnimation(this); 447 } 448 } else if (mResultReceiver != null) { 449 mResultReceiver.send(MSG_HIDE_SHARED_ELEMENTS, null); 450 mResultReceiver = null; // all done sending messages. 451 } 452 } 453 }); 454 } 455 } 456 removeNullViews(ArrayList<View> views)457 private static void removeNullViews(ArrayList<View> views) { 458 if (views != null) { 459 for (int i = views.size() - 1; i >= 0; i--) { 460 if (views.get(i) == null) { 461 views.remove(i); 462 } 463 } 464 } 465 } 466 onTakeSharedElements()467 private void onTakeSharedElements() { 468 if (!mIsReadyForTransition || mSharedElementsBundle == null) { 469 return; 470 } 471 final Bundle sharedElementState = mSharedElementsBundle; 472 mSharedElementsBundle = null; 473 OnSharedElementsReadyListener listener = new OnSharedElementsReadyListener() { 474 @Override 475 public void onSharedElementsReady() { 476 final View decorView = getDecor(); 477 if (decorView != null) { 478 OneShotPreDrawListener.add(decorView, false, () -> { 479 startTransition(() -> { 480 startSharedElementTransition(sharedElementState); 481 }); 482 }); 483 decorView.invalidate(); 484 } 485 } 486 }; 487 if (mListener == null) { 488 listener.onSharedElementsReady(); 489 } else { 490 mListener.onSharedElementsArrived(mSharedElementNames, mSharedElements, listener); 491 } 492 } 493 requestLayoutForSharedElements()494 private void requestLayoutForSharedElements() { 495 int numSharedElements = mSharedElements.size(); 496 for (int i = 0; i < numSharedElements; i++) { 497 mSharedElements.get(i).requestLayout(); 498 } 499 } 500 beginTransition(ViewGroup decorView, boolean startEnterTransition, boolean startSharedElementTransition)501 private Transition beginTransition(ViewGroup decorView, boolean startEnterTransition, 502 boolean startSharedElementTransition) { 503 Transition sharedElementTransition = null; 504 if (startSharedElementTransition) { 505 if (!mSharedElementNames.isEmpty()) { 506 sharedElementTransition = configureTransition(getSharedElementTransition(), false); 507 } 508 if (sharedElementTransition == null) { 509 sharedElementTransitionStarted(); 510 sharedElementTransitionComplete(); 511 } else { 512 sharedElementTransition.addListener(new TransitionListenerAdapter() { 513 @Override 514 public void onTransitionStart(Transition transition) { 515 sharedElementTransitionStarted(); 516 } 517 518 @Override 519 public void onTransitionEnd(Transition transition) { 520 transition.removeListener(this); 521 sharedElementTransitionComplete(); 522 } 523 }); 524 } 525 } 526 Transition viewsTransition = null; 527 if (startEnterTransition) { 528 mIsViewsTransitionStarted = true; 529 if (mTransitioningViews != null && !mTransitioningViews.isEmpty()) { 530 viewsTransition = configureTransition(getViewsTransition(), true); 531 } 532 if (viewsTransition == null) { 533 viewsTransitionComplete(); 534 } else { 535 final ArrayList<View> transitioningViews = mTransitioningViews; 536 viewsTransition.addListener(new ContinueTransitionListener() { 537 @Override 538 public void onTransitionStart(Transition transition) { 539 mEnterViewsTransition = transition; 540 if (transitioningViews != null) { 541 showViews(transitioningViews, false); 542 } 543 super.onTransitionStart(transition); 544 } 545 546 @Override 547 public void onTransitionEnd(Transition transition) { 548 mEnterViewsTransition = null; 549 transition.removeListener(this); 550 viewsTransitionComplete(); 551 super.onTransitionEnd(transition); 552 } 553 }); 554 } 555 } 556 557 Transition transition = mergeTransitions(sharedElementTransition, viewsTransition); 558 if (transition != null) { 559 transition.addListener(new ContinueTransitionListener()); 560 if (startEnterTransition) { 561 setTransitioningViewsVisiblity(View.INVISIBLE, false); 562 } 563 TransitionManager.beginDelayedTransition(decorView, transition); 564 if (startEnterTransition) { 565 setTransitioningViewsVisiblity(View.VISIBLE, false); 566 } 567 decorView.invalidate(); 568 } else { 569 transitionStarted(); 570 } 571 return transition; 572 } 573 runAfterTransitionsComplete(Runnable onTransitionComplete)574 public void runAfterTransitionsComplete(Runnable onTransitionComplete) { 575 if (!isTransitionRunning()) { 576 onTransitionsComplete(); 577 } else { 578 mOnTransitionComplete = onTransitionComplete; 579 } 580 } 581 582 @Override onTransitionsComplete()583 protected void onTransitionsComplete() { 584 moveSharedElementsFromOverlay(); 585 final ViewGroup decorView = getDecor(); 586 if (decorView != null) { 587 decorView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); 588 589 Window window = getWindow(); 590 if (window != null && mReplacedBackground == decorView.getBackground()) { 591 window.setBackgroundDrawable(null); 592 } 593 } 594 if (mOnTransitionComplete != null) { 595 mOnTransitionComplete.run(); 596 mOnTransitionComplete = null; 597 } 598 } 599 sharedElementTransitionStarted()600 private void sharedElementTransitionStarted() { 601 mSharedElementTransitionStarted = true; 602 if (mIsExitTransitionComplete) { 603 send(MSG_EXIT_TRANSITION_COMPLETE, null); 604 } 605 } 606 startEnterTransition(Transition transition)607 private void startEnterTransition(Transition transition) { 608 ViewGroup decorView = getDecor(); 609 if (!mIsReturning && decorView != null) { 610 Drawable background = decorView.getBackground(); 611 if (background != null) { 612 background = background.mutate(); 613 getWindow().setBackgroundDrawable(background); 614 mBackgroundAnimator = ObjectAnimator.ofInt(background, "alpha", 255); 615 mBackgroundAnimator.setDuration(getFadeDuration()); 616 mBackgroundAnimator.addListener(new AnimatorListenerAdapter() { 617 @Override 618 public void onAnimationEnd(Animator animation) { 619 makeOpaque(); 620 backgroundAnimatorComplete(); 621 } 622 }); 623 mBackgroundAnimator.start(); 624 } else if (transition != null) { 625 transition.addListener(new TransitionListenerAdapter() { 626 @Override 627 public void onTransitionEnd(Transition transition) { 628 transition.removeListener(this); 629 makeOpaque(); 630 } 631 }); 632 backgroundAnimatorComplete(); 633 } else { 634 makeOpaque(); 635 backgroundAnimatorComplete(); 636 } 637 } else { 638 backgroundAnimatorComplete(); 639 } 640 } 641 stop()642 public void stop() { 643 // Restore the background to its previous state since the 644 // Activity is stopping. 645 if (mBackgroundAnimator != null) { 646 mBackgroundAnimator.end(); 647 mBackgroundAnimator = null; 648 } else if (mWasOpaque) { 649 ViewGroup decorView = getDecor(); 650 if (decorView != null) { 651 Drawable drawable = decorView.getBackground(); 652 if (drawable != null) { 653 drawable.setAlpha(1); 654 } 655 } 656 } 657 makeOpaque(); 658 mIsCanceled = true; 659 mResultReceiver = null; 660 mActivity = null; 661 moveSharedElementsFromOverlay(); 662 if (mTransitioningViews != null) { 663 showViews(mTransitioningViews, true); 664 setTransitioningViewsVisiblity(View.VISIBLE, true); 665 } 666 showViews(mSharedElements, true); 667 clearState(); 668 } 669 670 /** 671 * Cancels the enter transition. 672 * @return True if the enter transition is still pending capturing the target state. If so, 673 * any transition started on the decor will do nothing. 674 */ cancelEnter()675 public boolean cancelEnter() { 676 setGhostVisibility(View.INVISIBLE); 677 mHasStopped = true; 678 mIsCanceled = true; 679 clearState(); 680 return super.cancelPendingTransitions(); 681 } 682 683 @Override clearState()684 protected void clearState() { 685 mSharedElementsBundle = null; 686 mEnterViewsTransition = null; 687 mResultReceiver = null; 688 if (mBackgroundAnimator != null) { 689 mBackgroundAnimator.cancel(); 690 mBackgroundAnimator = null; 691 } 692 if (mOnTransitionComplete != null) { 693 mOnTransitionComplete.run(); 694 mOnTransitionComplete = null; 695 } 696 super.clearState(); 697 } 698 makeOpaque()699 private void makeOpaque() { 700 if (!mHasStopped && mActivity != null) { 701 if (mWasOpaque) { 702 mActivity.convertFromTranslucent(); 703 } 704 mActivity = null; 705 } 706 } 707 allowOverlappingTransitions()708 private boolean allowOverlappingTransitions() { 709 return mIsReturning ? getWindow().getAllowReturnTransitionOverlap() 710 : getWindow().getAllowEnterTransitionOverlap(); 711 } 712 startRejectedAnimations(final ArrayList<View> rejectedSnapshots)713 private void startRejectedAnimations(final ArrayList<View> rejectedSnapshots) { 714 if (rejectedSnapshots == null || rejectedSnapshots.isEmpty()) { 715 return; 716 } 717 final ViewGroup decorView = getDecor(); 718 if (decorView != null) { 719 ViewGroupOverlay overlay = decorView.getOverlay(); 720 ObjectAnimator animator = null; 721 int numRejected = rejectedSnapshots.size(); 722 for (int i = 0; i < numRejected; i++) { 723 View snapshot = rejectedSnapshots.get(i); 724 overlay.add(snapshot); 725 animator = ObjectAnimator.ofFloat(snapshot, View.ALPHA, 1, 0); 726 animator.start(); 727 } 728 animator.addListener(new AnimatorListenerAdapter() { 729 @Override 730 public void onAnimationEnd(Animator animation) { 731 ViewGroupOverlay overlay = decorView.getOverlay(); 732 int numRejected = rejectedSnapshots.size(); 733 for (int i = 0; i < numRejected; i++) { 734 overlay.remove(rejectedSnapshots.get(i)); 735 } 736 } 737 }); 738 } 739 } 740 onRemoteExitTransitionComplete()741 protected void onRemoteExitTransitionComplete() { 742 if (!allowOverlappingTransitions()) { 743 startEnterTransitionOnly(); 744 } 745 } 746 startEnterTransitionOnly()747 private void startEnterTransitionOnly() { 748 startTransition(new Runnable() { 749 @Override 750 public void run() { 751 boolean startEnterTransition = true; 752 boolean startSharedElementTransition = false; 753 ViewGroup decorView = getDecor(); 754 if (decorView != null) { 755 Transition transition = beginTransition(decorView, startEnterTransition, 756 startSharedElementTransition); 757 startEnterTransition(transition); 758 } 759 } 760 }); 761 } 762 } 763