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