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