ExitTransitionCoordinator.java revision 995f4b5721e092478881b8929bdd235ec5855646
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.animation.Animator; 19import android.animation.AnimatorListenerAdapter; 20import android.animation.ObjectAnimator; 21import android.app.SharedElementCallback.OnSharedElementsReadyListener; 22import android.content.Intent; 23import android.graphics.Color; 24import android.graphics.Matrix; 25import android.graphics.RectF; 26import android.graphics.drawable.ColorDrawable; 27import android.graphics.drawable.Drawable; 28import android.os.Build.VERSION_CODES; 29import android.os.Bundle; 30import android.os.Handler; 31import android.os.Message; 32import android.os.ResultReceiver; 33import android.transition.Transition; 34import android.transition.TransitionListenerAdapter; 35import android.transition.TransitionManager; 36import android.view.View; 37import android.view.ViewGroup; 38import android.view.Window; 39 40import com.android.internal.view.OneShotPreDrawListener; 41 42import java.util.ArrayList; 43 44/** 45 * This ActivityTransitionCoordinator is created in ActivityOptions#makeSceneTransitionAnimation 46 * to govern the exit of the Scene and the shared elements when calling an Activity as well as 47 * the reentry of the Scene when coming back from the called Activity. 48 */ 49class ExitTransitionCoordinator extends ActivityTransitionCoordinator { 50 private static final String TAG = "ExitTransitionCoordinator"; 51 private static final long MAX_WAIT_MS = 1000; 52 53 private Bundle mSharedElementBundle; 54 private boolean mExitNotified; 55 private boolean mSharedElementNotified; 56 private Activity mActivity; 57 private boolean mIsBackgroundReady; 58 private boolean mIsCanceled; 59 private Handler mHandler; 60 private ObjectAnimator mBackgroundAnimator; 61 private boolean mIsHidden; 62 private Bundle mExitSharedElementBundle; 63 private boolean mIsExitStarted; 64 private boolean mSharedElementsHidden; 65 private HideSharedElementsCallback mHideSharedElementsCallback; 66 67 public ExitTransitionCoordinator(Activity activity, Window window, 68 SharedElementCallback listener, ArrayList<String> names, 69 ArrayList<String> accepted, ArrayList<View> mapped, boolean isReturning) { 70 super(window, names, listener, isReturning); 71 viewsReady(mapSharedElements(accepted, mapped)); 72 stripOffscreenViews(); 73 mIsBackgroundReady = !isReturning; 74 mActivity = activity; 75 } 76 77 void setHideSharedElementsCallback(HideSharedElementsCallback callback) { 78 mHideSharedElementsCallback = callback; 79 } 80 81 @Override 82 protected void onReceiveResult(int resultCode, Bundle resultData) { 83 switch (resultCode) { 84 case MSG_SET_REMOTE_RECEIVER: 85 stopCancel(); 86 mResultReceiver = resultData.getParcelable(KEY_REMOTE_RECEIVER); 87 if (mIsCanceled) { 88 mResultReceiver.send(MSG_CANCEL, null); 89 mResultReceiver = null; 90 } else { 91 notifyComplete(); 92 } 93 break; 94 case MSG_HIDE_SHARED_ELEMENTS: 95 stopCancel(); 96 if (!mIsCanceled) { 97 hideSharedElements(); 98 } 99 break; 100 case MSG_START_EXIT_TRANSITION: 101 mHandler.removeMessages(MSG_CANCEL); 102 startExit(); 103 break; 104 case MSG_SHARED_ELEMENT_DESTINATION: 105 mExitSharedElementBundle = resultData; 106 sharedElementExitBack(); 107 break; 108 case MSG_CANCEL: 109 mIsCanceled = true; 110 finish(); 111 break; 112 } 113 } 114 115 private void stopCancel() { 116 if (mHandler != null) { 117 mHandler.removeMessages(MSG_CANCEL); 118 } 119 } 120 121 private void delayCancel() { 122 if (mHandler != null) { 123 mHandler.sendEmptyMessageDelayed(MSG_CANCEL, MAX_WAIT_MS); 124 } 125 } 126 127 public void resetViews() { 128 ViewGroup decorView = getDecor(); 129 if (decorView != null) { 130 TransitionManager.endTransitions(decorView); 131 } 132 if (mTransitioningViews != null) { 133 showViews(mTransitioningViews, true); 134 setTransitioningViewsVisiblity(View.VISIBLE, true); 135 } 136 showViews(mSharedElements, true); 137 mIsHidden = true; 138 if (!mIsReturning && decorView != null) { 139 decorView.suppressLayout(false); 140 } 141 moveSharedElementsFromOverlay(); 142 clearState(); 143 } 144 145 private void sharedElementExitBack() { 146 final ViewGroup decorView = getDecor(); 147 if (decorView != null) { 148 decorView.suppressLayout(true); 149 } 150 if (decorView != null && mExitSharedElementBundle != null && 151 !mExitSharedElementBundle.isEmpty() && 152 !mSharedElements.isEmpty() && getSharedElementTransition() != null) { 153 startTransition(new Runnable() { 154 public void run() { 155 startSharedElementExit(decorView); 156 } 157 }); 158 } else { 159 sharedElementTransitionComplete(); 160 } 161 } 162 163 private void startSharedElementExit(final ViewGroup decorView) { 164 Transition transition = getSharedElementExitTransition(); 165 transition.addListener(new TransitionListenerAdapter() { 166 @Override 167 public void onTransitionEnd(Transition transition) { 168 transition.removeListener(this); 169 if (isViewsTransitionComplete()) { 170 delayCancel(); 171 } 172 } 173 }); 174 final ArrayList<View> sharedElementSnapshots = createSnapshots(mExitSharedElementBundle, 175 mSharedElementNames); 176 OneShotPreDrawListener.add(decorView, () -> { 177 setSharedElementState(mExitSharedElementBundle, sharedElementSnapshots); 178 }); 179 setGhostVisibility(View.INVISIBLE); 180 scheduleGhostVisibilityChange(View.INVISIBLE); 181 if (mListener != null) { 182 mListener.onSharedElementEnd(mSharedElementNames, mSharedElements, 183 sharedElementSnapshots); 184 } 185 TransitionManager.beginDelayedTransition(decorView, transition); 186 scheduleGhostVisibilityChange(View.VISIBLE); 187 setGhostVisibility(View.VISIBLE); 188 decorView.invalidate(); 189 } 190 191 private void hideSharedElements() { 192 moveSharedElementsFromOverlay(); 193 if (mHideSharedElementsCallback != null) { 194 mHideSharedElementsCallback.hideSharedElements(); 195 } 196 if (!mIsHidden) { 197 hideViews(mSharedElements); 198 } 199 mSharedElementsHidden = true; 200 finishIfNecessary(); 201 } 202 203 public void startExit() { 204 if (!mIsExitStarted) { 205 backgroundAnimatorComplete(); 206 mIsExitStarted = true; 207 pauseInput(); 208 ViewGroup decorView = getDecor(); 209 if (decorView != null) { 210 decorView.suppressLayout(true); 211 } 212 moveSharedElementsToOverlay(); 213 startTransition(new Runnable() { 214 @Override 215 public void run() { 216 if (mActivity != null) { 217 beginTransitions(); 218 } else { 219 startExitTransition(); 220 } 221 } 222 }); 223 } 224 } 225 226 public void startExit(int resultCode, Intent data) { 227 if (!mIsExitStarted) { 228 mIsExitStarted = true; 229 pauseInput(); 230 ViewGroup decorView = getDecor(); 231 if (decorView != null) { 232 decorView.suppressLayout(true); 233 } 234 mHandler = new Handler() { 235 @Override 236 public void handleMessage(Message msg) { 237 mIsCanceled = true; 238 finish(); 239 } 240 }; 241 delayCancel(); 242 moveSharedElementsToOverlay(); 243 if (decorView != null && decorView.getBackground() == null) { 244 getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); 245 } 246 final boolean targetsM = decorView == null || decorView.getContext() 247 .getApplicationInfo().targetSdkVersion >= VERSION_CODES.M; 248 ArrayList<String> sharedElementNames = targetsM ? mSharedElementNames : 249 mAllSharedElementNames; 250 ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(mActivity, this, 251 sharedElementNames, resultCode, data); 252 mActivity.convertToTranslucent(new Activity.TranslucentConversionListener() { 253 @Override 254 public void onTranslucentConversionComplete(boolean drawComplete) { 255 if (!mIsCanceled) { 256 fadeOutBackground(); 257 } 258 } 259 }, options); 260 startTransition(new Runnable() { 261 @Override 262 public void run() { 263 startExitTransition(); 264 } 265 }); 266 } 267 } 268 269 public void stop() { 270 if (mIsReturning && mActivity != null) { 271 // Override the previous ActivityOptions. We don't want the 272 // activity to have options since we're essentially canceling the 273 // transition and finishing right now. 274 mActivity.convertToTranslucent(null, null); 275 finish(); 276 } 277 } 278 279 private void startExitTransition() { 280 Transition transition = getExitTransition(); 281 ViewGroup decorView = getDecor(); 282 if (transition != null && decorView != null && mTransitioningViews != null) { 283 setTransitioningViewsVisiblity(View.VISIBLE, false); 284 TransitionManager.beginDelayedTransition(decorView, transition); 285 setTransitioningViewsVisiblity(View.INVISIBLE, false); 286 decorView.invalidate(); 287 } else { 288 transitionStarted(); 289 } 290 } 291 292 private void fadeOutBackground() { 293 if (mBackgroundAnimator == null) { 294 ViewGroup decor = getDecor(); 295 Drawable background; 296 if (decor != null && (background = decor.getBackground()) != null) { 297 background = background.mutate(); 298 getWindow().setBackgroundDrawable(background); 299 mBackgroundAnimator = ObjectAnimator.ofInt(background, "alpha", 0); 300 mBackgroundAnimator.addListener(new AnimatorListenerAdapter() { 301 @Override 302 public void onAnimationEnd(Animator animation) { 303 mBackgroundAnimator = null; 304 if (!mIsCanceled) { 305 mIsBackgroundReady = true; 306 notifyComplete(); 307 } 308 backgroundAnimatorComplete(); 309 } 310 }); 311 mBackgroundAnimator.setDuration(getFadeDuration()); 312 mBackgroundAnimator.start(); 313 } else { 314 backgroundAnimatorComplete(); 315 mIsBackgroundReady = true; 316 } 317 } 318 } 319 320 private Transition getExitTransition() { 321 Transition viewsTransition = null; 322 if (mTransitioningViews != null && !mTransitioningViews.isEmpty()) { 323 viewsTransition = configureTransition(getViewsTransition(), true); 324 } 325 if (viewsTransition == null) { 326 viewsTransitionComplete(); 327 } else { 328 final ArrayList<View> transitioningViews = mTransitioningViews; 329 viewsTransition.addListener(new ContinueTransitionListener() { 330 @Override 331 public void onTransitionEnd(Transition transition) { 332 viewsTransitionComplete(); 333 if (mIsHidden && transitioningViews != null) { 334 showViews(transitioningViews, true); 335 setTransitioningViewsVisiblity(View.VISIBLE, true); 336 } 337 if (mSharedElementBundle != null) { 338 delayCancel(); 339 } 340 super.onTransitionEnd(transition); 341 } 342 }); 343 } 344 return viewsTransition; 345 } 346 347 private Transition getSharedElementExitTransition() { 348 Transition sharedElementTransition = null; 349 if (!mSharedElements.isEmpty()) { 350 sharedElementTransition = configureTransition(getSharedElementTransition(), false); 351 } 352 if (sharedElementTransition == null) { 353 sharedElementTransitionComplete(); 354 } else { 355 sharedElementTransition.addListener(new ContinueTransitionListener() { 356 @Override 357 public void onTransitionEnd(Transition transition) { 358 sharedElementTransitionComplete(); 359 if (mIsHidden) { 360 showViews(mSharedElements, true); 361 } 362 super.onTransitionEnd(transition); 363 } 364 }); 365 mSharedElements.get(0).invalidate(); 366 } 367 return sharedElementTransition; 368 } 369 370 private void beginTransitions() { 371 Transition sharedElementTransition = getSharedElementExitTransition(); 372 Transition viewsTransition = getExitTransition(); 373 374 Transition transition = mergeTransitions(sharedElementTransition, viewsTransition); 375 ViewGroup decorView = getDecor(); 376 if (transition != null && decorView != null) { 377 setGhostVisibility(View.INVISIBLE); 378 scheduleGhostVisibilityChange(View.INVISIBLE); 379 if (viewsTransition != null) { 380 setTransitioningViewsVisiblity(View.VISIBLE, false); 381 } 382 TransitionManager.beginDelayedTransition(decorView, transition); 383 scheduleGhostVisibilityChange(View.VISIBLE); 384 setGhostVisibility(View.VISIBLE); 385 if (viewsTransition != null) { 386 setTransitioningViewsVisiblity(View.INVISIBLE, false); 387 } 388 decorView.invalidate(); 389 } else { 390 transitionStarted(); 391 } 392 } 393 394 protected boolean isReadyToNotify() { 395 return mSharedElementBundle != null && mResultReceiver != null && mIsBackgroundReady; 396 } 397 398 @Override 399 protected void sharedElementTransitionComplete() { 400 mSharedElementBundle = mExitSharedElementBundle == null 401 ? captureSharedElementState() : captureExitSharedElementsState(); 402 super.sharedElementTransitionComplete(); 403 } 404 405 private Bundle captureExitSharedElementsState() { 406 Bundle bundle = new Bundle(); 407 RectF bounds = new RectF(); 408 Matrix matrix = new Matrix(); 409 for (int i = 0; i < mSharedElements.size(); i++) { 410 String name = mSharedElementNames.get(i); 411 Bundle sharedElementState = mExitSharedElementBundle.getBundle(name); 412 if (sharedElementState != null) { 413 bundle.putBundle(name, sharedElementState); 414 } else { 415 View view = mSharedElements.get(i); 416 captureSharedElementState(view, name, bundle, matrix, bounds); 417 } 418 } 419 return bundle; 420 } 421 422 @Override 423 protected void onTransitionsComplete() { 424 notifyComplete(); 425 } 426 427 protected void notifyComplete() { 428 if (isReadyToNotify()) { 429 if (!mSharedElementNotified) { 430 mSharedElementNotified = true; 431 delayCancel(); 432 if (mListener == null) { 433 mResultReceiver.send(MSG_TAKE_SHARED_ELEMENTS, mSharedElementBundle); 434 notifyExitComplete(); 435 } else { 436 final ResultReceiver resultReceiver = mResultReceiver; 437 final Bundle sharedElementBundle = mSharedElementBundle; 438 mListener.onSharedElementsArrived(mSharedElementNames, mSharedElements, 439 new OnSharedElementsReadyListener() { 440 @Override 441 public void onSharedElementsReady() { 442 resultReceiver.send(MSG_TAKE_SHARED_ELEMENTS, 443 sharedElementBundle); 444 notifyExitComplete(); 445 } 446 }); 447 } 448 } else { 449 notifyExitComplete(); 450 } 451 } 452 } 453 454 private void notifyExitComplete() { 455 if (!mExitNotified && isViewsTransitionComplete()) { 456 mExitNotified = true; 457 mResultReceiver.send(MSG_EXIT_TRANSITION_COMPLETE, null); 458 mResultReceiver = null; // done talking 459 ViewGroup decorView = getDecor(); 460 if (!mIsReturning && decorView != null) { 461 decorView.suppressLayout(false); 462 } 463 finishIfNecessary(); 464 } 465 } 466 467 private void finishIfNecessary() { 468 if (mIsReturning && mExitNotified && mActivity != null && (mSharedElements.isEmpty() || 469 mSharedElementsHidden)) { 470 finish(); 471 } 472 if (!mIsReturning && mExitNotified) { 473 mActivity = null; // don't need it anymore 474 } 475 } 476 477 private void finish() { 478 stopCancel(); 479 if (mActivity != null) { 480 mActivity.mActivityTransitionState.clear(); 481 mActivity.finish(); 482 mActivity.overridePendingTransition(0, 0); 483 mActivity = null; 484 } 485 // Clear the state so that we can't hold any references accidentally and leak memory. 486 clearState(); 487 } 488 489 @Override 490 protected void clearState() { 491 mHandler = null; 492 mSharedElementBundle = null; 493 if (mBackgroundAnimator != null) { 494 mBackgroundAnimator.cancel(); 495 mBackgroundAnimator = null; 496 } 497 mExitSharedElementBundle = null; 498 super.clearState(); 499 } 500 501 @Override 502 protected boolean moveSharedElementWithParent() { 503 return !mIsReturning; 504 } 505 506 @Override 507 protected Transition getViewsTransition() { 508 if (mIsReturning) { 509 return getWindow().getReturnTransition(); 510 } else { 511 return getWindow().getExitTransition(); 512 } 513 } 514 515 protected Transition getSharedElementTransition() { 516 if (mIsReturning) { 517 return getWindow().getSharedElementReturnTransition(); 518 } else { 519 return getWindow().getSharedElementExitTransition(); 520 } 521 } 522 523 interface HideSharedElementsCallback { 524 void hideSharedElements(); 525 } 526} 527