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