ActivityTransitionCoordinator.java revision 7c479f5a013fb5544be45cd3a952190fc3a923fc
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.content.Context; 19import android.graphics.Matrix; 20import android.graphics.Rect; 21import android.graphics.RectF; 22import android.os.Bundle; 23import android.os.Handler; 24import android.os.Parcelable; 25import android.os.ResultReceiver; 26import android.transition.Transition; 27import android.transition.TransitionSet; 28import android.util.ArrayMap; 29import android.view.GhostView; 30import android.view.View; 31import android.view.ViewGroup; 32import android.view.ViewGroupOverlay; 33import android.view.ViewParent; 34import android.view.ViewTreeObserver; 35import android.view.Window; 36import android.widget.ImageView; 37 38import java.util.ArrayList; 39import java.util.Collection; 40 41/** 42 * Base class for ExitTransitionCoordinator and EnterTransitionCoordinator, classes 43 * that manage activity transitions and the communications coordinating them between 44 * Activities. The ExitTransitionCoordinator is created in the 45 * ActivityOptions#makeSceneTransitionAnimation. The EnterTransitionCoordinator 46 * is created by ActivityOptions#createEnterActivityTransition by Activity when the window is 47 * attached. 48 * 49 * Typical startActivity goes like this: 50 * 1) ExitTransitionCoordinator created with ActivityOptions#makeSceneTransitionAnimation 51 * 2) Activity#startActivity called and that calls startExit() through 52 * ActivityOptions#dispatchStartExit 53 * - Exit transition starts by setting transitioning Views to INVISIBLE 54 * 3) Launched Activity starts, creating an EnterTransitionCoordinator. 55 * - The Window is made translucent 56 * - The Window background alpha is set to 0 57 * - The transitioning views are made INVISIBLE 58 * - MSG_SET_LISTENER is sent back to the ExitTransitionCoordinator. 59 * 4) The shared element transition completes. 60 * - MSG_TAKE_SHARED_ELEMENTS is sent to the EnterTransitionCoordinator 61 * 5) The MSG_TAKE_SHARED_ELEMENTS is received by the EnterTransitionCoordinator. 62 * - Shared elements are made VISIBLE 63 * - Shared elements positions and size are set to match the end state of the calling 64 * Activity. 65 * - The shared element transition is started 66 * - If the window allows overlapping transitions, the views transition is started by setting 67 * the entering Views to VISIBLE and the background alpha is animated to opaque. 68 * - MSG_HIDE_SHARED_ELEMENTS is sent to the ExitTransitionCoordinator 69 * 6) MSG_HIDE_SHARED_ELEMENTS is received by the ExitTransitionCoordinator 70 * - The shared elements are made INVISIBLE 71 * 7) The exit transition completes in the calling Activity. 72 * - MSG_EXIT_TRANSITION_COMPLETE is sent to the EnterTransitionCoordinator. 73 * 8) The MSG_EXIT_TRANSITION_COMPLETE is received by the EnterTransitionCoordinator. 74 * - If the window doesn't allow overlapping enter transitions, the enter transition is started 75 * by setting entering views to VISIBLE and the background is animated to opaque. 76 * 9) The background opacity animation completes. 77 * - The window is made opaque 78 * 10) The calling Activity gets an onStop() call 79 * - onActivityStopped() is called and all exited Views are made VISIBLE. 80 * 81 * Typical finishAfterTransition goes like this: 82 * 1) finishAfterTransition() creates an ExitTransitionCoordinator and calls startExit() 83 * - The Window start transitioning to Translucent with a new ActivityOptions. 84 * - If no background exists, a black background is substituted 85 * - The shared elements in the scene are matched against those shared elements 86 * that were sent by comparing the names. 87 * - The exit transition is started by setting Views to INVISIBLE. 88 * 2) The ActivityOptions is received by the Activity and an EnterTransitionCoordinator is created. 89 * - All transitioning views are made VISIBLE to reverse what was done when onActivityStopped() 90 * was called 91 * 3) The Window is made translucent and a callback is received 92 * - The background alpha is animated to 0 93 * 4) The background alpha animation completes 94 * 5) The shared element transition completes 95 * - After both 4 & 5 complete, MSG_TAKE_SHARED_ELEMENTS is sent to the 96 * EnterTransitionCoordinator 97 * 6) MSG_TAKE_SHARED_ELEMENTS is received by EnterTransitionCoordinator 98 * - Shared elements are made VISIBLE 99 * - Shared elements positions and size are set to match the end state of the calling 100 * Activity. 101 * - The shared element transition is started 102 * - If the window allows overlapping transitions, the views transition is started by setting 103 * the entering Views to VISIBLE. 104 * - MSG_HIDE_SHARED_ELEMENTS is sent to the ExitTransitionCoordinator 105 * 7) MSG_HIDE_SHARED_ELEMENTS is received by the ExitTransitionCoordinator 106 * - The shared elements are made INVISIBLE 107 * 8) The exit transition completes in the finishing Activity. 108 * - MSG_EXIT_TRANSITION_COMPLETE is sent to the EnterTransitionCoordinator. 109 * - finish() is called on the exiting Activity 110 * 9) The MSG_EXIT_TRANSITION_COMPLETE is received by the EnterTransitionCoordinator. 111 * - If the window doesn't allow overlapping enter transitions, the enter transition is started 112 * by setting entering views to VISIBLE. 113 */ 114abstract class ActivityTransitionCoordinator extends ResultReceiver { 115 private static final String TAG = "ActivityTransitionCoordinator"; 116 117 /** 118 * For Activity transitions, the called Activity's listener to receive calls 119 * when transitions complete. 120 */ 121 static final String KEY_REMOTE_RECEIVER = "android:remoteReceiver"; 122 123 protected static final String KEY_SCREEN_LEFT = "shared_element:screenLeft"; 124 protected static final String KEY_SCREEN_TOP = "shared_element:screenTop"; 125 protected static final String KEY_SCREEN_RIGHT = "shared_element:screenRight"; 126 protected static final String KEY_SCREEN_BOTTOM= "shared_element:screenBottom"; 127 protected static final String KEY_TRANSLATION_Z = "shared_element:translationZ"; 128 protected static final String KEY_SNAPSHOT = "shared_element:bitmap"; 129 protected static final String KEY_SCALE_TYPE = "shared_element:scaleType"; 130 protected static final String KEY_IMAGE_MATRIX = "shared_element:imageMatrix"; 131 protected static final String KEY_ELEVATION = "shared_element:elevation"; 132 133 protected static final ImageView.ScaleType[] SCALE_TYPE_VALUES = ImageView.ScaleType.values(); 134 135 /** 136 * Sent by the exiting coordinator (either EnterTransitionCoordinator 137 * or ExitTransitionCoordinator) after the shared elements have 138 * become stationary (shared element transition completes). This tells 139 * the remote coordinator to take control of the shared elements and 140 * that animations may begin. The remote Activity won't start entering 141 * until this message is received, but may wait for 142 * MSG_EXIT_TRANSITION_COMPLETE if allowOverlappingTransitions() is true. 143 */ 144 public static final int MSG_SET_REMOTE_RECEIVER = 100; 145 146 /** 147 * Sent by the entering coordinator to tell the exiting coordinator 148 * to hide its shared elements after it has started its shared 149 * element transition. This is temporary until the 150 * interlock of shared elements is figured out. 151 */ 152 public static final int MSG_HIDE_SHARED_ELEMENTS = 101; 153 154 /** 155 * Sent by the exiting coordinator (either EnterTransitionCoordinator 156 * or ExitTransitionCoordinator) after the shared elements have 157 * become stationary (shared element transition completes). This tells 158 * the remote coordinator to take control of the shared elements and 159 * that animations may begin. The remote Activity won't start entering 160 * until this message is received, but may wait for 161 * MSG_EXIT_TRANSITION_COMPLETE if allowOverlappingTransitions() is true. 162 */ 163 public static final int MSG_TAKE_SHARED_ELEMENTS = 103; 164 165 /** 166 * Sent by the exiting coordinator (either 167 * EnterTransitionCoordinator or ExitTransitionCoordinator) after 168 * the exiting Views have finished leaving the scene. This will 169 * be ignored if allowOverlappingTransitions() is true on the 170 * remote coordinator. If it is false, it will trigger the enter 171 * transition to start. 172 */ 173 public static final int MSG_EXIT_TRANSITION_COMPLETE = 104; 174 175 /** 176 * Sent by Activity#startActivity to begin the exit transition. 177 */ 178 public static final int MSG_START_EXIT_TRANSITION = 105; 179 180 /** 181 * It took too long for a message from the entering Activity, so we canceled the transition. 182 */ 183 public static final int MSG_CANCEL = 106; 184 185 /** 186 * When returning, this is the destination location for the shared element. 187 */ 188 public static final int MSG_SHARED_ELEMENT_DESTINATION = 107; 189 190 /** 191 * Send the shared element positions. 192 */ 193 public static final int MSG_SEND_SHARED_ELEMENT_DESTINATION = 108; 194 195 private Window mWindow; 196 final protected ArrayList<String> mAllSharedElementNames; 197 final protected ArrayList<View> mSharedElements = new ArrayList<View>(); 198 final protected ArrayList<String> mSharedElementNames = new ArrayList<String>(); 199 protected ArrayList<View> mTransitioningViews = new ArrayList<View>(); 200 protected SharedElementCallback mListener; 201 protected ResultReceiver mResultReceiver; 202 final private FixedEpicenterCallback mEpicenterCallback = new FixedEpicenterCallback(); 203 final protected boolean mIsReturning; 204 private Runnable mPendingTransition; 205 private boolean mIsStartingTransition; 206 private ArrayList<GhostViewListeners> mGhostViewListeners = 207 new ArrayList<GhostViewListeners>(); 208 private ArrayMap<View, Float> mOriginalAlphas = new ArrayMap<View, Float>(); 209 210 public ActivityTransitionCoordinator(Window window, 211 ArrayList<String> allSharedElementNames, 212 SharedElementCallback listener, boolean isReturning) { 213 super(new Handler()); 214 mWindow = window; 215 mListener = listener; 216 mAllSharedElementNames = allSharedElementNames; 217 mIsReturning = isReturning; 218 } 219 220 protected void viewsReady(ArrayMap<String, View> sharedElements) { 221 sharedElements.retainAll(mAllSharedElementNames); 222 if (mListener != null) { 223 mListener.onMapSharedElements(mAllSharedElementNames, sharedElements); 224 } 225 int numSharedElements = sharedElements.size(); 226 for (int i = 0; i < numSharedElements; i++) { 227 View sharedElement = sharedElements.valueAt(i); 228 String name = sharedElements.keyAt(i); 229 if (sharedElement != null && sharedElement.isAttachedToWindow() && name != null) { 230 mSharedElements.add(sharedElement); 231 mSharedElementNames.add(name); 232 } 233 } 234 if (getViewsTransition() != null && mTransitioningViews != null) { 235 ViewGroup decorView = getDecor(); 236 if (decorView != null) { 237 decorView.captureTransitioningViews(mTransitioningViews); 238 } 239 mTransitioningViews.removeAll(mSharedElements); 240 } 241 setEpicenter(); 242 } 243 244 protected void stripOffscreenViews() { 245 if (mTransitioningViews == null) { 246 return; 247 } 248 Rect r = new Rect(); 249 for (int i = mTransitioningViews.size() - 1; i >= 0; i--) { 250 View view = mTransitioningViews.get(i); 251 if (!view.getGlobalVisibleRect(r)) { 252 mTransitioningViews.remove(i); 253 showView(view, true); 254 } 255 } 256 } 257 258 protected Window getWindow() { 259 return mWindow; 260 } 261 262 public ViewGroup getDecor() { 263 return (mWindow == null) ? null : (ViewGroup) mWindow.getDecorView(); 264 } 265 266 /** 267 * Sets the transition epicenter to the position of the first shared element. 268 */ 269 protected void setEpicenter() { 270 View epicenter = null; 271 if (!mAllSharedElementNames.isEmpty() && !mSharedElementNames.isEmpty()) { 272 int index = mSharedElementNames.indexOf(mAllSharedElementNames.get(0)); 273 if (index >= 0) { 274 epicenter = mSharedElements.get(index); 275 } 276 } 277 setEpicenter(epicenter); 278 } 279 280 private void setEpicenter(View view) { 281 if (view == null) { 282 mEpicenterCallback.setEpicenter(null); 283 } else { 284 Rect epicenter = new Rect(); 285 view.getBoundsOnScreen(epicenter); 286 mEpicenterCallback.setEpicenter(epicenter); 287 } 288 } 289 290 public ArrayList<String> getAcceptedNames() { 291 return mSharedElementNames; 292 } 293 294 public ArrayList<String> getMappedNames() { 295 ArrayList<String> names = new ArrayList<String>(mSharedElements.size()); 296 for (int i = 0; i < mSharedElements.size(); i++) { 297 names.add(mSharedElements.get(i).getTransitionName()); 298 } 299 return names; 300 } 301 302 public ArrayList<View> copyMappedViews() { 303 return new ArrayList<View>(mSharedElements); 304 } 305 306 public ArrayList<String> getAllSharedElementNames() { return mAllSharedElementNames; } 307 308 protected Transition setTargets(Transition transition, boolean add) { 309 if (transition == null || (add && 310 (mTransitioningViews == null || mTransitioningViews.isEmpty()))) { 311 return null; 312 } 313 // Add the targets to a set containing transition so that transition 314 // remains unaffected. We don't want to modify the targets of transition itself. 315 TransitionSet set = new TransitionSet(); 316 if (mTransitioningViews != null) { 317 for (int i = mTransitioningViews.size() - 1; i >= 0; i--) { 318 View view = mTransitioningViews.get(i); 319 if (add) { 320 set.addTarget(view); 321 } else { 322 set.excludeTarget(view, true); 323 } 324 } 325 } 326 // By adding the transition after addTarget, we prevent addTarget from 327 // affecting transition. 328 set.addTransition(transition); 329 330 if (!add && mTransitioningViews != null && !mTransitioningViews.isEmpty()) { 331 // Allow children of excluded transitioning views, but not the views themselves 332 set = new TransitionSet().addTransition(set); 333 } 334 335 return set; 336 } 337 338 protected Transition configureTransition(Transition transition, 339 boolean includeTransitioningViews) { 340 if (transition != null) { 341 transition = transition.clone(); 342 transition.setEpicenterCallback(mEpicenterCallback); 343 transition = setTargets(transition, includeTransitioningViews); 344 } 345 return transition; 346 } 347 348 protected static Transition mergeTransitions(Transition transition1, Transition transition2) { 349 if (transition1 == null) { 350 return transition2; 351 } else if (transition2 == null) { 352 return transition1; 353 } else { 354 TransitionSet transitionSet = new TransitionSet(); 355 transitionSet.addTransition(transition1); 356 transitionSet.addTransition(transition2); 357 return transitionSet; 358 } 359 } 360 361 protected ArrayMap<String, View> mapSharedElements(ArrayList<String> accepted, 362 ArrayList<View> localViews) { 363 ArrayMap<String, View> sharedElements = new ArrayMap<String, View>(); 364 if (accepted != null) { 365 for (int i = 0; i < accepted.size(); i++) { 366 sharedElements.put(accepted.get(i), localViews.get(i)); 367 } 368 } else { 369 ViewGroup decorView = getDecor(); 370 if (decorView != null) { 371 decorView.findNamedViews(sharedElements); 372 } 373 } 374 return sharedElements; 375 } 376 377 protected void setResultReceiver(ResultReceiver resultReceiver) { 378 mResultReceiver = resultReceiver; 379 } 380 381 protected abstract Transition getViewsTransition(); 382 383 private void setSharedElementState(View view, String name, Bundle transitionArgs, 384 Matrix tempMatrix, RectF tempRect, int[] decorLoc) { 385 Bundle sharedElementBundle = transitionArgs.getBundle(name); 386 if (sharedElementBundle == null) { 387 return; 388 } 389 390 if (view instanceof ImageView) { 391 int scaleTypeInt = sharedElementBundle.getInt(KEY_SCALE_TYPE, -1); 392 if (scaleTypeInt >= 0) { 393 ImageView imageView = (ImageView) view; 394 ImageView.ScaleType scaleType = SCALE_TYPE_VALUES[scaleTypeInt]; 395 imageView.setScaleType(scaleType); 396 if (scaleType == ImageView.ScaleType.MATRIX) { 397 float[] matrixValues = sharedElementBundle.getFloatArray(KEY_IMAGE_MATRIX); 398 tempMatrix.setValues(matrixValues); 399 imageView.setImageMatrix(tempMatrix); 400 } 401 } 402 } 403 404 float z = sharedElementBundle.getFloat(KEY_TRANSLATION_Z); 405 view.setTranslationZ(z); 406 float elevation = sharedElementBundle.getFloat(KEY_ELEVATION); 407 view.setElevation(elevation); 408 409 float left = sharedElementBundle.getFloat(KEY_SCREEN_LEFT); 410 float top = sharedElementBundle.getFloat(KEY_SCREEN_TOP); 411 float right = sharedElementBundle.getFloat(KEY_SCREEN_RIGHT); 412 float bottom = sharedElementBundle.getFloat(KEY_SCREEN_BOTTOM); 413 414 if (decorLoc != null) { 415 left -= decorLoc[0]; 416 top -= decorLoc[1]; 417 right -= decorLoc[0]; 418 bottom -= decorLoc[1]; 419 } else { 420 // Find the location in the view's parent 421 getSharedElementParentMatrix(view, tempMatrix); 422 tempRect.set(left, top, right, bottom); 423 tempMatrix.mapRect(tempRect); 424 425 float leftInParent = tempRect.left; 426 float topInParent = tempRect.top; 427 428 // Find the size of the view 429 view.getInverseMatrix().mapRect(tempRect); 430 float width = tempRect.width(); 431 float height = tempRect.height(); 432 433 // Now determine the offset due to view transform: 434 view.setLeft(0); 435 view.setTop(0); 436 view.setRight(Math.round(width)); 437 view.setBottom(Math.round(height)); 438 tempRect.set(0, 0, width, height); 439 view.getMatrix().mapRect(tempRect); 440 441 ViewGroup parent = (ViewGroup) view.getParent(); 442 left = leftInParent - tempRect.left + parent.getScrollX(); 443 top = topInParent - tempRect.top + parent.getScrollY(); 444 right = left + width; 445 bottom = top + height; 446 } 447 448 int x = Math.round(left); 449 int y = Math.round(top); 450 int width = Math.round(right) - x; 451 int height = Math.round(bottom) - y; 452 int widthSpec = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY); 453 int heightSpec = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY); 454 view.measure(widthSpec, heightSpec); 455 456 view.layout(x, y, x + width, y + height); 457 } 458 459 protected void getSharedElementParentMatrix(View view, Matrix matrix) { 460 // Find the location in the view's parent 461 ViewGroup parent = (ViewGroup) view.getParent(); 462 matrix.reset(); 463 parent.transformMatrixToLocal(matrix); 464 } 465 466 protected ArrayList<SharedElementOriginalState> setSharedElementState( 467 Bundle sharedElementState, final ArrayList<View> snapshots) { 468 ArrayList<SharedElementOriginalState> originalImageState = 469 new ArrayList<SharedElementOriginalState>(); 470 if (sharedElementState != null) { 471 Matrix tempMatrix = new Matrix(); 472 RectF tempRect = new RectF(); 473 final int numSharedElements = mSharedElements.size(); 474 for (int i = 0; i < numSharedElements; i++) { 475 View sharedElement = mSharedElements.get(i); 476 String name = mSharedElementNames.get(i); 477 SharedElementOriginalState originalState = getOldSharedElementState(sharedElement, 478 name, sharedElementState); 479 originalImageState.add(originalState); 480 setSharedElementState(sharedElement, name, sharedElementState, 481 tempMatrix, tempRect, null); 482 } 483 } 484 if (mListener != null) { 485 mListener.onSharedElementStart(mSharedElementNames, mSharedElements, snapshots); 486 } 487 return originalImageState; 488 } 489 490 protected void notifySharedElementEnd(ArrayList<View> snapshots) { 491 if (mListener != null) { 492 mListener.onSharedElementEnd(mSharedElementNames, mSharedElements, snapshots); 493 } 494 } 495 496 protected void scheduleSetSharedElementEnd(final ArrayList<View> snapshots) { 497 final View decorView = getDecor(); 498 if (decorView != null) { 499 decorView.getViewTreeObserver().addOnPreDrawListener( 500 new ViewTreeObserver.OnPreDrawListener() { 501 @Override 502 public boolean onPreDraw() { 503 decorView.getViewTreeObserver().removeOnPreDrawListener(this); 504 notifySharedElementEnd(snapshots); 505 return true; 506 } 507 } 508 ); 509 } 510 } 511 512 private static SharedElementOriginalState getOldSharedElementState(View view, String name, 513 Bundle transitionArgs) { 514 515 SharedElementOriginalState state = new SharedElementOriginalState(); 516 state.mLeft = view.getLeft(); 517 state.mTop = view.getTop(); 518 state.mRight = view.getRight(); 519 state.mBottom = view.getBottom(); 520 state.mMeasuredWidth = view.getMeasuredWidth(); 521 state.mMeasuredHeight = view.getMeasuredHeight(); 522 state.mTranslationZ = view.getTranslationZ(); 523 state.mElevation = view.getElevation(); 524 if (!(view instanceof ImageView)) { 525 return state; 526 } 527 Bundle bundle = transitionArgs.getBundle(name); 528 if (bundle == null) { 529 return state; 530 } 531 int scaleTypeInt = bundle.getInt(KEY_SCALE_TYPE, -1); 532 if (scaleTypeInt < 0) { 533 return state; 534 } 535 536 ImageView imageView = (ImageView) view; 537 state.mScaleType = imageView.getScaleType(); 538 if (state.mScaleType == ImageView.ScaleType.MATRIX) { 539 state.mMatrix = new Matrix(imageView.getImageMatrix()); 540 } 541 return state; 542 } 543 544 protected ArrayList<View> createSnapshots(Bundle state, Collection<String> names) { 545 int numSharedElements = names.size(); 546 ArrayList<View> snapshots = new ArrayList<View>(numSharedElements); 547 if (numSharedElements == 0) { 548 return snapshots; 549 } 550 Context context = getWindow().getContext(); 551 int[] decorLoc = new int[2]; 552 ViewGroup decorView = getDecor(); 553 if (decorView != null) { 554 decorView.getLocationOnScreen(decorLoc); 555 } 556 for (String name: names) { 557 Bundle sharedElementBundle = state.getBundle(name); 558 if (sharedElementBundle != null) { 559 Parcelable parcelable = sharedElementBundle.getParcelable(KEY_SNAPSHOT); 560 View snapshot = null; 561 if (parcelable != null && mListener != null) { 562 snapshot = mListener.onCreateSnapshotView(context, parcelable); 563 } 564 if (snapshot != null) { 565 setSharedElementState(snapshot, name, state, null, null, decorLoc); 566 } 567 snapshots.add(snapshot); 568 } 569 } 570 return snapshots; 571 } 572 573 protected static void setOriginalSharedElementState(ArrayList<View> sharedElements, 574 ArrayList<SharedElementOriginalState> originalState) { 575 for (int i = 0; i < originalState.size(); i++) { 576 View view = sharedElements.get(i); 577 SharedElementOriginalState state = originalState.get(i); 578 if (view instanceof ImageView && state.mScaleType != null) { 579 ImageView imageView = (ImageView) view; 580 imageView.setScaleType(state.mScaleType); 581 if (state.mScaleType == ImageView.ScaleType.MATRIX) { 582 imageView.setImageMatrix(state.mMatrix); 583 } 584 } 585 view.setElevation(state.mElevation); 586 view.setTranslationZ(state.mTranslationZ); 587 int widthSpec = View.MeasureSpec.makeMeasureSpec(state.mMeasuredWidth, 588 View.MeasureSpec.EXACTLY); 589 int heightSpec = View.MeasureSpec.makeMeasureSpec(state.mMeasuredHeight, 590 View.MeasureSpec.EXACTLY); 591 view.measure(widthSpec, heightSpec); 592 view.layout(state.mLeft, state.mTop, state.mRight, state.mBottom); 593 } 594 } 595 596 protected Bundle captureSharedElementState() { 597 Bundle bundle = new Bundle(); 598 RectF tempBounds = new RectF(); 599 Matrix tempMatrix = new Matrix(); 600 for (int i = 0; i < mSharedElements.size(); i++) { 601 View sharedElement = mSharedElements.get(i); 602 String name = mSharedElementNames.get(i); 603 captureSharedElementState(sharedElement, name, bundle, tempMatrix, tempBounds); 604 } 605 return bundle; 606 } 607 608 protected void clearState() { 609 // Clear the state so that we can't hold any references accidentally and leak memory. 610 mWindow = null; 611 mSharedElements.clear(); 612 mTransitioningViews = null; 613 mOriginalAlphas.clear(); 614 mResultReceiver = null; 615 mPendingTransition = null; 616 mListener = null; 617 } 618 619 protected long getFadeDuration() { 620 return getWindow().getTransitionBackgroundFadeDuration(); 621 } 622 623 protected void hideViews(ArrayList<View> views) { 624 int count = views.size(); 625 for (int i = 0; i < count; i++) { 626 View view = views.get(i); 627 if (!mOriginalAlphas.containsKey(view)) { 628 mOriginalAlphas.put(view, view.getAlpha()); 629 } 630 view.setAlpha(0f); 631 } 632 } 633 634 protected void showViews(ArrayList<View> views, boolean setTransitionAlpha) { 635 int count = views.size(); 636 for (int i = 0; i < count; i++) { 637 showView(views.get(i), setTransitionAlpha); 638 } 639 } 640 641 private void showView(View view, boolean setTransitionAlpha) { 642 Float alpha = mOriginalAlphas.remove(view); 643 if (alpha != null) { 644 view.setAlpha(alpha); 645 } 646 if (setTransitionAlpha) { 647 view.setTransitionAlpha(1f); 648 } 649 } 650 651 /** 652 * Captures placement information for Views with a shared element name for 653 * Activity Transitions. 654 * 655 * @param view The View to capture the placement information for. 656 * @param name The shared element name in the target Activity to apply the placement 657 * information for. 658 * @param transitionArgs Bundle to store shared element placement information. 659 * @param tempBounds A temporary Rect for capturing the current location of views. 660 */ 661 protected void captureSharedElementState(View view, String name, Bundle transitionArgs, 662 Matrix tempMatrix, RectF tempBounds) { 663 Bundle sharedElementBundle = new Bundle(); 664 tempMatrix.reset(); 665 view.transformMatrixToGlobal(tempMatrix); 666 tempBounds.set(0, 0, view.getWidth(), view.getHeight()); 667 tempMatrix.mapRect(tempBounds); 668 669 sharedElementBundle.putFloat(KEY_SCREEN_LEFT, tempBounds.left); 670 sharedElementBundle.putFloat(KEY_SCREEN_RIGHT, tempBounds.right); 671 sharedElementBundle.putFloat(KEY_SCREEN_TOP, tempBounds.top); 672 sharedElementBundle.putFloat(KEY_SCREEN_BOTTOM, tempBounds.bottom); 673 sharedElementBundle.putFloat(KEY_TRANSLATION_Z, view.getTranslationZ()); 674 sharedElementBundle.putFloat(KEY_ELEVATION, view.getElevation()); 675 676 Parcelable bitmap = null; 677 if (mListener != null) { 678 bitmap = mListener.onCaptureSharedElementSnapshot(view, tempMatrix, tempBounds); 679 } 680 681 if (bitmap != null) { 682 sharedElementBundle.putParcelable(KEY_SNAPSHOT, bitmap); 683 } 684 685 if (view instanceof ImageView) { 686 ImageView imageView = (ImageView) view; 687 int scaleTypeInt = scaleTypeToInt(imageView.getScaleType()); 688 sharedElementBundle.putInt(KEY_SCALE_TYPE, scaleTypeInt); 689 if (imageView.getScaleType() == ImageView.ScaleType.MATRIX) { 690 float[] matrix = new float[9]; 691 imageView.getImageMatrix().getValues(matrix); 692 sharedElementBundle.putFloatArray(KEY_IMAGE_MATRIX, matrix); 693 } 694 } 695 696 transitionArgs.putBundle(name, sharedElementBundle); 697 } 698 699 700 protected void startTransition(Runnable runnable) { 701 if (mIsStartingTransition) { 702 mPendingTransition = runnable; 703 } else { 704 mIsStartingTransition = true; 705 runnable.run(); 706 } 707 } 708 709 protected void transitionStarted() { 710 mIsStartingTransition = false; 711 } 712 713 protected void moveSharedElementsToOverlay() { 714 if (!mWindow.getSharedElementsUseOverlay()) { 715 return; 716 } 717 int numSharedElements = mSharedElements.size(); 718 ViewGroup decor = getDecor(); 719 if (decor != null) { 720 boolean moveWithParent = moveSharedElementWithParent(); 721 for (int i = 0; i < numSharedElements; i++) { 722 View view = mSharedElements.get(i); 723 GhostView.addGhost(view, decor); 724 ViewGroup parent = (ViewGroup) view.getParent(); 725 if (moveWithParent && !isInTransitionGroup(parent, decor)) { 726 GhostViewListeners listener = new GhostViewListeners(view, parent, decor); 727 parent.getViewTreeObserver().addOnPreDrawListener(listener); 728 mGhostViewListeners.add(listener); 729 } 730 } 731 } 732 } 733 734 protected boolean moveSharedElementWithParent() { 735 return true; 736 } 737 738 public static boolean isInTransitionGroup(ViewParent viewParent, ViewGroup decor) { 739 if (viewParent == decor || !(viewParent instanceof ViewGroup)) { 740 return false; 741 } 742 ViewGroup parent = (ViewGroup) viewParent; 743 if (parent.isTransitionGroup()) { 744 return true; 745 } else { 746 return isInTransitionGroup(parent.getParent(), decor); 747 } 748 } 749 750 protected void moveSharedElementsFromOverlay() { 751 int numListeners = mGhostViewListeners.size(); 752 for (int i = 0; i < numListeners; i++) { 753 GhostViewListeners listener = mGhostViewListeners.get(i); 754 ViewGroup parent = (ViewGroup) listener.getView().getParent(); 755 parent.getViewTreeObserver().removeOnPreDrawListener(listener); 756 } 757 mGhostViewListeners.clear(); 758 759 if (mWindow == null || !mWindow.getSharedElementsUseOverlay()) { 760 return; 761 } 762 ViewGroup decor = getDecor(); 763 if (decor != null) { 764 ViewGroupOverlay overlay = decor.getOverlay(); 765 int count = mSharedElements.size(); 766 for (int i = 0; i < count; i++) { 767 View sharedElement = mSharedElements.get(i); 768 GhostView.removeGhost(sharedElement); 769 } 770 } 771 } 772 773 protected void setGhostVisibility(int visibility) { 774 int numSharedElements = mSharedElements.size(); 775 for (int i = 0; i < numSharedElements; i++) { 776 GhostView ghostView = GhostView.getGhost(mSharedElements.get(i)); 777 if (ghostView != null) { 778 ghostView.setVisibility(visibility); 779 } 780 } 781 } 782 783 protected void scheduleGhostVisibilityChange(final int visibility) { 784 final View decorView = getDecor(); 785 if (decorView != null) { 786 decorView.getViewTreeObserver() 787 .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { 788 @Override 789 public boolean onPreDraw() { 790 decorView.getViewTreeObserver().removeOnPreDrawListener(this); 791 setGhostVisibility(visibility); 792 return true; 793 } 794 }); 795 } 796 } 797 798 protected class ContinueTransitionListener extends Transition.TransitionListenerAdapter { 799 @Override 800 public void onTransitionStart(Transition transition) { 801 mIsStartingTransition = false; 802 Runnable pending = mPendingTransition; 803 mPendingTransition = null; 804 if (pending != null) { 805 startTransition(pending); 806 } 807 } 808 } 809 810 private static int scaleTypeToInt(ImageView.ScaleType scaleType) { 811 for (int i = 0; i < SCALE_TYPE_VALUES.length; i++) { 812 if (scaleType == SCALE_TYPE_VALUES[i]) { 813 return i; 814 } 815 } 816 return -1; 817 } 818 819 private static class FixedEpicenterCallback extends Transition.EpicenterCallback { 820 private Rect mEpicenter; 821 822 public void setEpicenter(Rect epicenter) { mEpicenter = epicenter; } 823 824 @Override 825 public Rect onGetEpicenter(Transition transition) { 826 return mEpicenter; 827 } 828 } 829 830 private static class GhostViewListeners implements ViewTreeObserver.OnPreDrawListener { 831 private View mView; 832 private ViewGroup mDecor; 833 private View mParent; 834 private Matrix mMatrix = new Matrix(); 835 836 public GhostViewListeners(View view, View parent, ViewGroup decor) { 837 mView = view; 838 mParent = parent; 839 mDecor = decor; 840 } 841 842 public View getView() { 843 return mView; 844 } 845 846 @Override 847 public boolean onPreDraw() { 848 GhostView ghostView = GhostView.getGhost(mView); 849 if (ghostView == null) { 850 mParent.getViewTreeObserver().removeOnPreDrawListener(this); 851 } else { 852 GhostView.calculateMatrix(mView, mDecor, mMatrix); 853 ghostView.setMatrix(mMatrix); 854 } 855 return true; 856 } 857 } 858 859 static class SharedElementOriginalState { 860 int mLeft; 861 int mTop; 862 int mRight; 863 int mBottom; 864 int mMeasuredWidth; 865 int mMeasuredHeight; 866 ImageView.ScaleType mScaleType; 867 Matrix mMatrix; 868 float mTranslationZ; 869 float mElevation; 870 } 871} 872