EnterTransitionCoordinator.java revision 6558056e8fccc32f9e1dc59e46d09f8d916b7538
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 */ 16package android.app; 17 18import android.animation.Animator; 19import android.animation.AnimatorListenerAdapter; 20import android.animation.ObjectAnimator; 21import android.graphics.Matrix; 22import android.graphics.drawable.Drawable; 23import android.os.Bundle; 24import android.os.ResultReceiver; 25import android.text.TextUtils; 26import android.transition.Transition; 27import android.transition.TransitionManager; 28import android.util.ArrayMap; 29import android.view.View; 30import android.view.ViewGroup; 31import android.view.ViewGroupOverlay; 32import android.view.ViewTreeObserver; 33 34import java.util.ArrayList; 35 36/** 37 * This ActivityTransitionCoordinator is created by the Activity to manage 38 * the enter scene and shared element transfer into the Scene, either during 39 * launch of an Activity or returning from a launched Activity. 40 */ 41class EnterTransitionCoordinator extends ActivityTransitionCoordinator { 42 private static final String TAG = "EnterTransitionCoordinator"; 43 44 private static final int MIN_ANIMATION_FRAMES = 2; 45 46 private boolean mSharedElementTransitionStarted; 47 private Activity mActivity; 48 private boolean mHasStopped; 49 private boolean mIsCanceled; 50 private ObjectAnimator mBackgroundAnimator; 51 private boolean mIsExitTransitionComplete; 52 private boolean mIsReadyForTransition; 53 private Bundle mSharedElementsBundle; 54 private boolean mWasOpaque; 55 private boolean mAreViewsReady; 56 private boolean mIsViewsTransitionStarted; 57 private boolean mIsViewsTransitionComplete; 58 private boolean mIsSharedElementTransitionComplete; 59 private ArrayList<Matrix> mSharedElementParentMatrices; 60 private Transition mEnterViewsTransition; 61 62 public EnterTransitionCoordinator(Activity activity, ResultReceiver resultReceiver, 63 ArrayList<String> sharedElementNames, boolean isReturning) { 64 super(activity.getWindow(), sharedElementNames, 65 getListener(activity, isReturning), isReturning); 66 mActivity = activity; 67 setResultReceiver(resultReceiver); 68 prepareEnter(); 69 Bundle resultReceiverBundle = new Bundle(); 70 resultReceiverBundle.putParcelable(KEY_REMOTE_RECEIVER, this); 71 mResultReceiver.send(MSG_SET_REMOTE_RECEIVER, resultReceiverBundle); 72 getDecor().getViewTreeObserver().addOnPreDrawListener( 73 new ViewTreeObserver.OnPreDrawListener() { 74 @Override 75 public boolean onPreDraw() { 76 if (mIsReadyForTransition) { 77 getDecor().getViewTreeObserver().removeOnPreDrawListener(this); 78 } 79 return mIsReadyForTransition; 80 } 81 }); 82 } 83 84 public void viewInstancesReady(ArrayList<String> accepted, ArrayList<String> localNames, 85 ArrayList<View> localViews) { 86 boolean remap = false; 87 for (int i = 0; i < localViews.size(); i++) { 88 View view = localViews.get(i); 89 if (!TextUtils.equals(view.getTransitionName(), localNames.get(i)) 90 || !view.isAttachedToWindow()) { 91 remap = true; 92 break; 93 } 94 } 95 if (remap) { 96 triggerViewsReady(mapNamedElements(accepted, localNames)); 97 } else { 98 triggerViewsReady(mapSharedElements(accepted, localViews)); 99 } 100 } 101 102 public void namedViewsReady(ArrayList<String> accepted, ArrayList<String> localNames) { 103 triggerViewsReady(mapNamedElements(accepted, localNames)); 104 } 105 106 public Transition getEnterViewsTransition() { 107 return mEnterViewsTransition; 108 } 109 110 @Override 111 protected void viewsReady(ArrayMap<String, View> sharedElements) { 112 super.viewsReady(sharedElements); 113 mIsReadyForTransition = true; 114 setTransitionAlpha(mSharedElements, 0); 115 if (getViewsTransition() != null) { 116 setTransitionAlpha(mTransitioningViews, 0); 117 } 118 if (mIsReturning) { 119 sendSharedElementDestination(); 120 } else { 121 setSharedElementMatrices(); 122 moveSharedElementsToOverlay(); 123 } 124 if (mSharedElementsBundle != null) { 125 onTakeSharedElements(); 126 } 127 } 128 129 private void triggerViewsReady(final ArrayMap<String, View> sharedElements) { 130 if (mAreViewsReady) { 131 return; 132 } 133 mAreViewsReady = true; 134 // Ensure the views have been laid out before capturing the views -- we need the epicenter. 135 if (sharedElements.isEmpty() || !sharedElements.valueAt(0).isLayoutRequested()) { 136 viewsReady(sharedElements); 137 } else { 138 sharedElements.valueAt(0).getViewTreeObserver() 139 .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { 140 @Override 141 public boolean onPreDraw() { 142 sharedElements.valueAt(0).getViewTreeObserver().removeOnPreDrawListener(this); 143 viewsReady(sharedElements); 144 return true; 145 } 146 }); 147 } 148 } 149 150 private ArrayMap<String, View> mapNamedElements(ArrayList<String> accepted, 151 ArrayList<String> localNames) { 152 ArrayMap<String, View> sharedElements = new ArrayMap<String, View>(); 153 getDecor().findNamedViews(sharedElements); 154 if (accepted != null) { 155 for (int i = 0; i < localNames.size(); i++) { 156 String localName = localNames.get(i); 157 String acceptedName = accepted.get(i); 158 if (localName != null && !localName.equals(acceptedName)) { 159 View view = sharedElements.remove(localName); 160 if (view != null) { 161 sharedElements.put(acceptedName, view); 162 } 163 } 164 } 165 } 166 return sharedElements; 167 } 168 169 private void sendSharedElementDestination() { 170 boolean allReady; 171 if (allowOverlappingTransitions() && getEnterViewsTransition() != null) { 172 allReady = false; 173 } else { 174 allReady = !getDecor().isLayoutRequested(); 175 if (allReady) { 176 for (int i = 0; i < mSharedElements.size(); i++) { 177 if (mSharedElements.get(i).isLayoutRequested()) { 178 allReady = false; 179 break; 180 } 181 } 182 } 183 } 184 if (allReady) { 185 Bundle state = captureSharedElementState(); 186 setSharedElementMatrices(); 187 moveSharedElementsToOverlay(); 188 mResultReceiver.send(MSG_SHARED_ELEMENT_DESTINATION, state); 189 } else { 190 getDecor().getViewTreeObserver() 191 .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { 192 @Override 193 public boolean onPreDraw() { 194 getDecor().getViewTreeObserver().removeOnPreDrawListener(this); 195 if (mResultReceiver != null) { 196 Bundle state = captureSharedElementState(); 197 setSharedElementMatrices(); 198 moveSharedElementsToOverlay(); 199 mResultReceiver.send(MSG_SHARED_ELEMENT_DESTINATION, state); 200 } 201 return true; 202 } 203 }); 204 } 205 if (allowOverlappingTransitions()) { 206 startEnterTransitionOnly(); 207 } 208 } 209 210 private static SharedElementCallback getListener(Activity activity, boolean isReturning) { 211 return isReturning ? activity.mExitTransitionListener : activity.mEnterTransitionListener; 212 } 213 214 @Override 215 protected void onReceiveResult(int resultCode, Bundle resultData) { 216 switch (resultCode) { 217 case MSG_TAKE_SHARED_ELEMENTS: 218 if (!mIsCanceled) { 219 mSharedElementsBundle = resultData; 220 onTakeSharedElements(); 221 } 222 break; 223 case MSG_EXIT_TRANSITION_COMPLETE: 224 if (!mIsCanceled) { 225 mIsExitTransitionComplete = true; 226 if (mSharedElementTransitionStarted) { 227 onRemoteExitTransitionComplete(); 228 } 229 } 230 break; 231 case MSG_CANCEL: 232 cancel(); 233 break; 234 } 235 } 236 237 private void cancel() { 238 if (!mIsCanceled) { 239 mIsCanceled = true; 240 if (getViewsTransition() == null || mIsViewsTransitionStarted) { 241 setTransitionAlpha(mSharedElements, 1); 242 } else { 243 mTransitioningViews.addAll(mSharedElements); 244 } 245 mSharedElementNames.clear(); 246 mSharedElements.clear(); 247 mAllSharedElementNames.clear(); 248 startSharedElementTransition(null); 249 onRemoteExitTransitionComplete(); 250 } 251 } 252 253 public boolean isReturning() { 254 return mIsReturning; 255 } 256 257 protected void prepareEnter() { 258 mActivity.overridePendingTransition(0, 0); 259 if (!mIsReturning) { 260 mWasOpaque = mActivity.convertToTranslucent(null, null); 261 Drawable background = getDecor().getBackground(); 262 if (background != null) { 263 getWindow().setBackgroundDrawable(null); 264 background = background.mutate(); 265 background.setAlpha(0); 266 getWindow().setBackgroundDrawable(background); 267 } 268 } else { 269 mActivity = null; // all done with it now. 270 } 271 } 272 273 @Override 274 protected Transition getViewsTransition() { 275 if (mIsReturning) { 276 return getWindow().getReenterTransition(); 277 } else { 278 return getWindow().getEnterTransition(); 279 } 280 } 281 282 protected Transition getSharedElementTransition() { 283 if (mIsReturning) { 284 return getWindow().getSharedElementReenterTransition(); 285 } else { 286 return getWindow().getSharedElementEnterTransition(); 287 } 288 } 289 290 private void startSharedElementTransition(Bundle sharedElementState) { 291 // Remove rejected shared elements 292 ArrayList<String> rejectedNames = new ArrayList<String>(mAllSharedElementNames); 293 rejectedNames.removeAll(mSharedElementNames); 294 ArrayList<View> rejectedSnapshots = createSnapshots(sharedElementState, rejectedNames); 295 mListener.onRejectSharedElements(rejectedSnapshots); 296 startRejectedAnimations(rejectedSnapshots); 297 298 // Now start shared element transition 299 ArrayList<View> sharedElementSnapshots = createSnapshots(sharedElementState, 300 mSharedElementNames); 301 setTransitionAlpha(mSharedElements, 1); 302 scheduleSetSharedElementEnd(sharedElementSnapshots); 303 ArrayList<SharedElementOriginalState> originalImageViewState = 304 setSharedElementState(sharedElementState, sharedElementSnapshots); 305 requestLayoutForSharedElements(); 306 307 boolean startEnterTransition = allowOverlappingTransitions() && !mIsReturning; 308 boolean startSharedElementTransition = true; 309 setGhostVisibility(View.INVISIBLE); 310 scheduleGhostVisibilityChange(View.INVISIBLE); 311 Transition transition = beginTransition(startEnterTransition, startSharedElementTransition); 312 scheduleGhostVisibilityChange(View.VISIBLE); 313 setGhostVisibility(View.VISIBLE); 314 315 if (startEnterTransition) { 316 startEnterTransition(transition); 317 } 318 319 setOriginalSharedElementState(mSharedElements, originalImageViewState); 320 321 if (mResultReceiver != null) { 322 // We can't trust that the view will disappear on the same frame that the shared 323 // element appears here. Assure that we get at least 2 frames for double-buffering. 324 getDecor().postOnAnimation(new Runnable() { 325 int mAnimations; 326 @Override 327 public void run() { 328 if (mAnimations++ < MIN_ANIMATION_FRAMES) { 329 getDecor().postOnAnimation(this); 330 } else if (mResultReceiver != null) { 331 mResultReceiver.send(MSG_HIDE_SHARED_ELEMENTS, null); 332 mResultReceiver = null; // all done sending messages. 333 } 334 } 335 }); 336 } 337 } 338 339 private void onTakeSharedElements() { 340 if (!mIsReadyForTransition || mSharedElementsBundle == null) { 341 return; 342 } 343 final Bundle sharedElementState = mSharedElementsBundle; 344 mSharedElementsBundle = null; 345 getDecor().getViewTreeObserver() 346 .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { 347 @Override 348 public boolean onPreDraw() { 349 getDecor().getViewTreeObserver().removeOnPreDrawListener(this); 350 startTransition(new Runnable() { 351 @Override 352 public void run() { 353 startSharedElementTransition(sharedElementState); 354 } 355 }); 356 return false; 357 } 358 }); 359 getDecor().invalidate(); 360 } 361 362 private void requestLayoutForSharedElements() { 363 int numSharedElements = mSharedElements.size(); 364 for (int i = 0; i < numSharedElements; i++) { 365 mSharedElements.get(i).requestLayout(); 366 } 367 } 368 369 private Transition beginTransition(boolean startEnterTransition, 370 boolean startSharedElementTransition) { 371 Transition sharedElementTransition = null; 372 if (startSharedElementTransition) { 373 if (!mSharedElementNames.isEmpty()) { 374 sharedElementTransition = configureTransition(getSharedElementTransition(), false); 375 } 376 if (sharedElementTransition == null) { 377 sharedElementTransitionStarted(); 378 sharedElementTransitionComplete(); 379 } else { 380 sharedElementTransition.addListener(new Transition.TransitionListenerAdapter() { 381 @Override 382 public void onTransitionStart(Transition transition) { 383 sharedElementTransitionStarted(); 384 } 385 386 @Override 387 public void onTransitionEnd(Transition transition) { 388 transition.removeListener(this); 389 sharedElementTransitionComplete(); 390 } 391 }); 392 } 393 } 394 Transition viewsTransition = null; 395 if (startEnterTransition) { 396 mIsViewsTransitionStarted = true; 397 if (!mTransitioningViews.isEmpty()) { 398 viewsTransition = configureTransition(getViewsTransition(), true); 399 if (viewsTransition != null && !mIsReturning) { 400 stripOffscreenViews(); 401 } 402 } 403 if (viewsTransition == null) { 404 viewTransitionComplete(); 405 } else { 406 viewsTransition.forceVisibility(View.INVISIBLE, true); 407 viewsTransition.addListener(new ContinueTransitionListener() { 408 @Override 409 public void onTransitionStart(Transition transition) { 410 mEnterViewsTransition = transition; 411 setTransitionAlpha(mTransitioningViews, 1); 412 super.onTransitionStart(transition); 413 } 414 415 @Override 416 public void onTransitionEnd(Transition transition) { 417 mEnterViewsTransition = null; 418 transition.removeListener(this); 419 viewTransitionComplete(); 420 super.onTransitionEnd(transition); 421 } 422 }); 423 } 424 } 425 426 Transition transition = mergeTransitions(sharedElementTransition, viewsTransition); 427 if (transition != null) { 428 transition.addListener(new ContinueTransitionListener()); 429 TransitionManager.beginDelayedTransition(getDecor(), transition); 430 if (startSharedElementTransition && !mSharedElementNames.isEmpty()) { 431 mSharedElements.get(0).invalidate(); 432 } else if (startEnterTransition && !mTransitioningViews.isEmpty()) { 433 mTransitioningViews.get(0).invalidate(); 434 } 435 } else { 436 transitionStarted(); 437 } 438 return transition; 439 } 440 441 private void viewTransitionComplete() { 442 mIsViewsTransitionComplete = true; 443 if (mIsSharedElementTransitionComplete) { 444 moveSharedElementsFromOverlay(); 445 } 446 } 447 448 private void sharedElementTransitionComplete() { 449 mIsSharedElementTransitionComplete = true; 450 if (mIsViewsTransitionComplete) { 451 moveSharedElementsFromOverlay(); 452 } 453 } 454 455 private void sharedElementTransitionStarted() { 456 mSharedElementTransitionStarted = true; 457 if (mIsExitTransitionComplete) { 458 send(MSG_EXIT_TRANSITION_COMPLETE, null); 459 } 460 } 461 462 private void startEnterTransition(Transition transition) { 463 if (!mIsReturning) { 464 Drawable background = getDecor().getBackground(); 465 if (background != null) { 466 background = background.mutate(); 467 getWindow().setBackgroundDrawable(background); 468 mBackgroundAnimator = ObjectAnimator.ofInt(background, "alpha", 255); 469 mBackgroundAnimator.setDuration(getFadeDuration()); 470 mBackgroundAnimator.addListener(new AnimatorListenerAdapter() { 471 @Override 472 public void onAnimationEnd(Animator animation) { 473 makeOpaque(); 474 } 475 }); 476 mBackgroundAnimator.start(); 477 } else if (transition != null) { 478 transition.addListener(new Transition.TransitionListenerAdapter() { 479 @Override 480 public void onTransitionEnd(Transition transition) { 481 transition.removeListener(this); 482 makeOpaque(); 483 } 484 }); 485 } else { 486 makeOpaque(); 487 } 488 } 489 } 490 491 public void stop() { 492 makeOpaque(); 493 mIsCanceled = true; 494 mResultReceiver = null; 495 if (mBackgroundAnimator != null) { 496 mBackgroundAnimator.end(); 497 mBackgroundAnimator = null; 498 } 499 mActivity = null; 500 moveSharedElementsFromOverlay(); 501 clearState(); 502 } 503 504 public void cancelEnter() { 505 setGhostVisibility(View.INVISIBLE); 506 mHasStopped = true; 507 mIsCanceled = true; 508 mResultReceiver = null; 509 if (mBackgroundAnimator != null) { 510 mBackgroundAnimator.cancel(); 511 mBackgroundAnimator = null; 512 } 513 mActivity = null; 514 clearState(); 515 } 516 517 private void makeOpaque() { 518 if (!mHasStopped && mActivity != null) { 519 if (mWasOpaque) { 520 mActivity.convertFromTranslucent(); 521 } 522 mActivity = null; 523 } 524 } 525 526 private boolean allowOverlappingTransitions() { 527 return mIsReturning ? getWindow().getAllowExitTransitionOverlap() 528 : getWindow().getAllowEnterTransitionOverlap(); 529 } 530 531 private void startRejectedAnimations(final ArrayList<View> rejectedSnapshots) { 532 if (rejectedSnapshots == null || rejectedSnapshots.isEmpty()) { 533 return; 534 } 535 ViewGroupOverlay overlay = getDecor().getOverlay(); 536 ObjectAnimator animator = null; 537 int numRejected = rejectedSnapshots.size(); 538 for (int i = 0; i < numRejected; i++) { 539 View snapshot = rejectedSnapshots.get(i); 540 overlay.add(snapshot); 541 animator = ObjectAnimator.ofFloat(snapshot, View.ALPHA, 1, 0); 542 animator.start(); 543 } 544 animator.addListener(new AnimatorListenerAdapter() { 545 @Override 546 public void onAnimationEnd(Animator animation) { 547 ViewGroupOverlay overlay = getDecor().getOverlay(); 548 int numRejected = rejectedSnapshots.size(); 549 for (int i = 0; i < numRejected; i++) { 550 overlay.remove(rejectedSnapshots.get(i)); 551 } 552 } 553 }); 554 } 555 556 protected void onRemoteExitTransitionComplete() { 557 if (!allowOverlappingTransitions()) { 558 startEnterTransitionOnly(); 559 } 560 } 561 562 private void startEnterTransitionOnly() { 563 startTransition(new Runnable() { 564 @Override 565 public void run() { 566 boolean startEnterTransition = true; 567 boolean startSharedElementTransition = false; 568 Transition transition = beginTransition(startEnterTransition, 569 startSharedElementTransition); 570 startEnterTransition(transition); 571 } 572 }); 573 } 574 575 private void setSharedElementMatrices() { 576 int numSharedElements = mSharedElements.size(); 577 if (numSharedElements > 0) { 578 mSharedElementParentMatrices = new ArrayList<Matrix>(numSharedElements); 579 } 580 for (int i = 0; i < numSharedElements; i++) { 581 View view = mSharedElements.get(i); 582 583 // Find the location in the view's parent 584 ViewGroup parent = (ViewGroup) view.getParent(); 585 Matrix matrix = new Matrix(); 586 parent.transformMatrixToLocal(matrix); 587 588 mSharedElementParentMatrices.add(matrix); 589 } 590 } 591 592 @Override 593 protected void getSharedElementParentMatrix(View view, Matrix matrix) { 594 int index = mSharedElementParentMatrices == null ? -1 : mSharedElements.indexOf(view); 595 if (index < 0) { 596 super.getSharedElementParentMatrix(view, matrix); 597 } else { 598 matrix.set(mSharedElementParentMatrices.get(index)); 599 } 600 } 601} 602