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.content.Context; 19 import android.graphics.Matrix; 20 import android.graphics.Rect; 21 import android.graphics.RectF; 22 import android.os.Bundle; 23 import android.os.Handler; 24 import android.os.Parcelable; 25 import android.os.ResultReceiver; 26 import android.transition.Transition; 27 import android.transition.TransitionSet; 28 import android.util.ArrayMap; 29 import android.view.GhostView; 30 import android.view.View; 31 import android.view.ViewGroup; 32 import android.view.ViewGroupOverlay; 33 import android.view.ViewParent; 34 import android.view.ViewTreeObserver; 35 import android.view.Window; 36 import android.widget.ImageView; 37 38 import java.util.ArrayList; 39 import 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 */ 114 abstract 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 final private ArrayList<View> mRootSharedElements = new ArrayList<View>(); 210 private ArrayList<Matrix> mSharedElementParentMatrices; 211 ActivityTransitionCoordinator(Window window, ArrayList<String> allSharedElementNames, SharedElementCallback listener, boolean isReturning)212 public ActivityTransitionCoordinator(Window window, 213 ArrayList<String> allSharedElementNames, 214 SharedElementCallback listener, boolean isReturning) { 215 super(new Handler()); 216 mWindow = window; 217 mListener = listener; 218 mAllSharedElementNames = allSharedElementNames; 219 mIsReturning = isReturning; 220 } 221 viewsReady(ArrayMap<String, View> sharedElements)222 protected void viewsReady(ArrayMap<String, View> sharedElements) { 223 sharedElements.retainAll(mAllSharedElementNames); 224 if (mListener != null) { 225 mListener.onMapSharedElements(mAllSharedElementNames, sharedElements); 226 } 227 setSharedElements(sharedElements); 228 if (getViewsTransition() != null && mTransitioningViews != null) { 229 ViewGroup decorView = getDecor(); 230 if (decorView != null) { 231 decorView.captureTransitioningViews(mTransitioningViews); 232 } 233 mTransitioningViews.removeAll(mSharedElements); 234 } 235 setEpicenter(); 236 } 237 238 /** 239 * Iterates over the shared elements and adds them to the members in order. 240 * Shared elements that are nested in other shared elements are placed after the 241 * elements that they are nested in. This means that layout ordering can be done 242 * from first to last. 243 * 244 * @param sharedElements The map of transition names to shared elements to set into 245 * the member fields. 246 */ setSharedElements(ArrayMap<String, View> sharedElements)247 private void setSharedElements(ArrayMap<String, View> sharedElements) { 248 boolean isFirstRun = true; 249 while (!sharedElements.isEmpty()) { 250 final int numSharedElements = sharedElements.size(); 251 for (int i = numSharedElements - 1; i >= 0; i--) { 252 final View view = sharedElements.valueAt(i); 253 final String name = sharedElements.keyAt(i); 254 if (isFirstRun && (view == null || !view.isAttachedToWindow() || name == null)) { 255 sharedElements.removeAt(i); 256 } else { 257 if (!isNested(view, sharedElements)) { 258 mSharedElementNames.add(name); 259 mSharedElements.add(view); 260 sharedElements.removeAt(i); 261 if (isFirstRun) { 262 // We need to keep track which shared elements are roots 263 // and which are nested. 264 mRootSharedElements.add(view); 265 } 266 } 267 } 268 } 269 isFirstRun = false; 270 } 271 } 272 273 /** 274 * Returns true when view is nested in any of the values of sharedElements. 275 */ isNested(View view, ArrayMap<String, View> sharedElements)276 private static boolean isNested(View view, ArrayMap<String, View> sharedElements) { 277 ViewParent parent = view.getParent(); 278 boolean isNested = false; 279 while (parent instanceof View) { 280 View parentView = (View) parent; 281 if (sharedElements.containsValue(parentView)) { 282 isNested = true; 283 break; 284 } 285 parent = parentView.getParent(); 286 } 287 return isNested; 288 } 289 stripOffscreenViews()290 protected void stripOffscreenViews() { 291 if (mTransitioningViews == null) { 292 return; 293 } 294 Rect r = new Rect(); 295 for (int i = mTransitioningViews.size() - 1; i >= 0; i--) { 296 View view = mTransitioningViews.get(i); 297 if (!view.getGlobalVisibleRect(r)) { 298 mTransitioningViews.remove(i); 299 showView(view, true); 300 } 301 } 302 } 303 getWindow()304 protected Window getWindow() { 305 return mWindow; 306 } 307 getDecor()308 public ViewGroup getDecor() { 309 return (mWindow == null) ? null : (ViewGroup) mWindow.getDecorView(); 310 } 311 312 /** 313 * Sets the transition epicenter to the position of the first shared element. 314 */ setEpicenter()315 protected void setEpicenter() { 316 View epicenter = null; 317 if (!mAllSharedElementNames.isEmpty() && !mSharedElementNames.isEmpty()) { 318 int index = mSharedElementNames.indexOf(mAllSharedElementNames.get(0)); 319 if (index >= 0) { 320 epicenter = mSharedElements.get(index); 321 } 322 } 323 setEpicenter(epicenter); 324 } 325 setEpicenter(View view)326 private void setEpicenter(View view) { 327 if (view == null) { 328 mEpicenterCallback.setEpicenter(null); 329 } else { 330 Rect epicenter = new Rect(); 331 view.getBoundsOnScreen(epicenter); 332 mEpicenterCallback.setEpicenter(epicenter); 333 } 334 } 335 getAcceptedNames()336 public ArrayList<String> getAcceptedNames() { 337 return mSharedElementNames; 338 } 339 getMappedNames()340 public ArrayList<String> getMappedNames() { 341 ArrayList<String> names = new ArrayList<String>(mSharedElements.size()); 342 for (int i = 0; i < mSharedElements.size(); i++) { 343 names.add(mSharedElements.get(i).getTransitionName()); 344 } 345 return names; 346 } 347 copyMappedViews()348 public ArrayList<View> copyMappedViews() { 349 return new ArrayList<View>(mSharedElements); 350 } 351 getAllSharedElementNames()352 public ArrayList<String> getAllSharedElementNames() { return mAllSharedElementNames; } 353 setTargets(Transition transition, boolean add)354 protected Transition setTargets(Transition transition, boolean add) { 355 if (transition == null || (add && 356 (mTransitioningViews == null || mTransitioningViews.isEmpty()))) { 357 return null; 358 } 359 // Add the targets to a set containing transition so that transition 360 // remains unaffected. We don't want to modify the targets of transition itself. 361 TransitionSet set = new TransitionSet(); 362 if (mTransitioningViews != null) { 363 for (int i = mTransitioningViews.size() - 1; i >= 0; i--) { 364 View view = mTransitioningViews.get(i); 365 if (add) { 366 set.addTarget(view); 367 } else { 368 set.excludeTarget(view, true); 369 } 370 } 371 } 372 // By adding the transition after addTarget, we prevent addTarget from 373 // affecting transition. 374 set.addTransition(transition); 375 376 if (!add && mTransitioningViews != null && !mTransitioningViews.isEmpty()) { 377 // Allow children of excluded transitioning views, but not the views themselves 378 set = new TransitionSet().addTransition(set); 379 } 380 381 return set; 382 } 383 configureTransition(Transition transition, boolean includeTransitioningViews)384 protected Transition configureTransition(Transition transition, 385 boolean includeTransitioningViews) { 386 if (transition != null) { 387 transition = transition.clone(); 388 transition.setEpicenterCallback(mEpicenterCallback); 389 transition = setTargets(transition, includeTransitioningViews); 390 } 391 return transition; 392 } 393 mergeTransitions(Transition transition1, Transition transition2)394 protected static Transition mergeTransitions(Transition transition1, Transition transition2) { 395 if (transition1 == null) { 396 return transition2; 397 } else if (transition2 == null) { 398 return transition1; 399 } else { 400 TransitionSet transitionSet = new TransitionSet(); 401 transitionSet.addTransition(transition1); 402 transitionSet.addTransition(transition2); 403 return transitionSet; 404 } 405 } 406 mapSharedElements(ArrayList<String> accepted, ArrayList<View> localViews)407 protected ArrayMap<String, View> mapSharedElements(ArrayList<String> accepted, 408 ArrayList<View> localViews) { 409 ArrayMap<String, View> sharedElements = new ArrayMap<String, View>(); 410 if (accepted != null) { 411 for (int i = 0; i < accepted.size(); i++) { 412 sharedElements.put(accepted.get(i), localViews.get(i)); 413 } 414 } else { 415 ViewGroup decorView = getDecor(); 416 if (decorView != null) { 417 decorView.findNamedViews(sharedElements); 418 } 419 } 420 return sharedElements; 421 } 422 setResultReceiver(ResultReceiver resultReceiver)423 protected void setResultReceiver(ResultReceiver resultReceiver) { 424 mResultReceiver = resultReceiver; 425 } 426 getViewsTransition()427 protected abstract Transition getViewsTransition(); 428 setSharedElementState(View view, String name, Bundle transitionArgs, Matrix tempMatrix, RectF tempRect, int[] decorLoc)429 private void setSharedElementState(View view, String name, Bundle transitionArgs, 430 Matrix tempMatrix, RectF tempRect, int[] decorLoc) { 431 Bundle sharedElementBundle = transitionArgs.getBundle(name); 432 if (sharedElementBundle == null) { 433 return; 434 } 435 436 if (view instanceof ImageView) { 437 int scaleTypeInt = sharedElementBundle.getInt(KEY_SCALE_TYPE, -1); 438 if (scaleTypeInt >= 0) { 439 ImageView imageView = (ImageView) view; 440 ImageView.ScaleType scaleType = SCALE_TYPE_VALUES[scaleTypeInt]; 441 imageView.setScaleType(scaleType); 442 if (scaleType == ImageView.ScaleType.MATRIX) { 443 float[] matrixValues = sharedElementBundle.getFloatArray(KEY_IMAGE_MATRIX); 444 tempMatrix.setValues(matrixValues); 445 imageView.setImageMatrix(tempMatrix); 446 } 447 } 448 } 449 450 float z = sharedElementBundle.getFloat(KEY_TRANSLATION_Z); 451 view.setTranslationZ(z); 452 float elevation = sharedElementBundle.getFloat(KEY_ELEVATION); 453 view.setElevation(elevation); 454 455 float left = sharedElementBundle.getFloat(KEY_SCREEN_LEFT); 456 float top = sharedElementBundle.getFloat(KEY_SCREEN_TOP); 457 float right = sharedElementBundle.getFloat(KEY_SCREEN_RIGHT); 458 float bottom = sharedElementBundle.getFloat(KEY_SCREEN_BOTTOM); 459 460 if (decorLoc != null) { 461 left -= decorLoc[0]; 462 top -= decorLoc[1]; 463 right -= decorLoc[0]; 464 bottom -= decorLoc[1]; 465 } else { 466 // Find the location in the view's parent 467 getSharedElementParentMatrix(view, tempMatrix); 468 tempRect.set(left, top, right, bottom); 469 tempMatrix.mapRect(tempRect); 470 471 float leftInParent = tempRect.left; 472 float topInParent = tempRect.top; 473 474 // Find the size of the view 475 view.getInverseMatrix().mapRect(tempRect); 476 float width = tempRect.width(); 477 float height = tempRect.height(); 478 479 // Now determine the offset due to view transform: 480 view.setLeft(0); 481 view.setTop(0); 482 view.setRight(Math.round(width)); 483 view.setBottom(Math.round(height)); 484 tempRect.set(0, 0, width, height); 485 view.getMatrix().mapRect(tempRect); 486 487 ViewGroup parent = (ViewGroup) view.getParent(); 488 left = leftInParent - tempRect.left + parent.getScrollX(); 489 top = topInParent - tempRect.top + parent.getScrollY(); 490 right = left + width; 491 bottom = top + height; 492 } 493 494 int x = Math.round(left); 495 int y = Math.round(top); 496 int width = Math.round(right) - x; 497 int height = Math.round(bottom) - y; 498 int widthSpec = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY); 499 int heightSpec = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY); 500 view.measure(widthSpec, heightSpec); 501 502 view.layout(x, y, x + width, y + height); 503 } 504 setSharedElementMatrices()505 private void setSharedElementMatrices() { 506 int numSharedElements = mSharedElements.size(); 507 if (numSharedElements > 0) { 508 mSharedElementParentMatrices = new ArrayList<Matrix>(numSharedElements); 509 } 510 for (int i = 0; i < numSharedElements; i++) { 511 View view = mSharedElements.get(i); 512 513 // Find the location in the view's parent 514 ViewGroup parent = (ViewGroup) view.getParent(); 515 Matrix matrix = new Matrix(); 516 parent.transformMatrixToLocal(matrix); 517 518 mSharedElementParentMatrices.add(matrix); 519 } 520 } 521 getSharedElementParentMatrix(View view, Matrix matrix)522 private void getSharedElementParentMatrix(View view, Matrix matrix) { 523 final boolean isNestedInOtherSharedElement = !mRootSharedElements.contains(view); 524 final boolean useParentMatrix; 525 if (isNestedInOtherSharedElement) { 526 useParentMatrix = true; 527 } else { 528 final int index = mSharedElementParentMatrices == null ? -1 529 : mSharedElements.indexOf(view); 530 if (index < 0) { 531 useParentMatrix = true; 532 } else { 533 // The indices of mSharedElementParentMatrices matches the 534 // mSharedElement matrices. 535 Matrix parentMatrix = mSharedElementParentMatrices.get(index); 536 matrix.set(parentMatrix); 537 useParentMatrix = false; 538 } 539 } 540 if (useParentMatrix) { 541 matrix.reset(); 542 ViewParent viewParent = view.getParent(); 543 if (viewParent instanceof ViewGroup) { 544 // Find the location in the view's parent 545 ViewGroup parent = (ViewGroup) viewParent; 546 parent.transformMatrixToLocal(matrix); 547 } 548 } 549 } 550 setSharedElementState( Bundle sharedElementState, final ArrayList<View> snapshots)551 protected ArrayList<SharedElementOriginalState> setSharedElementState( 552 Bundle sharedElementState, final ArrayList<View> snapshots) { 553 ArrayList<SharedElementOriginalState> originalImageState = 554 new ArrayList<SharedElementOriginalState>(); 555 if (sharedElementState != null) { 556 Matrix tempMatrix = new Matrix(); 557 RectF tempRect = new RectF(); 558 final int numSharedElements = mSharedElements.size(); 559 for (int i = 0; i < numSharedElements; i++) { 560 View sharedElement = mSharedElements.get(i); 561 String name = mSharedElementNames.get(i); 562 SharedElementOriginalState originalState = getOldSharedElementState(sharedElement, 563 name, sharedElementState); 564 originalImageState.add(originalState); 565 setSharedElementState(sharedElement, name, sharedElementState, 566 tempMatrix, tempRect, null); 567 } 568 } 569 if (mListener != null) { 570 mListener.onSharedElementStart(mSharedElementNames, mSharedElements, snapshots); 571 } 572 return originalImageState; 573 } 574 notifySharedElementEnd(ArrayList<View> snapshots)575 protected void notifySharedElementEnd(ArrayList<View> snapshots) { 576 if (mListener != null) { 577 mListener.onSharedElementEnd(mSharedElementNames, mSharedElements, snapshots); 578 } 579 } 580 scheduleSetSharedElementEnd(final ArrayList<View> snapshots)581 protected void scheduleSetSharedElementEnd(final ArrayList<View> snapshots) { 582 final View decorView = getDecor(); 583 if (decorView != null) { 584 decorView.getViewTreeObserver().addOnPreDrawListener( 585 new ViewTreeObserver.OnPreDrawListener() { 586 @Override 587 public boolean onPreDraw() { 588 decorView.getViewTreeObserver().removeOnPreDrawListener(this); 589 notifySharedElementEnd(snapshots); 590 return true; 591 } 592 } 593 ); 594 } 595 } 596 getOldSharedElementState(View view, String name, Bundle transitionArgs)597 private static SharedElementOriginalState getOldSharedElementState(View view, String name, 598 Bundle transitionArgs) { 599 600 SharedElementOriginalState state = new SharedElementOriginalState(); 601 state.mLeft = view.getLeft(); 602 state.mTop = view.getTop(); 603 state.mRight = view.getRight(); 604 state.mBottom = view.getBottom(); 605 state.mMeasuredWidth = view.getMeasuredWidth(); 606 state.mMeasuredHeight = view.getMeasuredHeight(); 607 state.mTranslationZ = view.getTranslationZ(); 608 state.mElevation = view.getElevation(); 609 if (!(view instanceof ImageView)) { 610 return state; 611 } 612 Bundle bundle = transitionArgs.getBundle(name); 613 if (bundle == null) { 614 return state; 615 } 616 int scaleTypeInt = bundle.getInt(KEY_SCALE_TYPE, -1); 617 if (scaleTypeInt < 0) { 618 return state; 619 } 620 621 ImageView imageView = (ImageView) view; 622 state.mScaleType = imageView.getScaleType(); 623 if (state.mScaleType == ImageView.ScaleType.MATRIX) { 624 state.mMatrix = new Matrix(imageView.getImageMatrix()); 625 } 626 return state; 627 } 628 createSnapshots(Bundle state, Collection<String> names)629 protected ArrayList<View> createSnapshots(Bundle state, Collection<String> names) { 630 int numSharedElements = names.size(); 631 ArrayList<View> snapshots = new ArrayList<View>(numSharedElements); 632 if (numSharedElements == 0) { 633 return snapshots; 634 } 635 Context context = getWindow().getContext(); 636 int[] decorLoc = new int[2]; 637 ViewGroup decorView = getDecor(); 638 if (decorView != null) { 639 decorView.getLocationOnScreen(decorLoc); 640 } 641 Matrix tempMatrix = new Matrix(); 642 for (String name: names) { 643 Bundle sharedElementBundle = state.getBundle(name); 644 View snapshot = null; 645 if (sharedElementBundle != null) { 646 Parcelable parcelable = sharedElementBundle.getParcelable(KEY_SNAPSHOT); 647 if (parcelable != null && mListener != null) { 648 snapshot = mListener.onCreateSnapshotView(context, parcelable); 649 } 650 if (snapshot != null) { 651 setSharedElementState(snapshot, name, state, tempMatrix, null, decorLoc); 652 } 653 } 654 // Even null snapshots are added so they remain in the same order as shared elements. 655 snapshots.add(snapshot); 656 } 657 return snapshots; 658 } 659 setOriginalSharedElementState(ArrayList<View> sharedElements, ArrayList<SharedElementOriginalState> originalState)660 protected static void setOriginalSharedElementState(ArrayList<View> sharedElements, 661 ArrayList<SharedElementOriginalState> originalState) { 662 for (int i = 0; i < originalState.size(); i++) { 663 View view = sharedElements.get(i); 664 SharedElementOriginalState state = originalState.get(i); 665 if (view instanceof ImageView && state.mScaleType != null) { 666 ImageView imageView = (ImageView) view; 667 imageView.setScaleType(state.mScaleType); 668 if (state.mScaleType == ImageView.ScaleType.MATRIX) { 669 imageView.setImageMatrix(state.mMatrix); 670 } 671 } 672 view.setElevation(state.mElevation); 673 view.setTranslationZ(state.mTranslationZ); 674 int widthSpec = View.MeasureSpec.makeMeasureSpec(state.mMeasuredWidth, 675 View.MeasureSpec.EXACTLY); 676 int heightSpec = View.MeasureSpec.makeMeasureSpec(state.mMeasuredHeight, 677 View.MeasureSpec.EXACTLY); 678 view.measure(widthSpec, heightSpec); 679 view.layout(state.mLeft, state.mTop, state.mRight, state.mBottom); 680 } 681 } 682 captureSharedElementState()683 protected Bundle captureSharedElementState() { 684 Bundle bundle = new Bundle(); 685 RectF tempBounds = new RectF(); 686 Matrix tempMatrix = new Matrix(); 687 for (int i = 0; i < mSharedElements.size(); i++) { 688 View sharedElement = mSharedElements.get(i); 689 String name = mSharedElementNames.get(i); 690 captureSharedElementState(sharedElement, name, bundle, tempMatrix, tempBounds); 691 } 692 return bundle; 693 } 694 clearState()695 protected void clearState() { 696 // Clear the state so that we can't hold any references accidentally and leak memory. 697 mWindow = null; 698 mSharedElements.clear(); 699 mTransitioningViews = null; 700 mOriginalAlphas.clear(); 701 mResultReceiver = null; 702 mPendingTransition = null; 703 mListener = null; 704 mRootSharedElements.clear(); 705 mSharedElementParentMatrices = null; 706 } 707 getFadeDuration()708 protected long getFadeDuration() { 709 return getWindow().getTransitionBackgroundFadeDuration(); 710 } 711 hideViews(ArrayList<View> views)712 protected void hideViews(ArrayList<View> views) { 713 int count = views.size(); 714 for (int i = 0; i < count; i++) { 715 View view = views.get(i); 716 if (!mOriginalAlphas.containsKey(view)) { 717 mOriginalAlphas.put(view, view.getAlpha()); 718 } 719 view.setAlpha(0f); 720 } 721 } 722 showViews(ArrayList<View> views, boolean setTransitionAlpha)723 protected void showViews(ArrayList<View> views, boolean setTransitionAlpha) { 724 int count = views.size(); 725 for (int i = 0; i < count; i++) { 726 showView(views.get(i), setTransitionAlpha); 727 } 728 } 729 showView(View view, boolean setTransitionAlpha)730 private void showView(View view, boolean setTransitionAlpha) { 731 Float alpha = mOriginalAlphas.remove(view); 732 if (alpha != null) { 733 view.setAlpha(alpha); 734 } 735 if (setTransitionAlpha) { 736 view.setTransitionAlpha(1f); 737 } 738 } 739 740 /** 741 * Captures placement information for Views with a shared element name for 742 * Activity Transitions. 743 * 744 * @param view The View to capture the placement information for. 745 * @param name The shared element name in the target Activity to apply the placement 746 * information for. 747 * @param transitionArgs Bundle to store shared element placement information. 748 * @param tempBounds A temporary Rect for capturing the current location of views. 749 */ captureSharedElementState(View view, String name, Bundle transitionArgs, Matrix tempMatrix, RectF tempBounds)750 protected void captureSharedElementState(View view, String name, Bundle transitionArgs, 751 Matrix tempMatrix, RectF tempBounds) { 752 Bundle sharedElementBundle = new Bundle(); 753 tempMatrix.reset(); 754 view.transformMatrixToGlobal(tempMatrix); 755 tempBounds.set(0, 0, view.getWidth(), view.getHeight()); 756 tempMatrix.mapRect(tempBounds); 757 758 sharedElementBundle.putFloat(KEY_SCREEN_LEFT, tempBounds.left); 759 sharedElementBundle.putFloat(KEY_SCREEN_RIGHT, tempBounds.right); 760 sharedElementBundle.putFloat(KEY_SCREEN_TOP, tempBounds.top); 761 sharedElementBundle.putFloat(KEY_SCREEN_BOTTOM, tempBounds.bottom); 762 sharedElementBundle.putFloat(KEY_TRANSLATION_Z, view.getTranslationZ()); 763 sharedElementBundle.putFloat(KEY_ELEVATION, view.getElevation()); 764 765 Parcelable bitmap = null; 766 if (mListener != null) { 767 bitmap = mListener.onCaptureSharedElementSnapshot(view, tempMatrix, tempBounds); 768 } 769 770 if (bitmap != null) { 771 sharedElementBundle.putParcelable(KEY_SNAPSHOT, bitmap); 772 } 773 774 if (view instanceof ImageView) { 775 ImageView imageView = (ImageView) view; 776 int scaleTypeInt = scaleTypeToInt(imageView.getScaleType()); 777 sharedElementBundle.putInt(KEY_SCALE_TYPE, scaleTypeInt); 778 if (imageView.getScaleType() == ImageView.ScaleType.MATRIX) { 779 float[] matrix = new float[9]; 780 imageView.getImageMatrix().getValues(matrix); 781 sharedElementBundle.putFloatArray(KEY_IMAGE_MATRIX, matrix); 782 } 783 } 784 785 transitionArgs.putBundle(name, sharedElementBundle); 786 } 787 788 startTransition(Runnable runnable)789 protected void startTransition(Runnable runnable) { 790 if (mIsStartingTransition) { 791 mPendingTransition = runnable; 792 } else { 793 mIsStartingTransition = true; 794 runnable.run(); 795 } 796 } 797 transitionStarted()798 protected void transitionStarted() { 799 mIsStartingTransition = false; 800 } 801 802 /** 803 * Cancels any pending transitions and returns true if there is a transition is in 804 * the middle of starting. 805 */ cancelPendingTransitions()806 protected boolean cancelPendingTransitions() { 807 mPendingTransition = null; 808 return mIsStartingTransition; 809 } 810 moveSharedElementsToOverlay()811 protected void moveSharedElementsToOverlay() { 812 if (mWindow == null || !mWindow.getSharedElementsUseOverlay()) { 813 return; 814 } 815 setSharedElementMatrices(); 816 int numSharedElements = mSharedElements.size(); 817 ViewGroup decor = getDecor(); 818 if (decor != null) { 819 boolean moveWithParent = moveSharedElementWithParent(); 820 for (int i = 0; i < numSharedElements; i++) { 821 View view = mSharedElements.get(i); 822 GhostView.addGhost(view, decor); 823 ViewGroup parent = (ViewGroup) view.getParent(); 824 if (moveWithParent && !isInTransitionGroup(parent, decor)) { 825 GhostViewListeners listener = new GhostViewListeners(view, parent, decor); 826 parent.getViewTreeObserver().addOnPreDrawListener(listener); 827 mGhostViewListeners.add(listener); 828 } 829 } 830 } 831 } 832 moveSharedElementWithParent()833 protected boolean moveSharedElementWithParent() { 834 return true; 835 } 836 isInTransitionGroup(ViewParent viewParent, ViewGroup decor)837 public static boolean isInTransitionGroup(ViewParent viewParent, ViewGroup decor) { 838 if (viewParent == decor || !(viewParent instanceof ViewGroup)) { 839 return false; 840 } 841 ViewGroup parent = (ViewGroup) viewParent; 842 if (parent.isTransitionGroup()) { 843 return true; 844 } else { 845 return isInTransitionGroup(parent.getParent(), decor); 846 } 847 } 848 moveSharedElementsFromOverlay()849 protected void moveSharedElementsFromOverlay() { 850 int numListeners = mGhostViewListeners.size(); 851 for (int i = 0; i < numListeners; i++) { 852 GhostViewListeners listener = mGhostViewListeners.get(i); 853 ViewGroup parent = (ViewGroup) listener.getView().getParent(); 854 parent.getViewTreeObserver().removeOnPreDrawListener(listener); 855 } 856 mGhostViewListeners.clear(); 857 858 if (mWindow == null || !mWindow.getSharedElementsUseOverlay()) { 859 return; 860 } 861 ViewGroup decor = getDecor(); 862 if (decor != null) { 863 ViewGroupOverlay overlay = decor.getOverlay(); 864 int count = mSharedElements.size(); 865 for (int i = 0; i < count; i++) { 866 View sharedElement = mSharedElements.get(i); 867 GhostView.removeGhost(sharedElement); 868 } 869 } 870 } 871 setGhostVisibility(int visibility)872 protected void setGhostVisibility(int visibility) { 873 int numSharedElements = mSharedElements.size(); 874 for (int i = 0; i < numSharedElements; i++) { 875 GhostView ghostView = GhostView.getGhost(mSharedElements.get(i)); 876 if (ghostView != null) { 877 ghostView.setVisibility(visibility); 878 } 879 } 880 } 881 scheduleGhostVisibilityChange(final int visibility)882 protected void scheduleGhostVisibilityChange(final int visibility) { 883 final View decorView = getDecor(); 884 if (decorView != null) { 885 decorView.getViewTreeObserver() 886 .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { 887 @Override 888 public boolean onPreDraw() { 889 decorView.getViewTreeObserver().removeOnPreDrawListener(this); 890 setGhostVisibility(visibility); 891 return true; 892 } 893 }); 894 } 895 } 896 897 protected class ContinueTransitionListener extends Transition.TransitionListenerAdapter { 898 @Override onTransitionStart(Transition transition)899 public void onTransitionStart(Transition transition) { 900 mIsStartingTransition = false; 901 Runnable pending = mPendingTransition; 902 mPendingTransition = null; 903 if (pending != null) { 904 startTransition(pending); 905 } 906 } 907 } 908 scaleTypeToInt(ImageView.ScaleType scaleType)909 private static int scaleTypeToInt(ImageView.ScaleType scaleType) { 910 for (int i = 0; i < SCALE_TYPE_VALUES.length; i++) { 911 if (scaleType == SCALE_TYPE_VALUES[i]) { 912 return i; 913 } 914 } 915 return -1; 916 } 917 918 private static class FixedEpicenterCallback extends Transition.EpicenterCallback { 919 private Rect mEpicenter; 920 setEpicenter(Rect epicenter)921 public void setEpicenter(Rect epicenter) { mEpicenter = epicenter; } 922 923 @Override onGetEpicenter(Transition transition)924 public Rect onGetEpicenter(Transition transition) { 925 return mEpicenter; 926 } 927 } 928 929 private static class GhostViewListeners implements ViewTreeObserver.OnPreDrawListener { 930 private View mView; 931 private ViewGroup mDecor; 932 private View mParent; 933 private Matrix mMatrix = new Matrix(); 934 GhostViewListeners(View view, View parent, ViewGroup decor)935 public GhostViewListeners(View view, View parent, ViewGroup decor) { 936 mView = view; 937 mParent = parent; 938 mDecor = decor; 939 } 940 getView()941 public View getView() { 942 return mView; 943 } 944 945 @Override onPreDraw()946 public boolean onPreDraw() { 947 GhostView ghostView = GhostView.getGhost(mView); 948 if (ghostView == null) { 949 mParent.getViewTreeObserver().removeOnPreDrawListener(this); 950 } else { 951 GhostView.calculateMatrix(mView, mDecor, mMatrix); 952 ghostView.setMatrix(mMatrix); 953 } 954 return true; 955 } 956 } 957 958 static class SharedElementOriginalState { 959 int mLeft; 960 int mTop; 961 int mRight; 962 int mBottom; 963 int mMeasuredWidth; 964 int mMeasuredHeight; 965 ImageView.ScaleType mScaleType; 966 Matrix mMatrix; 967 float mTranslationZ; 968 float mElevation; 969 } 970 } 971