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