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