ActivityTransitionCoordinator.java revision 8c2614ce4328640642d8e8be437859e0508a39b4
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.content.res.Resources; 20import android.graphics.Bitmap; 21import android.graphics.Canvas; 22import android.graphics.Matrix; 23import android.graphics.Rect; 24import android.graphics.drawable.BitmapDrawable; 25import android.os.Bundle; 26import android.os.Handler; 27import android.os.ResultReceiver; 28import android.transition.Transition; 29import android.transition.TransitionSet; 30import android.util.ArrayMap; 31import android.util.Pair; 32import android.view.View; 33import android.view.ViewGroup; 34import android.view.ViewTreeObserver; 35import android.view.Window; 36import android.widget.ImageView; 37 38import java.util.ArrayList; 39import java.util.Collection; 40 41/** 42 * Base class for ExitTransitionCoordinator and EnterTransitionCoordinator, classes 43 * that manage activity transitions and the communications coordinating them between 44 * Activities. The ExitTransitionCoordinator is created in the 45 * ActivityOptions#makeSceneTransitionAnimation. The EnterTransitionCoordinator 46 * is created by ActivityOptions#createEnterActivityTransition by Activity when the window is 47 * attached. 48 * 49 * Typical startActivity goes like this: 50 * 1) ExitTransitionCoordinator created with ActivityOptions#makeSceneTransitionAnimation 51 * 2) Activity#startActivity called and that calls startExit() through 52 * ActivityOptions#dispatchStartExit 53 * - Exit transition starts by setting transitioning Views to INVISIBLE 54 * 3) Launched Activity starts, creating an EnterTransitionCoordinator. 55 * - The Window is made translucent 56 * - The Window background alpha is set to 0 57 * - The transitioning views are made INVISIBLE 58 * - MSG_SET_LISTENER is sent back to the ExitTransitionCoordinator. 59 * 4) The shared element transition completes. 60 * - MSG_TAKE_SHARED_ELEMENTS is sent to the EnterTransitionCoordinator 61 * 5) The MSG_TAKE_SHARED_ELEMENTS is received by the EnterTransitionCoordinator. 62 * - Shared elements are made VISIBLE 63 * - Shared elements positions and size are set to match the end state of the calling 64 * Activity. 65 * - The shared element transition is started 66 * - If the window allows overlapping transitions, the views transition is started by setting 67 * the entering Views to VISIBLE and the background alpha is animated to opaque. 68 * - MSG_HIDE_SHARED_ELEMENTS is sent to the ExitTransitionCoordinator 69 * 6) MSG_HIDE_SHARED_ELEMENTS is received by the ExitTransitionCoordinator 70 * - The shared elements are made INVISIBLE 71 * 7) The exit transition completes in the calling Activity. 72 * - MSG_EXIT_TRANSITION_COMPLETE is sent to the EnterTransitionCoordinator. 73 * 8) The MSG_EXIT_TRANSITION_COMPLETE is received by the EnterTransitionCoordinator. 74 * - If the window doesn't allow overlapping enter transitions, the enter transition is started 75 * by setting entering views to VISIBLE and the background is animated to opaque. 76 * 9) The background opacity animation completes. 77 * - The window is made opaque 78 * 10) The calling Activity gets an onStop() call 79 * - onActivityStopped() is called and all exited Views are made VISIBLE. 80 * 81 * Typical finishAfterTransition goes like this: 82 * 1) finishAfterTransition() creates an ExitTransitionCoordinator and calls startExit() 83 * - The Window start transitioning to Translucent with a new ActivityOptions. 84 * - If no background exists, a black background is substituted 85 * - The shared elements in the scene are matched against those shared elements 86 * that were sent by comparing the names. 87 * - The exit transition is started by setting Views to INVISIBLE. 88 * 2) The ActivityOptions is received by the Activity and an EnterTransitionCoordinator is created. 89 * - All transitioning views are made VISIBLE to reverse what was done when onActivityStopped() 90 * was called 91 * 3) The Window is made translucent and a callback is received 92 * - The background alpha is animated to 0 93 * 4) The background alpha animation completes 94 * 5) The shared element transition completes 95 * - After both 4 & 5 complete, MSG_TAKE_SHARED_ELEMENTS is sent to the 96 * EnterTransitionCoordinator 97 * 6) MSG_TAKE_SHARED_ELEMENTS is received by EnterTransitionCoordinator 98 * - Shared elements are made VISIBLE 99 * - Shared elements positions and size are set to match the end state of the calling 100 * Activity. 101 * - The shared element transition is started 102 * - If the window allows overlapping transitions, the views transition is started by setting 103 * the entering Views to VISIBLE. 104 * - MSG_HIDE_SHARED_ELEMENTS is sent to the ExitTransitionCoordinator 105 * 7) MSG_HIDE_SHARED_ELEMENTS is received by the ExitTransitionCoordinator 106 * - The shared elements are made INVISIBLE 107 * 8) The exit transition completes in the finishing Activity. 108 * - MSG_EXIT_TRANSITION_COMPLETE is sent to the EnterTransitionCoordinator. 109 * - finish() is called on the exiting Activity 110 * 9) The MSG_EXIT_TRANSITION_COMPLETE is received by the EnterTransitionCoordinator. 111 * - If the window doesn't allow overlapping enter transitions, the enter transition is started 112 * by setting entering views to VISIBLE. 113 */ 114abstract class ActivityTransitionCoordinator extends ResultReceiver { 115 private static final String TAG = "ActivityTransitionCoordinator"; 116 117 /** 118 * For Activity transitions, the called Activity's listener to receive calls 119 * when transitions complete. 120 */ 121 static final String KEY_REMOTE_RECEIVER = "android:remoteReceiver"; 122 123 protected static final String KEY_SCREEN_X = "shared_element:screenX"; 124 protected static final String KEY_SCREEN_Y = "shared_element:screenY"; 125 protected static final String KEY_TRANSLATION_Z = "shared_element:translationZ"; 126 protected static final String KEY_WIDTH = "shared_element:width"; 127 protected static final String KEY_HEIGHT = "shared_element:height"; 128 protected static final String KEY_BITMAP = "shared_element:bitmap"; 129 protected static final String KEY_SCALE_TYPE = "shared_element:scaleType"; 130 protected static final String KEY_IMAGE_MATRIX = "shared_element:imageMatrix"; 131 132 // The background fade in/out duration. TODO: Enable tuning this. 133 public static final int FADE_BACKGROUND_DURATION_MS = 300; 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 Activity in ActivityOptions#dispatchActivityStopped 158 * to leave the Activity in a good state after it has been hidden. 159 */ 160 public static final int MSG_ACTIVITY_STOPPED = 102; 161 162 /** 163 * Sent by the exiting coordinator (either EnterTransitionCoordinator 164 * or ExitTransitionCoordinator) after the shared elements have 165 * become stationary (shared element transition completes). This tells 166 * the remote coordinator to take control of the shared elements and 167 * that animations may begin. The remote Activity won't start entering 168 * until this message is received, but may wait for 169 * MSG_EXIT_TRANSITION_COMPLETE if allowOverlappingTransitions() is true. 170 */ 171 public static final int MSG_TAKE_SHARED_ELEMENTS = 103; 172 173 /** 174 * Sent by the exiting coordinator (either 175 * EnterTransitionCoordinator or ExitTransitionCoordinator) after 176 * the exiting Views have finished leaving the scene. This will 177 * be ignored if allowOverlappingTransitions() is true on the 178 * remote coordinator. If it is false, it will trigger the enter 179 * transition to start. 180 */ 181 public static final int MSG_EXIT_TRANSITION_COMPLETE = 104; 182 183 /** 184 * Sent by Activity#startActivity to begin the exit transition. 185 */ 186 public static final int MSG_START_EXIT_TRANSITION = 105; 187 188 /** 189 * It took too long for a message from the entering Activity, so we canceled the transition. 190 */ 191 public static final int MSG_CANCEL = 106; 192 193 /** 194 * When returning, this is the destination location for the shared element. 195 */ 196 public static final int MSG_SHARED_ELEMENT_DESTINATION = 107; 197 198 /** 199 * Send the shared element positions. 200 */ 201 public static final int MSG_SEND_SHARED_ELEMENT_DESTINATION = 108; 202 203 final 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 final protected ArrayList<View> mTransitioningViews = new ArrayList<View>(); 208 final protected SharedElementListener mListener; 209 protected ResultReceiver mResultReceiver; 210 final private FixedEpicenterCallback mEpicenterCallback = new FixedEpicenterCallback(); 211 final protected boolean mIsReturning; 212 213 public ActivityTransitionCoordinator(Window window, 214 ArrayList<String> allSharedElementNames, 215 ArrayList<String> accepted, ArrayList<String> localNames, 216 SharedElementListener listener, boolean isReturning) { 217 this(window, allSharedElementNames, listener, isReturning); 218 viewsReady(accepted, localNames); 219 } 220 221 public ActivityTransitionCoordinator(Window window, 222 ArrayList<String> allSharedElementNames, 223 SharedElementListener listener, boolean isReturning) { 224 super(new Handler()); 225 mWindow = window; 226 mListener = listener; 227 mAllSharedElementNames = allSharedElementNames; 228 mIsReturning = isReturning; 229 } 230 231 protected void viewsReady(ArrayList<String> accepted, ArrayList<String> localNames) { 232 setSharedElements(accepted, localNames); 233 if (getViewsTransition() != null) { 234 getDecor().captureTransitioningViews(mTransitioningViews); 235 mTransitioningViews.removeAll(mSharedElements); 236 } 237 setEpicenter(); 238 } 239 240 protected Window getWindow() { 241 return mWindow; 242 } 243 244 protected ViewGroup getDecor() { 245 return (mWindow == null) ? null : (ViewGroup) mWindow.getDecorView(); 246 } 247 248 /** 249 * Sets the transition epicenter to the position of the first shared element. 250 */ 251 protected void setEpicenter() { 252 View epicenter = null; 253 if (!mAllSharedElementNames.isEmpty() && !mSharedElementNames.isEmpty() && 254 mAllSharedElementNames.get(0).equals(mSharedElementNames.get(0))) { 255 epicenter = mSharedElements.get(0); 256 } 257 setEpicenter(epicenter); 258 } 259 260 private void setEpicenter(View view) { 261 if (view == null) { 262 mEpicenterCallback.setEpicenter(null); 263 } else { 264 Rect epicenter = new Rect(); 265 view.getBoundsOnScreen(epicenter); 266 mEpicenterCallback.setEpicenter(epicenter); 267 } 268 } 269 270 public ArrayList<String> getAcceptedNames() { 271 return mSharedElementNames; 272 } 273 274 public ArrayList<String> getMappedNames() { 275 ArrayList<String> names = new ArrayList<String>(mSharedElements.size()); 276 for (int i = 0; i < mSharedElements.size(); i++) { 277 names.add(mSharedElements.get(i).getViewName()); 278 } 279 return names; 280 } 281 282 public ArrayList<String> getAllSharedElementNames() { return mAllSharedElementNames; } 283 284 public static void setViewVisibility(Collection<View> views, int visibility) { 285 if (views != null) { 286 for (View view : views) { 287 view.setVisibility(visibility); 288 } 289 } 290 } 291 292 protected static Transition addTargets(Transition transition, Collection<View> views) { 293 if (transition == null || views == null || views.isEmpty()) { 294 return null; 295 } 296 TransitionSet set = new TransitionSet(); 297 set.addTransition(transition); 298 if (views != null) { 299 for (View view: views) { 300 set.addTarget(view); 301 } 302 } 303 return set; 304 } 305 306 protected Transition configureTransition(Transition transition) { 307 if (transition != null) { 308 transition = transition.clone(); 309 transition.setEpicenterCallback(mEpicenterCallback); 310 } 311 return transition; 312 } 313 314 protected static Transition mergeTransitions(Transition transition1, Transition transition2) { 315 if (transition1 == null) { 316 return transition2; 317 } else if (transition2 == null) { 318 return transition1; 319 } else { 320 TransitionSet transitionSet = new TransitionSet(); 321 transitionSet.addTransition(transition1); 322 transitionSet.addTransition(transition2); 323 return transitionSet; 324 } 325 } 326 327 private void setSharedElements(ArrayList<String> accepted, ArrayList<String> localNames) { 328 if (!mAllSharedElementNames.isEmpty()) { 329 ArrayMap<String, View> sharedElements = new ArrayMap<String, View>(); 330 getDecor().findNamedViews(sharedElements); 331 if (accepted != null) { 332 for (int i = 0; i < localNames.size(); i++) { 333 String localName = localNames.get(i); 334 String acceptedName = accepted.get(i); 335 if (!localName.equals(acceptedName)) { 336 View view = sharedElements.remove(localName); 337 if (view != null) { 338 sharedElements.put(acceptedName, view); 339 } 340 } 341 } 342 } 343 sharedElements.retainAll(mAllSharedElementNames); 344 mListener.remapSharedElements(mAllSharedElementNames, sharedElements); 345 sharedElements.retainAll(mAllSharedElementNames); 346 for (int i = 0; i < mAllSharedElementNames.size(); i++) { 347 String name = mAllSharedElementNames.get(i); 348 View sharedElement = sharedElements.get(name); 349 if (sharedElement != null) { 350 mSharedElementNames.add(name); 351 mSharedElements.add(sharedElement); 352 } 353 } 354 } 355 } 356 357 protected void setResultReceiver(ResultReceiver resultReceiver) { 358 mResultReceiver = resultReceiver; 359 } 360 361 protected abstract Transition getViewsTransition(); 362 363 private static void setSharedElementState(View view, String name, Bundle transitionArgs, 364 int[] parentLoc) { 365 Bundle sharedElementBundle = transitionArgs.getBundle(name); 366 if (sharedElementBundle == null) { 367 return; 368 } 369 370 if (view instanceof ImageView) { 371 int scaleTypeInt = sharedElementBundle.getInt(KEY_SCALE_TYPE, -1); 372 if (scaleTypeInt >= 0) { 373 ImageView imageView = (ImageView) view; 374 ImageView.ScaleType scaleType = SCALE_TYPE_VALUES[scaleTypeInt]; 375 imageView.setScaleType(scaleType); 376 if (scaleType == ImageView.ScaleType.MATRIX) { 377 float[] matrixValues = sharedElementBundle.getFloatArray(KEY_IMAGE_MATRIX); 378 Matrix matrix = new Matrix(); 379 matrix.setValues(matrixValues); 380 imageView.setImageMatrix(matrix); 381 } 382 } 383 } 384 385 float z = sharedElementBundle.getFloat(KEY_TRANSLATION_Z); 386 view.setTranslationZ(z); 387 388 int x = sharedElementBundle.getInt(KEY_SCREEN_X); 389 int y = sharedElementBundle.getInt(KEY_SCREEN_Y); 390 int width = sharedElementBundle.getInt(KEY_WIDTH); 391 int height = sharedElementBundle.getInt(KEY_HEIGHT); 392 393 int widthSpec = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY); 394 int heightSpec = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY); 395 view.measure(widthSpec, heightSpec); 396 397 int left = x - parentLoc[0]; 398 int top = y - parentLoc[1]; 399 int right = left + width; 400 int bottom = top + height; 401 view.layout(left, top, right, bottom); 402 } 403 404 protected ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>> setSharedElementState( 405 Bundle sharedElementState, final ArrayList<View> snapshots) { 406 ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>> originalImageState = 407 new ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>>(); 408 if (sharedElementState != null) { 409 int[] tempLoc = new int[2]; 410 for (int i = 0; i < mSharedElementNames.size(); i++) { 411 View sharedElement = mSharedElements.get(i); 412 String name = mSharedElementNames.get(i); 413 Pair<ImageView.ScaleType, Matrix> originalState = getOldImageState(sharedElement, 414 name, sharedElementState); 415 if (originalState != null) { 416 originalImageState.put((ImageView) sharedElement, originalState); 417 } 418 View parent = (View) sharedElement.getParent(); 419 parent.getLocationOnScreen(tempLoc); 420 setSharedElementState(sharedElement, name, sharedElementState, tempLoc); 421 } 422 } 423 mListener.setSharedElementStart(mSharedElementNames, mSharedElements, snapshots); 424 425 getDecor().getViewTreeObserver().addOnPreDrawListener( 426 new ViewTreeObserver.OnPreDrawListener() { 427 @Override 428 public boolean onPreDraw() { 429 getDecor().getViewTreeObserver().removeOnPreDrawListener(this); 430 mListener.setSharedElementEnd(mSharedElementNames, mSharedElements, 431 snapshots); 432 return true; 433 } 434 } 435 ); 436 return originalImageState; 437 } 438 439 private static Pair<ImageView.ScaleType, Matrix> getOldImageState(View view, String name, 440 Bundle transitionArgs) { 441 if (!(view instanceof ImageView)) { 442 return null; 443 } 444 Bundle bundle = transitionArgs.getBundle(name); 445 if (bundle == null) { 446 return null; 447 } 448 int scaleTypeInt = bundle.getInt(KEY_SCALE_TYPE, -1); 449 if (scaleTypeInt < 0) { 450 return null; 451 } 452 453 ImageView imageView = (ImageView) view; 454 ImageView.ScaleType originalScaleType = imageView.getScaleType(); 455 456 Matrix originalMatrix = null; 457 if (originalScaleType == ImageView.ScaleType.MATRIX) { 458 originalMatrix = new Matrix(imageView.getImageMatrix()); 459 } 460 461 return Pair.create(originalScaleType, originalMatrix); 462 } 463 464 protected ArrayList<View> createSnapshots(Bundle state, Collection<String> names) { 465 int numSharedElements = names.size(); 466 if (numSharedElements == 0) { 467 return null; 468 } 469 ArrayList<View> snapshots = new ArrayList<View>(numSharedElements); 470 Context context = getWindow().getContext(); 471 int[] parentLoc = new int[2]; 472 getDecor().getLocationOnScreen(parentLoc); 473 for (String name: names) { 474 Bundle sharedElementBundle = state.getBundle(name); 475 if (sharedElementBundle != null) { 476 Bitmap bitmap = sharedElementBundle.getParcelable(KEY_BITMAP); 477 View snapshot = new View(context); 478 Resources resources = getWindow().getContext().getResources(); 479 if (bitmap != null) { 480 snapshot.setBackground(new BitmapDrawable(resources, bitmap)); 481 } 482 snapshot.setViewName(name); 483 setSharedElementState(snapshot, name, state, parentLoc); 484 snapshots.add(snapshot); 485 } 486 } 487 return snapshots; 488 } 489 490 protected static void setOriginalImageViewState( 491 ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>> originalState) { 492 for (int i = 0; i < originalState.size(); i++) { 493 ImageView imageView = originalState.keyAt(i); 494 Pair<ImageView.ScaleType, Matrix> state = originalState.valueAt(i); 495 imageView.setScaleType(state.first); 496 imageView.setImageMatrix(state.second); 497 } 498 } 499 500 protected Bundle captureSharedElementState() { 501 Bundle bundle = new Bundle(); 502 Rect tempBounds = new Rect(); 503 for (int i = 0; i < mSharedElementNames.size(); i++) { 504 View sharedElement = mSharedElements.get(i); 505 String name = mSharedElementNames.get(i); 506 captureSharedElementState(sharedElement, name, bundle, tempBounds); 507 } 508 return bundle; 509 } 510 511 /** 512 * Captures placement information for Views with a shared element name for 513 * Activity Transitions. 514 * 515 * @param view The View to capture the placement information for. 516 * @param name The shared element name in the target Activity to apply the placement 517 * information for. 518 * @param transitionArgs Bundle to store shared element placement information. 519 * @param tempBounds A temporary Rect for capturing the current location of views. 520 */ 521 private static void captureSharedElementState(View view, String name, Bundle transitionArgs, 522 Rect tempBounds) { 523 Bundle sharedElementBundle = new Bundle(); 524 tempBounds.set(0, 0, view.getWidth(), view.getHeight()); 525 view.getBoundsOnScreen(tempBounds); 526 sharedElementBundle.putInt(KEY_SCREEN_X, tempBounds.left); 527 int width = tempBounds.width(); 528 sharedElementBundle.putInt(KEY_WIDTH, width); 529 530 sharedElementBundle.putInt(KEY_SCREEN_Y, tempBounds.top); 531 int height = tempBounds.height(); 532 sharedElementBundle.putInt(KEY_HEIGHT, height); 533 534 sharedElementBundle.putFloat(KEY_TRANSLATION_Z, view.getTranslationZ()); 535 536 if (width > 0 && height > 0) { 537 Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 538 Canvas canvas = new Canvas(bitmap); 539 view.draw(canvas); 540 sharedElementBundle.putParcelable(KEY_BITMAP, bitmap); 541 } 542 543 if (view instanceof ImageView) { 544 ImageView imageView = (ImageView) view; 545 int scaleTypeInt = scaleTypeToInt(imageView.getScaleType()); 546 sharedElementBundle.putInt(KEY_SCALE_TYPE, scaleTypeInt); 547 if (imageView.getScaleType() == ImageView.ScaleType.MATRIX) { 548 float[] matrix = new float[9]; 549 imageView.getImageMatrix().getValues(matrix); 550 sharedElementBundle.putFloatArray(KEY_IMAGE_MATRIX, matrix); 551 } 552 } 553 554 transitionArgs.putBundle(name, sharedElementBundle); 555 } 556 557 private static int scaleTypeToInt(ImageView.ScaleType scaleType) { 558 for (int i = 0; i < SCALE_TYPE_VALUES.length; i++) { 559 if (scaleType == SCALE_TYPE_VALUES[i]) { 560 return i; 561 } 562 } 563 return -1; 564 } 565 566 private static class FixedEpicenterCallback extends Transition.EpicenterCallback { 567 private Rect mEpicenter; 568 569 public void setEpicenter(Rect epicenter) { mEpicenter = epicenter; } 570 571 @Override 572 public Rect onGetEpicenter(Transition transition) { 573 return mEpicenter; 574 } 575 } 576 577} 578