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