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