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