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