ExitTransitionCoordinator.java revision ce2ee3d6de670e52abed7f432778701113ba9c07
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.content.Intent; 22import android.graphics.Color; 23import android.graphics.Matrix; 24import android.graphics.RectF; 25import android.graphics.drawable.ColorDrawable; 26import android.graphics.drawable.Drawable; 27import android.os.Bundle; 28import android.os.Handler; 29import android.os.Message; 30import android.transition.Transition; 31import android.transition.TransitionManager; 32import android.view.View; 33import android.view.ViewGroup; 34import android.view.ViewTreeObserver; 35 36import java.util.ArrayList; 37 38/** 39 * This ActivityTransitionCoordinator is created in ActivityOptions#makeSceneTransitionAnimation 40 * to govern the exit of the Scene and the shared elements when calling an Activity as well as 41 * the reentry of the Scene when coming back from the called Activity. 42 */ 43class ExitTransitionCoordinator extends ActivityTransitionCoordinator { 44 private static final String TAG = "ExitTransitionCoordinator"; 45 private static final long MAX_WAIT_MS = 1000; 46 47 private boolean mExitComplete; 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 showViews(mTransitioningViews, true); 130 showViews(mSharedElements, true); 131 mIsHidden = true; 132 if (!mIsReturning && getDecor() != null) { 133 getDecor().suppressLayout(false); 134 } 135 moveSharedElementsFromOverlay(); 136 clearState(); 137 } 138 139 private void sharedElementExitBack() { 140 if (getDecor() != null) { 141 getDecor().suppressLayout(true); 142 } 143 if (mExitSharedElementBundle != null && !mExitSharedElementBundle.isEmpty() && 144 !mSharedElements.isEmpty() && getSharedElementTransition() != null) { 145 startTransition(new Runnable() { 146 public void run() { 147 startSharedElementExit(); 148 } 149 }); 150 } else { 151 sharedElementTransitionComplete(); 152 } 153 } 154 155 private void startSharedElementExit() { 156 Transition transition = getSharedElementExitTransition(); 157 transition.addListener(new Transition.TransitionListenerAdapter() { 158 @Override 159 public void onTransitionEnd(Transition transition) { 160 transition.removeListener(this); 161 if (mExitComplete) { 162 delayCancel(); 163 } 164 } 165 }); 166 final ArrayList<View> sharedElementSnapshots = createSnapshots(mExitSharedElementBundle, 167 mSharedElementNames); 168 final View decorView = getDecor(); 169 decorView.getViewTreeObserver() 170 .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { 171 @Override 172 public boolean onPreDraw() { 173 decorView.getViewTreeObserver().removeOnPreDrawListener(this); 174 setSharedElementState(mExitSharedElementBundle, sharedElementSnapshots); 175 return true; 176 } 177 }); 178 setGhostVisibility(View.INVISIBLE); 179 scheduleGhostVisibilityChange(View.INVISIBLE); 180 mListener.onSharedElementEnd(mSharedElementNames, mSharedElements, sharedElementSnapshots); 181 TransitionManager.beginDelayedTransition(getDecor(), transition); 182 scheduleGhostVisibilityChange(View.VISIBLE); 183 setGhostVisibility(View.VISIBLE); 184 getDecor().invalidate(); 185 } 186 187 private void hideSharedElements() { 188 moveSharedElementsFromOverlay(); 189 if (!mIsHidden) { 190 hideViews(mSharedElements); 191 } 192 mSharedElementsHidden = true; 193 finishIfNecessary(); 194 } 195 196 public void startExit() { 197 if (!mIsExitStarted) { 198 mIsExitStarted = true; 199 if (getDecor() != null) { 200 getDecor().suppressLayout(true); 201 } 202 moveSharedElementsToOverlay(); 203 startTransition(new Runnable() { 204 @Override 205 public void run() { 206 beginTransitions(); 207 } 208 }); 209 } 210 } 211 212 public void startExit(int resultCode, Intent data) { 213 if (!mIsExitStarted) { 214 mIsExitStarted = true; 215 if (getDecor() != null) { 216 getDecor().suppressLayout(true); 217 } 218 mHandler = new Handler() { 219 @Override 220 public void handleMessage(Message msg) { 221 mIsCanceled = true; 222 finish(); 223 } 224 }; 225 delayCancel(); 226 moveSharedElementsToOverlay(); 227 if (getDecor().getBackground() == null) { 228 getWindow().setBackgroundDrawable(new ColorDrawable(Color.BLACK)); 229 } 230 ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(mActivity, this, 231 mAllSharedElementNames, resultCode, data); 232 mActivity.convertToTranslucent(new Activity.TranslucentConversionListener() { 233 @Override 234 public void onTranslucentConversionComplete(boolean drawComplete) { 235 if (!mIsCanceled) { 236 fadeOutBackground(); 237 } 238 } 239 }, options); 240 startTransition(new Runnable() { 241 @Override 242 public void run() { 243 startExitTransition(); 244 } 245 }); 246 } 247 } 248 249 private void startExitTransition() { 250 Transition transition = getExitTransition(); 251 if (transition != null) { 252 TransitionManager.beginDelayedTransition(getDecor(), transition); 253 mTransitioningViews.get(0).invalidate(); 254 } else { 255 transitionStarted(); 256 } 257 } 258 259 private void fadeOutBackground() { 260 if (mBackgroundAnimator == null) { 261 ViewGroup decor = getDecor(); 262 Drawable background; 263 if (decor != null && (background = decor.getBackground()) != null) { 264 background = background.mutate(); 265 getWindow().setBackgroundDrawable(background); 266 mBackgroundAnimator = ObjectAnimator.ofInt(background, "alpha", 0); 267 mBackgroundAnimator.addListener(new AnimatorListenerAdapter() { 268 @Override 269 public void onAnimationEnd(Animator animation) { 270 mBackgroundAnimator = null; 271 if (!mIsCanceled) { 272 mIsBackgroundReady = true; 273 notifyComplete(); 274 } 275 } 276 }); 277 mBackgroundAnimator.setDuration(getFadeDuration()); 278 mBackgroundAnimator.start(); 279 } else { 280 mIsBackgroundReady = true; 281 } 282 } 283 } 284 285 private Transition getExitTransition() { 286 Transition viewsTransition = null; 287 if (!mTransitioningViews.isEmpty()) { 288 viewsTransition = configureTransition(getViewsTransition(), true); 289 } 290 if (viewsTransition == null) { 291 exitTransitionComplete(); 292 } else { 293 viewsTransition.addListener(new ContinueTransitionListener() { 294 @Override 295 public void onTransitionEnd(Transition transition) { 296 transition.removeListener(this); 297 exitTransitionComplete(); 298 if (mIsHidden) { 299 showViews(mTransitioningViews, true); 300 } 301 if (mSharedElementBundle != null) { 302 delayCancel(); 303 } 304 super.onTransitionEnd(transition); 305 } 306 }); 307 viewsTransition.forceVisibility(View.INVISIBLE, false); 308 } 309 return viewsTransition; 310 } 311 312 private Transition getSharedElementExitTransition() { 313 Transition sharedElementTransition = null; 314 if (!mSharedElements.isEmpty()) { 315 sharedElementTransition = configureTransition(getSharedElementTransition(), false); 316 } 317 if (sharedElementTransition == null) { 318 sharedElementTransitionComplete(); 319 } else { 320 sharedElementTransition.addListener(new ContinueTransitionListener() { 321 @Override 322 public void onTransitionEnd(Transition transition) { 323 transition.removeListener(this); 324 sharedElementTransitionComplete(); 325 if (mIsHidden) { 326 showViews(mSharedElements, true); 327 } 328 } 329 }); 330 mSharedElements.get(0).invalidate(); 331 } 332 return sharedElementTransition; 333 } 334 335 private void beginTransitions() { 336 Transition sharedElementTransition = getSharedElementExitTransition(); 337 Transition viewsTransition = getExitTransition(); 338 339 Transition transition = mergeTransitions(sharedElementTransition, viewsTransition); 340 if (transition != null) { 341 setGhostVisibility(View.INVISIBLE); 342 scheduleGhostVisibilityChange(View.INVISIBLE); 343 TransitionManager.beginDelayedTransition(getDecor(), transition); 344 scheduleGhostVisibilityChange(View.VISIBLE); 345 setGhostVisibility(View.VISIBLE); 346 getDecor().invalidate(); 347 } else { 348 transitionStarted(); 349 } 350 } 351 352 private void exitTransitionComplete() { 353 mExitComplete = true; 354 notifyComplete(); 355 } 356 357 protected boolean isReadyToNotify() { 358 return mSharedElementBundle != null && mResultReceiver != null && mIsBackgroundReady; 359 } 360 361 private void sharedElementTransitionComplete() { 362 mSharedElementBundle = mExitSharedElementBundle == null 363 ? captureSharedElementState() : captureExitSharedElementsState(); 364 notifyComplete(); 365 } 366 367 private Bundle captureExitSharedElementsState() { 368 Bundle bundle = new Bundle(); 369 RectF bounds = new RectF(); 370 Matrix matrix = new Matrix(); 371 for (int i = 0; i < mSharedElements.size(); i++) { 372 String name = mSharedElementNames.get(i); 373 Bundle sharedElementState = mExitSharedElementBundle.getBundle(name); 374 if (sharedElementState != null) { 375 bundle.putBundle(name, sharedElementState); 376 } else { 377 View view = mSharedElements.get(i); 378 captureSharedElementState(view, name, bundle, matrix, bounds); 379 } 380 } 381 return bundle; 382 } 383 384 protected void notifyComplete() { 385 if (isReadyToNotify()) { 386 if (!mSharedElementNotified) { 387 mSharedElementNotified = true; 388 delayCancel(); 389 mResultReceiver.send(MSG_TAKE_SHARED_ELEMENTS, mSharedElementBundle); 390 } 391 if (!mExitNotified && mExitComplete) { 392 mExitNotified = true; 393 mResultReceiver.send(MSG_EXIT_TRANSITION_COMPLETE, null); 394 mResultReceiver = null; // done talking 395 if (!mIsReturning && getDecor() != null) { 396 getDecor().suppressLayout(false); 397 } 398 finishIfNecessary(); 399 } 400 } 401 } 402 403 private void finishIfNecessary() { 404 if (mIsReturning && mExitNotified && mActivity != null && (mSharedElements.isEmpty() || 405 mSharedElementsHidden)) { 406 finish(); 407 } 408 if (!mIsReturning && mExitNotified) { 409 mActivity = null; // don't need it anymore 410 } 411 } 412 413 private void finish() { 414 stopCancel(); 415 mActivity.mActivityTransitionState.clear(); 416 // Clear the state so that we can't hold any references accidentally and leak memory. 417 mHandler.removeMessages(MSG_CANCEL); 418 mHandler = null; 419 mActivity.finish(); 420 mActivity.overridePendingTransition(0, 0); 421 mActivity = null; 422 mSharedElementBundle = null; 423 if (mBackgroundAnimator != null) { 424 mBackgroundAnimator.cancel(); 425 mBackgroundAnimator = null; 426 } 427 mExitSharedElementBundle = null; 428 clearState(); 429 } 430 431 @Override 432 protected boolean moveSharedElementWithParent() { 433 return !mIsReturning; 434 } 435 436 @Override 437 protected Transition getViewsTransition() { 438 if (mIsReturning) { 439 return getWindow().getReturnTransition(); 440 } else { 441 return getWindow().getExitTransition(); 442 } 443 } 444 445 protected Transition getSharedElementTransition() { 446 if (mIsReturning) { 447 return getWindow().getSharedElementReturnTransition(); 448 } else { 449 return getWindow().getSharedElementExitTransition(); 450 } 451 } 452} 453