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.TransitionListenerAdapter; 28 import android.transition.TransitionSet; 29 import android.transition.Visibility; 30 import android.util.ArrayMap; 31 import android.util.ArraySet; 32 import android.view.GhostView; 33 import android.view.View; 34 import android.view.ViewGroup; 35 import android.view.ViewGroupOverlay; 36 import android.view.ViewParent; 37 import android.view.ViewRootImpl; 38 import android.view.ViewTreeObserver; 39 import android.view.Window; 40 import android.widget.ImageView; 41 42 import com.android.internal.view.OneShotPreDrawListener; 43 44 import java.util.ArrayList; 45 import java.util.Collection; 46 47 /** 48 * Base class for ExitTransitionCoordinator and EnterTransitionCoordinator, classes 49 * that manage activity transitions and the communications coordinating them between 50 * Activities. The ExitTransitionCoordinator is created in the 51 * ActivityOptions#makeSceneTransitionAnimation. The EnterTransitionCoordinator 52 * is created by ActivityOptions#createEnterActivityTransition by Activity when the window is 53 * attached. 54 * 55 * Typical startActivity goes like this: 56 * 1) ExitTransitionCoordinator created with ActivityOptions#makeSceneTransitionAnimation 57 * 2) Activity#startActivity called and that calls startExit() through 58 * ActivityOptions#dispatchStartExit 59 * - Exit transition starts by setting transitioning Views to INVISIBLE 60 * 3) Launched Activity starts, creating an EnterTransitionCoordinator. 61 * - The Window is made translucent 62 * - The Window background alpha is set to 0 63 * - The transitioning views are made INVISIBLE 64 * - MSG_SET_LISTENER is sent back to the ExitTransitionCoordinator. 65 * 4) The shared element transition completes. 66 * - MSG_TAKE_SHARED_ELEMENTS is sent to the EnterTransitionCoordinator 67 * 5) The MSG_TAKE_SHARED_ELEMENTS is received by the EnterTransitionCoordinator. 68 * - Shared elements are made VISIBLE 69 * - Shared elements positions and size are set to match the end state of the calling 70 * Activity. 71 * - The shared element transition is started 72 * - If the window allows overlapping transitions, the views transition is started by setting 73 * the entering Views to VISIBLE and the background alpha is animated to opaque. 74 * - MSG_HIDE_SHARED_ELEMENTS is sent to the ExitTransitionCoordinator 75 * 6) MSG_HIDE_SHARED_ELEMENTS is received by the ExitTransitionCoordinator 76 * - The shared elements are made INVISIBLE 77 * 7) The exit transition completes in the calling Activity. 78 * - MSG_EXIT_TRANSITION_COMPLETE is sent to the EnterTransitionCoordinator. 79 * 8) The MSG_EXIT_TRANSITION_COMPLETE is received by the EnterTransitionCoordinator. 80 * - If the window doesn't allow overlapping enter transitions, the enter transition is started 81 * by setting entering views to VISIBLE and the background is animated to opaque. 82 * 9) The background opacity animation completes. 83 * - The window is made opaque 84 * 10) The calling Activity gets an onStop() call 85 * - onActivityStopped() is called and all exited Views are made VISIBLE. 86 * 87 * Typical finishAfterTransition goes like this: 88 * 1) finishAfterTransition() creates an ExitTransitionCoordinator and calls startExit() 89 * - The Window start transitioning to Translucent with a new ActivityOptions. 90 * - If no background exists, a black background is substituted 91 * - The shared elements in the scene are matched against those shared elements 92 * that were sent by comparing the names. 93 * - The exit transition is started by setting Views to INVISIBLE. 94 * 2) The ActivityOptions is received by the Activity and an EnterTransitionCoordinator is created. 95 * - All transitioning views are made VISIBLE to reverse what was done when onActivityStopped() 96 * was called 97 * 3) The Window is made translucent and a callback is received 98 * - The background alpha is animated to 0 99 * 4) The background alpha animation completes 100 * 5) The shared element transition completes 101 * - After both 4 & 5 complete, MSG_TAKE_SHARED_ELEMENTS is sent to the 102 * EnterTransitionCoordinator 103 * 6) MSG_TAKE_SHARED_ELEMENTS is received by EnterTransitionCoordinator 104 * - Shared elements are made VISIBLE 105 * - Shared elements positions and size are set to match the end state of the calling 106 * Activity. 107 * - The shared element transition is started 108 * - If the window allows overlapping transitions, the views transition is started by setting 109 * the entering Views to VISIBLE. 110 * - MSG_HIDE_SHARED_ELEMENTS is sent to the ExitTransitionCoordinator 111 * 7) MSG_HIDE_SHARED_ELEMENTS is received by the ExitTransitionCoordinator 112 * - The shared elements are made INVISIBLE 113 * 8) The exit transition completes in the finishing Activity. 114 * - MSG_EXIT_TRANSITION_COMPLETE is sent to the EnterTransitionCoordinator. 115 * - finish() is called on the exiting Activity 116 * 9) The MSG_EXIT_TRANSITION_COMPLETE is received by the EnterTransitionCoordinator. 117 * - If the window doesn't allow overlapping enter transitions, the enter transition is started 118 * by setting entering views to VISIBLE. 119 */ 120 abstract class ActivityTransitionCoordinator extends ResultReceiver { 121 private static final String TAG = "ActivityTransitionCoordinator"; 122 123 /** 124 * For Activity transitions, the called Activity's listener to receive calls 125 * when transitions complete. 126 */ 127 static final String KEY_REMOTE_RECEIVER = "android:remoteReceiver"; 128 129 protected static final String KEY_SCREEN_LEFT = "shared_element:screenLeft"; 130 protected static final String KEY_SCREEN_TOP = "shared_element:screenTop"; 131 protected static final String KEY_SCREEN_RIGHT = "shared_element:screenRight"; 132 protected static final String KEY_SCREEN_BOTTOM= "shared_element:screenBottom"; 133 protected static final String KEY_TRANSLATION_Z = "shared_element:translationZ"; 134 protected static final String KEY_SNAPSHOT = "shared_element:bitmap"; 135 protected static final String KEY_SCALE_TYPE = "shared_element:scaleType"; 136 protected static final String KEY_IMAGE_MATRIX = "shared_element:imageMatrix"; 137 protected static final String KEY_ELEVATION = "shared_element:elevation"; 138 139 protected static final ImageView.ScaleType[] SCALE_TYPE_VALUES = ImageView.ScaleType.values(); 140 141 /** 142 * Sent by the exiting coordinator (either EnterTransitionCoordinator 143 * or ExitTransitionCoordinator) after the shared elements have 144 * become stationary (shared element transition completes). This tells 145 * the remote coordinator to take control of the shared elements and 146 * that animations may begin. The remote Activity won't start entering 147 * until this message is received, but may wait for 148 * MSG_EXIT_TRANSITION_COMPLETE if allowOverlappingTransitions() is true. 149 */ 150 public static final int MSG_SET_REMOTE_RECEIVER = 100; 151 152 /** 153 * Sent by the entering coordinator to tell the exiting coordinator 154 * to hide its shared elements after it has started its shared 155 * element transition. This is temporary until the 156 * interlock of shared elements is figured out. 157 */ 158 public static final int MSG_HIDE_SHARED_ELEMENTS = 101; 159 160 /** 161 * Sent by the exiting coordinator (either EnterTransitionCoordinator 162 * or ExitTransitionCoordinator) after the shared elements have 163 * become stationary (shared element transition completes). This tells 164 * the remote coordinator to take control of the shared elements and 165 * that animations may begin. The remote Activity won't start entering 166 * until this message is received, but may wait for 167 * MSG_EXIT_TRANSITION_COMPLETE if allowOverlappingTransitions() is true. 168 */ 169 public static final int MSG_TAKE_SHARED_ELEMENTS = 103; 170 171 /** 172 * Sent by the exiting coordinator (either 173 * EnterTransitionCoordinator or ExitTransitionCoordinator) after 174 * the exiting Views have finished leaving the scene. This will 175 * be ignored if allowOverlappingTransitions() is true on the 176 * remote coordinator. If it is false, it will trigger the enter 177 * transition to start. 178 */ 179 public static final int MSG_EXIT_TRANSITION_COMPLETE = 104; 180 181 /** 182 * Sent by Activity#startActivity to begin the exit transition. 183 */ 184 public static final int MSG_START_EXIT_TRANSITION = 105; 185 186 /** 187 * It took too long for a message from the entering Activity, so we canceled the transition. 188 */ 189 public static final int MSG_CANCEL = 106; 190 191 /** 192 * When returning, this is the destination location for the shared element. 193 */ 194 public static final int MSG_SHARED_ELEMENT_DESTINATION = 107; 195 196 private Window mWindow; 197 final protected ArrayList<String> mAllSharedElementNames; 198 final protected ArrayList<View> mSharedElements = new ArrayList<View>(); 199 final protected ArrayList<String> mSharedElementNames = new ArrayList<String>(); 200 protected ArrayList<View> mTransitioningViews = new ArrayList<View>(); 201 protected SharedElementCallback mListener; 202 protected ResultReceiver mResultReceiver; 203 final private FixedEpicenterCallback mEpicenterCallback = new FixedEpicenterCallback(); 204 final protected boolean mIsReturning; 205 private Runnable mPendingTransition; 206 private boolean mIsStartingTransition; 207 private ArrayList<GhostViewListeners> mGhostViewListeners = 208 new ArrayList<GhostViewListeners>(); 209 private ArrayMap<View, Float> mOriginalAlphas = new ArrayMap<View, Float>(); 210 private ArrayList<Matrix> mSharedElementParentMatrices; 211 private boolean mSharedElementTransitionComplete; 212 private boolean mViewsTransitionComplete; 213 private boolean mBackgroundAnimatorComplete; 214 private ArrayList<View> mStrippedTransitioningViews = new ArrayList<>(); 215 ActivityTransitionCoordinator(Window window, ArrayList<String> allSharedElementNames, SharedElementCallback listener, boolean isReturning)216 public ActivityTransitionCoordinator(Window window, 217 ArrayList<String> allSharedElementNames, 218 SharedElementCallback listener, boolean isReturning) { 219 super(new Handler()); 220 mWindow = window; 221 mListener = listener; 222 mAllSharedElementNames = allSharedElementNames; 223 mIsReturning = isReturning; 224 } 225 viewsReady(ArrayMap<String, View> sharedElements)226 protected void viewsReady(ArrayMap<String, View> sharedElements) { 227 sharedElements.retainAll(mAllSharedElementNames); 228 if (mListener != null) { 229 mListener.onMapSharedElements(mAllSharedElementNames, sharedElements); 230 } 231 setSharedElements(sharedElements); 232 if (getViewsTransition() != null && mTransitioningViews != null) { 233 ViewGroup decorView = getDecor(); 234 if (decorView != null) { 235 decorView.captureTransitioningViews(mTransitioningViews); 236 } 237 mTransitioningViews.removeAll(mSharedElements); 238 } 239 setEpicenter(); 240 } 241 242 /** 243 * Iterates over the shared elements and adds them to the members in order. 244 * Shared elements that are nested in other shared elements are placed after the 245 * elements that they are nested in. This means that layout ordering can be done 246 * from first to last. 247 * 248 * @param sharedElements The map of transition names to shared elements to set into 249 * the member fields. 250 */ setSharedElements(ArrayMap<String, View> sharedElements)251 private void setSharedElements(ArrayMap<String, View> sharedElements) { 252 boolean isFirstRun = true; 253 while (!sharedElements.isEmpty()) { 254 final int numSharedElements = sharedElements.size(); 255 for (int i = numSharedElements - 1; i >= 0; i--) { 256 final View view = sharedElements.valueAt(i); 257 final String name = sharedElements.keyAt(i); 258 if (isFirstRun && (view == null || !view.isAttachedToWindow() || name == null)) { 259 sharedElements.removeAt(i); 260 } else if (!isNested(view, sharedElements)) { 261 mSharedElementNames.add(name); 262 mSharedElements.add(view); 263 sharedElements.removeAt(i); 264 } 265 } 266 isFirstRun = false; 267 } 268 } 269 270 /** 271 * Returns true when view is nested in any of the values of sharedElements. 272 */ isNested(View view, ArrayMap<String, View> sharedElements)273 private static boolean isNested(View view, ArrayMap<String, View> sharedElements) { 274 ViewParent parent = view.getParent(); 275 boolean isNested = false; 276 while (parent instanceof View) { 277 View parentView = (View) parent; 278 if (sharedElements.containsValue(parentView)) { 279 isNested = true; 280 break; 281 } 282 parent = parentView.getParent(); 283 } 284 return isNested; 285 } 286 stripOffscreenViews()287 protected void stripOffscreenViews() { 288 if (mTransitioningViews == null) { 289 return; 290 } 291 Rect r = new Rect(); 292 for (int i = mTransitioningViews.size() - 1; i >= 0; i--) { 293 View view = mTransitioningViews.get(i); 294 if (!view.getGlobalVisibleRect(r)) { 295 mTransitioningViews.remove(i); 296 mStrippedTransitioningViews.add(view); 297 } 298 } 299 } 300 getWindow()301 protected Window getWindow() { 302 return mWindow; 303 } 304 getDecor()305 public ViewGroup getDecor() { 306 return (mWindow == null) ? null : (ViewGroup) mWindow.getDecorView(); 307 } 308 309 /** 310 * Sets the transition epicenter to the position of the first shared element. 311 */ setEpicenter()312 protected void setEpicenter() { 313 View epicenter = null; 314 if (!mAllSharedElementNames.isEmpty() && !mSharedElementNames.isEmpty()) { 315 int index = mSharedElementNames.indexOf(mAllSharedElementNames.get(0)); 316 if (index >= 0) { 317 epicenter = mSharedElements.get(index); 318 } 319 } 320 setEpicenter(epicenter); 321 } 322 setEpicenter(View view)323 private void setEpicenter(View view) { 324 if (view == null) { 325 mEpicenterCallback.setEpicenter(null); 326 } else { 327 Rect epicenter = new Rect(); 328 view.getBoundsOnScreen(epicenter); 329 mEpicenterCallback.setEpicenter(epicenter); 330 } 331 } 332 getAcceptedNames()333 public ArrayList<String> getAcceptedNames() { 334 return mSharedElementNames; 335 } 336 getMappedNames()337 public ArrayList<String> getMappedNames() { 338 ArrayList<String> names = new ArrayList<String>(mSharedElements.size()); 339 for (int i = 0; i < mSharedElements.size(); i++) { 340 names.add(mSharedElements.get(i).getTransitionName()); 341 } 342 return names; 343 } 344 copyMappedViews()345 public ArrayList<View> copyMappedViews() { 346 return new ArrayList<View>(mSharedElements); 347 } 348 getAllSharedElementNames()349 public ArrayList<String> getAllSharedElementNames() { return mAllSharedElementNames; } 350 setTargets(Transition transition, boolean add)351 protected Transition setTargets(Transition transition, boolean add) { 352 if (transition == null || (add && 353 (mTransitioningViews == null || mTransitioningViews.isEmpty()))) { 354 return null; 355 } 356 // Add the targets to a set containing transition so that transition 357 // remains unaffected. We don't want to modify the targets of transition itself. 358 TransitionSet set = new TransitionSet(); 359 if (mTransitioningViews != null) { 360 for (int i = mTransitioningViews.size() - 1; i >= 0; i--) { 361 View view = mTransitioningViews.get(i); 362 if (add) { 363 set.addTarget(view); 364 } else { 365 set.excludeTarget(view, true); 366 } 367 } 368 } 369 if (mStrippedTransitioningViews != null) { 370 for (int i = mStrippedTransitioningViews.size() - 1; i >= 0; i--) { 371 View view = mStrippedTransitioningViews.get(i); 372 set.excludeTarget(view, true); 373 } 374 } 375 // By adding the transition after addTarget, we prevent addTarget from 376 // affecting transition. 377 set.addTransition(transition); 378 379 if (!add && mTransitioningViews != null && !mTransitioningViews.isEmpty()) { 380 // Allow children of excluded transitioning views, but not the views themselves 381 set = new TransitionSet().addTransition(set); 382 } 383 384 return set; 385 } 386 configureTransition(Transition transition, boolean includeTransitioningViews)387 protected Transition configureTransition(Transition transition, 388 boolean includeTransitioningViews) { 389 if (transition != null) { 390 transition = transition.clone(); 391 transition.setEpicenterCallback(mEpicenterCallback); 392 transition = setTargets(transition, includeTransitioningViews); 393 } 394 noLayoutSuppressionForVisibilityTransitions(transition); 395 return transition; 396 } 397 398 /** 399 * Looks through the transition to see which Views have been included and which have been 400 * excluded. {@code views} will be modified to contain only those Views that are included 401 * in the transition. If {@code transition} is a TransitionSet, it will search through all 402 * contained Transitions to find targeted Views. 403 * 404 * @param transition The transition to look through for inclusion of Views 405 * @param views The list of Views that are to be checked for inclusion. Will be modified 406 * to remove all excluded Views, possibly leaving an empty list. 407 */ removeExcludedViews(Transition transition, ArrayList<View> views)408 protected static void removeExcludedViews(Transition transition, ArrayList<View> views) { 409 ArraySet<View> included = new ArraySet<>(); 410 findIncludedViews(transition, views, included); 411 views.clear(); 412 views.addAll(included); 413 } 414 415 /** 416 * Looks through the transition to see which Views have been included. Only {@code views} 417 * will be examined for inclusion. If {@code transition} is a TransitionSet, it will search 418 * through all contained Transitions to find targeted Views. 419 * 420 * @param transition The transition to look through for inclusion of Views 421 * @param views The list of Views that are to be checked for inclusion. 422 * @param included Modified to contain all Views in views that have at least one Transition 423 * that affects it. 424 */ findIncludedViews(Transition transition, ArrayList<View> views, ArraySet<View> included)425 private static void findIncludedViews(Transition transition, ArrayList<View> views, 426 ArraySet<View> included) { 427 if (transition instanceof TransitionSet) { 428 TransitionSet set = (TransitionSet) transition; 429 ArrayList<View> includedViews = new ArrayList<>(); 430 final int numViews = views.size(); 431 for (int i = 0; i < numViews; i++) { 432 final View view = views.get(i); 433 if (transition.isValidTarget(view)) { 434 includedViews.add(view); 435 } 436 } 437 final int count = set.getTransitionCount(); 438 for (int i = 0; i < count; i++) { 439 findIncludedViews(set.getTransitionAt(i), includedViews, included); 440 } 441 } else { 442 final int numViews = views.size(); 443 for (int i = 0; i < numViews; i++) { 444 final View view = views.get(i); 445 if (transition.isValidTarget(view)) { 446 included.add(view); 447 } 448 } 449 } 450 } 451 mergeTransitions(Transition transition1, Transition transition2)452 protected static Transition mergeTransitions(Transition transition1, Transition transition2) { 453 if (transition1 == null) { 454 return transition2; 455 } else if (transition2 == null) { 456 return transition1; 457 } else { 458 TransitionSet transitionSet = new TransitionSet(); 459 transitionSet.addTransition(transition1); 460 transitionSet.addTransition(transition2); 461 return transitionSet; 462 } 463 } 464 mapSharedElements(ArrayList<String> accepted, ArrayList<View> localViews)465 protected ArrayMap<String, View> mapSharedElements(ArrayList<String> accepted, 466 ArrayList<View> localViews) { 467 ArrayMap<String, View> sharedElements = new ArrayMap<String, View>(); 468 if (accepted != null) { 469 for (int i = 0; i < accepted.size(); i++) { 470 sharedElements.put(accepted.get(i), localViews.get(i)); 471 } 472 } else { 473 ViewGroup decorView = getDecor(); 474 if (decorView != null) { 475 decorView.findNamedViews(sharedElements); 476 } 477 } 478 return sharedElements; 479 } 480 setResultReceiver(ResultReceiver resultReceiver)481 protected void setResultReceiver(ResultReceiver resultReceiver) { 482 mResultReceiver = resultReceiver; 483 } 484 getViewsTransition()485 protected abstract Transition getViewsTransition(); 486 setSharedElementState(View view, String name, Bundle transitionArgs, Matrix tempMatrix, RectF tempRect, int[] decorLoc)487 private void setSharedElementState(View view, String name, Bundle transitionArgs, 488 Matrix tempMatrix, RectF tempRect, int[] decorLoc) { 489 Bundle sharedElementBundle = transitionArgs.getBundle(name); 490 if (sharedElementBundle == null) { 491 return; 492 } 493 494 if (view instanceof ImageView) { 495 int scaleTypeInt = sharedElementBundle.getInt(KEY_SCALE_TYPE, -1); 496 if (scaleTypeInt >= 0) { 497 ImageView imageView = (ImageView) view; 498 ImageView.ScaleType scaleType = SCALE_TYPE_VALUES[scaleTypeInt]; 499 imageView.setScaleType(scaleType); 500 if (scaleType == ImageView.ScaleType.MATRIX) { 501 float[] matrixValues = sharedElementBundle.getFloatArray(KEY_IMAGE_MATRIX); 502 tempMatrix.setValues(matrixValues); 503 imageView.setImageMatrix(tempMatrix); 504 } 505 } 506 } 507 508 float z = sharedElementBundle.getFloat(KEY_TRANSLATION_Z); 509 view.setTranslationZ(z); 510 float elevation = sharedElementBundle.getFloat(KEY_ELEVATION); 511 view.setElevation(elevation); 512 513 float left = sharedElementBundle.getFloat(KEY_SCREEN_LEFT); 514 float top = sharedElementBundle.getFloat(KEY_SCREEN_TOP); 515 float right = sharedElementBundle.getFloat(KEY_SCREEN_RIGHT); 516 float bottom = sharedElementBundle.getFloat(KEY_SCREEN_BOTTOM); 517 518 if (decorLoc != null) { 519 left -= decorLoc[0]; 520 top -= decorLoc[1]; 521 right -= decorLoc[0]; 522 bottom -= decorLoc[1]; 523 } else { 524 // Find the location in the view's parent 525 getSharedElementParentMatrix(view, tempMatrix); 526 tempRect.set(left, top, right, bottom); 527 tempMatrix.mapRect(tempRect); 528 529 float leftInParent = tempRect.left; 530 float topInParent = tempRect.top; 531 532 // Find the size of the view 533 view.getInverseMatrix().mapRect(tempRect); 534 float width = tempRect.width(); 535 float height = tempRect.height(); 536 537 // Now determine the offset due to view transform: 538 view.setLeft(0); 539 view.setTop(0); 540 view.setRight(Math.round(width)); 541 view.setBottom(Math.round(height)); 542 tempRect.set(0, 0, width, height); 543 view.getMatrix().mapRect(tempRect); 544 545 left = leftInParent - tempRect.left; 546 top = topInParent - tempRect.top; 547 right = left + width; 548 bottom = top + height; 549 } 550 551 int x = Math.round(left); 552 int y = Math.round(top); 553 int width = Math.round(right) - x; 554 int height = Math.round(bottom) - y; 555 int widthSpec = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY); 556 int heightSpec = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY); 557 view.measure(widthSpec, heightSpec); 558 559 view.layout(x, y, x + width, y + height); 560 } 561 setSharedElementMatrices()562 private void setSharedElementMatrices() { 563 int numSharedElements = mSharedElements.size(); 564 if (numSharedElements > 0) { 565 mSharedElementParentMatrices = new ArrayList<Matrix>(numSharedElements); 566 } 567 for (int i = 0; i < numSharedElements; i++) { 568 View view = mSharedElements.get(i); 569 570 // Find the location in the view's parent 571 ViewGroup parent = (ViewGroup) view.getParent(); 572 Matrix matrix = new Matrix(); 573 if (parent != null) { 574 parent.transformMatrixToLocal(matrix); 575 matrix.postTranslate(parent.getScrollX(), parent.getScrollY()); 576 } 577 mSharedElementParentMatrices.add(matrix); 578 } 579 } 580 getSharedElementParentMatrix(View view, Matrix matrix)581 private void getSharedElementParentMatrix(View view, Matrix matrix) { 582 final int index = mSharedElementParentMatrices == null ? -1 583 : mSharedElements.indexOf(view); 584 if (index < 0) { 585 matrix.reset(); 586 ViewParent viewParent = view.getParent(); 587 if (viewParent instanceof ViewGroup) { 588 // Find the location in the view's parent 589 ViewGroup parent = (ViewGroup) viewParent; 590 parent.transformMatrixToLocal(matrix); 591 matrix.postTranslate(parent.getScrollX(), parent.getScrollY()); 592 } 593 } else { 594 // The indices of mSharedElementParentMatrices matches the 595 // mSharedElement matrices. 596 Matrix parentMatrix = mSharedElementParentMatrices.get(index); 597 matrix.set(parentMatrix); 598 } 599 } 600 setSharedElementState( Bundle sharedElementState, final ArrayList<View> snapshots)601 protected ArrayList<SharedElementOriginalState> setSharedElementState( 602 Bundle sharedElementState, final ArrayList<View> snapshots) { 603 ArrayList<SharedElementOriginalState> originalImageState = 604 new ArrayList<SharedElementOriginalState>(); 605 if (sharedElementState != null) { 606 Matrix tempMatrix = new Matrix(); 607 RectF tempRect = new RectF(); 608 final int numSharedElements = mSharedElements.size(); 609 for (int i = 0; i < numSharedElements; i++) { 610 View sharedElement = mSharedElements.get(i); 611 String name = mSharedElementNames.get(i); 612 SharedElementOriginalState originalState = getOldSharedElementState(sharedElement, 613 name, sharedElementState); 614 originalImageState.add(originalState); 615 setSharedElementState(sharedElement, name, sharedElementState, 616 tempMatrix, tempRect, null); 617 } 618 } 619 if (mListener != null) { 620 mListener.onSharedElementStart(mSharedElementNames, mSharedElements, snapshots); 621 } 622 return originalImageState; 623 } 624 notifySharedElementEnd(ArrayList<View> snapshots)625 protected void notifySharedElementEnd(ArrayList<View> snapshots) { 626 if (mListener != null) { 627 mListener.onSharedElementEnd(mSharedElementNames, mSharedElements, snapshots); 628 } 629 } 630 scheduleSetSharedElementEnd(final ArrayList<View> snapshots)631 protected void scheduleSetSharedElementEnd(final ArrayList<View> snapshots) { 632 final View decorView = getDecor(); 633 if (decorView != null) { 634 OneShotPreDrawListener.add(decorView, () -> { 635 notifySharedElementEnd(snapshots); 636 }); 637 } 638 } 639 getOldSharedElementState(View view, String name, Bundle transitionArgs)640 private static SharedElementOriginalState getOldSharedElementState(View view, String name, 641 Bundle transitionArgs) { 642 643 SharedElementOriginalState state = new SharedElementOriginalState(); 644 state.mLeft = view.getLeft(); 645 state.mTop = view.getTop(); 646 state.mRight = view.getRight(); 647 state.mBottom = view.getBottom(); 648 state.mMeasuredWidth = view.getMeasuredWidth(); 649 state.mMeasuredHeight = view.getMeasuredHeight(); 650 state.mTranslationZ = view.getTranslationZ(); 651 state.mElevation = view.getElevation(); 652 if (!(view instanceof ImageView)) { 653 return state; 654 } 655 Bundle bundle = transitionArgs.getBundle(name); 656 if (bundle == null) { 657 return state; 658 } 659 int scaleTypeInt = bundle.getInt(KEY_SCALE_TYPE, -1); 660 if (scaleTypeInt < 0) { 661 return state; 662 } 663 664 ImageView imageView = (ImageView) view; 665 state.mScaleType = imageView.getScaleType(); 666 if (state.mScaleType == ImageView.ScaleType.MATRIX) { 667 state.mMatrix = new Matrix(imageView.getImageMatrix()); 668 } 669 return state; 670 } 671 createSnapshots(Bundle state, Collection<String> names)672 protected ArrayList<View> createSnapshots(Bundle state, Collection<String> names) { 673 int numSharedElements = names.size(); 674 ArrayList<View> snapshots = new ArrayList<View>(numSharedElements); 675 if (numSharedElements == 0) { 676 return snapshots; 677 } 678 Context context = getWindow().getContext(); 679 int[] decorLoc = new int[2]; 680 ViewGroup decorView = getDecor(); 681 if (decorView != null) { 682 decorView.getLocationOnScreen(decorLoc); 683 } 684 Matrix tempMatrix = new Matrix(); 685 for (String name: names) { 686 Bundle sharedElementBundle = state.getBundle(name); 687 View snapshot = null; 688 if (sharedElementBundle != null) { 689 Parcelable parcelable = sharedElementBundle.getParcelable(KEY_SNAPSHOT); 690 if (parcelable != null && mListener != null) { 691 snapshot = mListener.onCreateSnapshotView(context, parcelable); 692 } 693 if (snapshot != null) { 694 setSharedElementState(snapshot, name, state, tempMatrix, null, decorLoc); 695 } 696 } 697 // Even null snapshots are added so they remain in the same order as shared elements. 698 snapshots.add(snapshot); 699 } 700 return snapshots; 701 } 702 setOriginalSharedElementState(ArrayList<View> sharedElements, ArrayList<SharedElementOriginalState> originalState)703 protected static void setOriginalSharedElementState(ArrayList<View> sharedElements, 704 ArrayList<SharedElementOriginalState> originalState) { 705 for (int i = 0; i < originalState.size(); i++) { 706 View view = sharedElements.get(i); 707 SharedElementOriginalState state = originalState.get(i); 708 if (view instanceof ImageView && state.mScaleType != null) { 709 ImageView imageView = (ImageView) view; 710 imageView.setScaleType(state.mScaleType); 711 if (state.mScaleType == ImageView.ScaleType.MATRIX) { 712 imageView.setImageMatrix(state.mMatrix); 713 } 714 } 715 view.setElevation(state.mElevation); 716 view.setTranslationZ(state.mTranslationZ); 717 int widthSpec = View.MeasureSpec.makeMeasureSpec(state.mMeasuredWidth, 718 View.MeasureSpec.EXACTLY); 719 int heightSpec = View.MeasureSpec.makeMeasureSpec(state.mMeasuredHeight, 720 View.MeasureSpec.EXACTLY); 721 view.measure(widthSpec, heightSpec); 722 view.layout(state.mLeft, state.mTop, state.mRight, state.mBottom); 723 } 724 } 725 captureSharedElementState()726 protected Bundle captureSharedElementState() { 727 Bundle bundle = new Bundle(); 728 RectF tempBounds = new RectF(); 729 Matrix tempMatrix = new Matrix(); 730 for (int i = 0; i < mSharedElements.size(); i++) { 731 View sharedElement = mSharedElements.get(i); 732 String name = mSharedElementNames.get(i); 733 captureSharedElementState(sharedElement, name, bundle, tempMatrix, tempBounds); 734 } 735 return bundle; 736 } 737 clearState()738 protected void clearState() { 739 // Clear the state so that we can't hold any references accidentally and leak memory. 740 mWindow = null; 741 mSharedElements.clear(); 742 mTransitioningViews = null; 743 mStrippedTransitioningViews = null; 744 mOriginalAlphas.clear(); 745 mResultReceiver = null; 746 mPendingTransition = null; 747 mListener = null; 748 mSharedElementParentMatrices = null; 749 } 750 getFadeDuration()751 protected long getFadeDuration() { 752 return getWindow().getTransitionBackgroundFadeDuration(); 753 } 754 hideViews(ArrayList<View> views)755 protected void hideViews(ArrayList<View> views) { 756 int count = views.size(); 757 for (int i = 0; i < count; i++) { 758 View view = views.get(i); 759 if (!mOriginalAlphas.containsKey(view)) { 760 mOriginalAlphas.put(view, view.getAlpha()); 761 } 762 view.setAlpha(0f); 763 } 764 } 765 showViews(ArrayList<View> views, boolean setTransitionAlpha)766 protected void showViews(ArrayList<View> views, boolean setTransitionAlpha) { 767 int count = views.size(); 768 for (int i = 0; i < count; i++) { 769 showView(views.get(i), setTransitionAlpha); 770 } 771 } 772 showView(View view, boolean setTransitionAlpha)773 private void showView(View view, boolean setTransitionAlpha) { 774 Float alpha = mOriginalAlphas.remove(view); 775 if (alpha != null) { 776 view.setAlpha(alpha); 777 } 778 if (setTransitionAlpha) { 779 view.setTransitionAlpha(1f); 780 } 781 } 782 783 /** 784 * Captures placement information for Views with a shared element name for 785 * Activity Transitions. 786 * 787 * @param view The View to capture the placement information for. 788 * @param name The shared element name in the target Activity to apply the placement 789 * information for. 790 * @param transitionArgs Bundle to store shared element placement information. 791 * @param tempBounds A temporary Rect for capturing the current location of views. 792 */ captureSharedElementState(View view, String name, Bundle transitionArgs, Matrix tempMatrix, RectF tempBounds)793 protected void captureSharedElementState(View view, String name, Bundle transitionArgs, 794 Matrix tempMatrix, RectF tempBounds) { 795 Bundle sharedElementBundle = new Bundle(); 796 tempMatrix.reset(); 797 view.transformMatrixToGlobal(tempMatrix); 798 tempBounds.set(0, 0, view.getWidth(), view.getHeight()); 799 tempMatrix.mapRect(tempBounds); 800 801 sharedElementBundle.putFloat(KEY_SCREEN_LEFT, tempBounds.left); 802 sharedElementBundle.putFloat(KEY_SCREEN_RIGHT, tempBounds.right); 803 sharedElementBundle.putFloat(KEY_SCREEN_TOP, tempBounds.top); 804 sharedElementBundle.putFloat(KEY_SCREEN_BOTTOM, tempBounds.bottom); 805 sharedElementBundle.putFloat(KEY_TRANSLATION_Z, view.getTranslationZ()); 806 sharedElementBundle.putFloat(KEY_ELEVATION, view.getElevation()); 807 808 Parcelable bitmap = null; 809 if (mListener != null) { 810 bitmap = mListener.onCaptureSharedElementSnapshot(view, tempMatrix, tempBounds); 811 } 812 813 if (bitmap != null) { 814 sharedElementBundle.putParcelable(KEY_SNAPSHOT, bitmap); 815 } 816 817 if (view instanceof ImageView) { 818 ImageView imageView = (ImageView) view; 819 int scaleTypeInt = scaleTypeToInt(imageView.getScaleType()); 820 sharedElementBundle.putInt(KEY_SCALE_TYPE, scaleTypeInt); 821 if (imageView.getScaleType() == ImageView.ScaleType.MATRIX) { 822 float[] matrix = new float[9]; 823 imageView.getImageMatrix().getValues(matrix); 824 sharedElementBundle.putFloatArray(KEY_IMAGE_MATRIX, matrix); 825 } 826 } 827 828 transitionArgs.putBundle(name, sharedElementBundle); 829 } 830 831 startTransition(Runnable runnable)832 protected void startTransition(Runnable runnable) { 833 if (mIsStartingTransition) { 834 mPendingTransition = runnable; 835 } else { 836 mIsStartingTransition = true; 837 runnable.run(); 838 } 839 } 840 transitionStarted()841 protected void transitionStarted() { 842 mIsStartingTransition = false; 843 } 844 845 /** 846 * Cancels any pending transitions and returns true if there is a transition is in 847 * the middle of starting. 848 */ cancelPendingTransitions()849 protected boolean cancelPendingTransitions() { 850 mPendingTransition = null; 851 return mIsStartingTransition; 852 } 853 moveSharedElementsToOverlay()854 protected void moveSharedElementsToOverlay() { 855 if (mWindow == null || !mWindow.getSharedElementsUseOverlay()) { 856 return; 857 } 858 setSharedElementMatrices(); 859 int numSharedElements = mSharedElements.size(); 860 ViewGroup decor = getDecor(); 861 if (decor != null) { 862 boolean moveWithParent = moveSharedElementWithParent(); 863 Matrix tempMatrix = new Matrix(); 864 for (int i = 0; i < numSharedElements; i++) { 865 View view = mSharedElements.get(i); 866 if (view.isAttachedToWindow()) { 867 tempMatrix.reset(); 868 mSharedElementParentMatrices.get(i).invert(tempMatrix); 869 GhostView.addGhost(view, decor, tempMatrix); 870 ViewGroup parent = (ViewGroup) view.getParent(); 871 if (moveWithParent && !isInTransitionGroup(parent, decor)) { 872 GhostViewListeners listener = new GhostViewListeners(view, parent, decor); 873 parent.getViewTreeObserver().addOnPreDrawListener(listener); 874 parent.addOnAttachStateChangeListener(listener); 875 mGhostViewListeners.add(listener); 876 } 877 } 878 } 879 } 880 } 881 moveSharedElementWithParent()882 protected boolean moveSharedElementWithParent() { 883 return true; 884 } 885 isInTransitionGroup(ViewParent viewParent, ViewGroup decor)886 public static boolean isInTransitionGroup(ViewParent viewParent, ViewGroup decor) { 887 if (viewParent == decor || !(viewParent instanceof ViewGroup)) { 888 return false; 889 } 890 ViewGroup parent = (ViewGroup) viewParent; 891 if (parent.isTransitionGroup()) { 892 return true; 893 } else { 894 return isInTransitionGroup(parent.getParent(), decor); 895 } 896 } 897 moveSharedElementsFromOverlay()898 protected void moveSharedElementsFromOverlay() { 899 int numListeners = mGhostViewListeners.size(); 900 for (int i = 0; i < numListeners; i++) { 901 GhostViewListeners listener = mGhostViewListeners.get(i); 902 listener.removeListener(); 903 } 904 mGhostViewListeners.clear(); 905 906 if (mWindow == null || !mWindow.getSharedElementsUseOverlay()) { 907 return; 908 } 909 ViewGroup decor = getDecor(); 910 if (decor != null) { 911 ViewGroupOverlay overlay = decor.getOverlay(); 912 int count = mSharedElements.size(); 913 for (int i = 0; i < count; i++) { 914 View sharedElement = mSharedElements.get(i); 915 GhostView.removeGhost(sharedElement); 916 } 917 } 918 } 919 setGhostVisibility(int visibility)920 protected void setGhostVisibility(int visibility) { 921 int numSharedElements = mSharedElements.size(); 922 for (int i = 0; i < numSharedElements; i++) { 923 GhostView ghostView = GhostView.getGhost(mSharedElements.get(i)); 924 if (ghostView != null) { 925 ghostView.setVisibility(visibility); 926 } 927 } 928 } 929 scheduleGhostVisibilityChange(final int visibility)930 protected void scheduleGhostVisibilityChange(final int visibility) { 931 final View decorView = getDecor(); 932 if (decorView != null) { 933 OneShotPreDrawListener.add(decorView, () -> { 934 setGhostVisibility(visibility); 935 }); 936 } 937 } 938 isViewsTransitionComplete()939 protected boolean isViewsTransitionComplete() { 940 return mViewsTransitionComplete; 941 } 942 viewsTransitionComplete()943 protected void viewsTransitionComplete() { 944 mViewsTransitionComplete = true; 945 startInputWhenTransitionsComplete(); 946 } 947 backgroundAnimatorComplete()948 protected void backgroundAnimatorComplete() { 949 mBackgroundAnimatorComplete = true; 950 } 951 sharedElementTransitionComplete()952 protected void sharedElementTransitionComplete() { 953 mSharedElementTransitionComplete = true; 954 startInputWhenTransitionsComplete(); 955 } startInputWhenTransitionsComplete()956 private void startInputWhenTransitionsComplete() { 957 if (mViewsTransitionComplete && mSharedElementTransitionComplete) { 958 final View decor = getDecor(); 959 if (decor != null) { 960 final ViewRootImpl viewRoot = decor.getViewRootImpl(); 961 if (viewRoot != null) { 962 viewRoot.setPausedForTransition(false); 963 } 964 } 965 onTransitionsComplete(); 966 } 967 } 968 pauseInput()969 protected void pauseInput() { 970 final View decor = getDecor(); 971 final ViewRootImpl viewRoot = decor == null ? null : decor.getViewRootImpl(); 972 if (viewRoot != null) { 973 viewRoot.setPausedForTransition(true); 974 } 975 } 976 onTransitionsComplete()977 protected void onTransitionsComplete() {} 978 979 protected class ContinueTransitionListener extends TransitionListenerAdapter { 980 @Override onTransitionStart(Transition transition)981 public void onTransitionStart(Transition transition) { 982 mIsStartingTransition = false; 983 Runnable pending = mPendingTransition; 984 mPendingTransition = null; 985 if (pending != null) { 986 startTransition(pending); 987 } 988 } 989 990 @Override onTransitionEnd(Transition transition)991 public void onTransitionEnd(Transition transition) { 992 transition.removeListener(this); 993 } 994 } 995 scaleTypeToInt(ImageView.ScaleType scaleType)996 private static int scaleTypeToInt(ImageView.ScaleType scaleType) { 997 for (int i = 0; i < SCALE_TYPE_VALUES.length; i++) { 998 if (scaleType == SCALE_TYPE_VALUES[i]) { 999 return i; 1000 } 1001 } 1002 return -1; 1003 } 1004 setTransitioningViewsVisiblity(int visiblity, boolean invalidate)1005 protected void setTransitioningViewsVisiblity(int visiblity, boolean invalidate) { 1006 final int numElements = mTransitioningViews == null ? 0 : mTransitioningViews.size(); 1007 for (int i = 0; i < numElements; i++) { 1008 final View view = mTransitioningViews.get(i); 1009 if (invalidate) { 1010 // Allow the view to be invalidated by the visibility change 1011 view.setVisibility(visiblity); 1012 } else { 1013 // Don't invalidate the view with the visibility change 1014 view.setTransitionVisibility(visiblity); 1015 } 1016 } 1017 } 1018 1019 /** 1020 * Blocks suppressLayout from Visibility transitions. It is ok to suppress the layout, 1021 * but we don't want to force the layout when suppressLayout becomes false. This leads 1022 * to visual glitches. 1023 */ noLayoutSuppressionForVisibilityTransitions(Transition transition)1024 private static void noLayoutSuppressionForVisibilityTransitions(Transition transition) { 1025 if (transition instanceof Visibility) { 1026 final Visibility visibility = (Visibility) transition; 1027 visibility.setSuppressLayout(false); 1028 } else if (transition instanceof TransitionSet) { 1029 final TransitionSet set = (TransitionSet) transition; 1030 final int count = set.getTransitionCount(); 1031 for (int i = 0; i < count; i++) { 1032 noLayoutSuppressionForVisibilityTransitions(set.getTransitionAt(i)); 1033 } 1034 } 1035 } 1036 isTransitionRunning()1037 public boolean isTransitionRunning() { 1038 return !(mViewsTransitionComplete && mSharedElementTransitionComplete && 1039 mBackgroundAnimatorComplete); 1040 } 1041 1042 private static class FixedEpicenterCallback extends Transition.EpicenterCallback { 1043 private Rect mEpicenter; 1044 setEpicenter(Rect epicenter)1045 public void setEpicenter(Rect epicenter) { mEpicenter = epicenter; } 1046 1047 @Override onGetEpicenter(Transition transition)1048 public Rect onGetEpicenter(Transition transition) { 1049 return mEpicenter; 1050 } 1051 } 1052 1053 private static class GhostViewListeners implements ViewTreeObserver.OnPreDrawListener, 1054 View.OnAttachStateChangeListener { 1055 private View mView; 1056 private ViewGroup mDecor; 1057 private View mParent; 1058 private Matrix mMatrix = new Matrix(); 1059 private ViewTreeObserver mViewTreeObserver; 1060 GhostViewListeners(View view, View parent, ViewGroup decor)1061 public GhostViewListeners(View view, View parent, ViewGroup decor) { 1062 mView = view; 1063 mParent = parent; 1064 mDecor = decor; 1065 mViewTreeObserver = parent.getViewTreeObserver(); 1066 } 1067 getView()1068 public View getView() { 1069 return mView; 1070 } 1071 1072 @Override onPreDraw()1073 public boolean onPreDraw() { 1074 GhostView ghostView = GhostView.getGhost(mView); 1075 if (ghostView == null || !mView.isAttachedToWindow()) { 1076 removeListener(); 1077 } else { 1078 GhostView.calculateMatrix(mView, mDecor, mMatrix); 1079 ghostView.setMatrix(mMatrix); 1080 } 1081 return true; 1082 } 1083 removeListener()1084 public void removeListener() { 1085 if (mViewTreeObserver.isAlive()) { 1086 mViewTreeObserver.removeOnPreDrawListener(this); 1087 } else { 1088 mParent.getViewTreeObserver().removeOnPreDrawListener(this); 1089 } 1090 mParent.removeOnAttachStateChangeListener(this); 1091 } 1092 1093 @Override onViewAttachedToWindow(View v)1094 public void onViewAttachedToWindow(View v) { 1095 mViewTreeObserver = v.getViewTreeObserver(); 1096 } 1097 1098 @Override onViewDetachedFromWindow(View v)1099 public void onViewDetachedFromWindow(View v) { 1100 removeListener(); 1101 } 1102 } 1103 1104 static class SharedElementOriginalState { 1105 int mLeft; 1106 int mTop; 1107 int mRight; 1108 int mBottom; 1109 int mMeasuredWidth; 1110 int mMeasuredHeight; 1111 ImageView.ScaleType mScaleType; 1112 Matrix mMatrix; 1113 float mTranslationZ; 1114 float mElevation; 1115 } 1116 } 1117