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