ExitTransitionCoordinator.java revision 68f96d8db5e5e701b6a12b5cddecc985e56a26c6
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.Matrix; 23import android.graphics.RectF; 24import android.graphics.drawable.ColorDrawable; 25import android.graphics.drawable.Drawable; 26import android.os.Bundle; 27import android.os.Handler; 28import android.os.Message; 29import android.transition.Transition; 30import android.transition.TransitionManager; 31import android.view.View; 32import android.view.ViewGroup; 33import android.view.ViewTreeObserver; 34 35import java.util.ArrayList; 36 37/** 38 * This ActivityTransitionCoordinator is created in ActivityOptions#makeSceneTransitionAnimation 39 * to govern the exit of the Scene and the shared elements when calling an Activity as well as 40 * the reentry of the Scene when coming back from the called Activity. 41 */ 42class ExitTransitionCoordinator extends ActivityTransitionCoordinator { 43 private static final String TAG = "ExitTransitionCoordinator"; 44 private static final long MAX_WAIT_MS = 1000; 45 46 private boolean mExitComplete; 47 48 private Bundle mSharedElementBundle; 49 50 private boolean mExitNotified; 51 52 private boolean mSharedElementNotified; 53 54 private Activity mActivity; 55 56 private boolean mIsBackgroundReady; 57 58 private boolean mIsCanceled; 59 60 private Handler mHandler; 61 62 private ObjectAnimator mBackgroundAnimator; 63 64 private boolean mIsHidden; 65 66 private Bundle mExitSharedElementBundle; 67 68 private boolean mIsExitStarted; 69 70 private boolean mSharedElementsHidden; 71 72 public ExitTransitionCoordinator(Activity activity, ArrayList<String> names, 73 ArrayList<String> accepted, ArrayList<View> mapped, boolean isReturning) { 74 super(activity.getWindow(), names, getListener(activity, isReturning), 75 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 mResultReceiver = resultData.getParcelable(KEY_REMOTE_RECEIVER); 91 if (mIsCanceled) { 92 mResultReceiver.send(MSG_CANCEL, null); 93 mResultReceiver = null; 94 } else { 95 if (mHandler != null) { 96 mHandler.removeMessages(MSG_CANCEL); 97 } 98 notifyComplete(); 99 } 100 break; 101 case MSG_HIDE_SHARED_ELEMENTS: 102 if (!mIsCanceled) { 103 hideSharedElements(); 104 } 105 break; 106 case MSG_START_EXIT_TRANSITION: 107 startExit(); 108 break; 109 case MSG_SHARED_ELEMENT_DESTINATION: 110 mExitSharedElementBundle = resultData; 111 sharedElementExitBack(); 112 break; 113 } 114 } 115 116 public void resetViews() { 117 setTransitionAlpha(mTransitioningViews, 1); 118 setTransitionAlpha(mSharedElements, 1); 119 mIsHidden = true; 120 if (!mIsReturning && getDecor() != null) { 121 getDecor().suppressLayout(false); 122 } 123 clearState(); 124 } 125 126 private void sharedElementExitBack() { 127 if (getDecor() != null) { 128 getDecor().suppressLayout(true); 129 } 130 if (!mSharedElements.isEmpty() && getSharedElementTransition() != null) { 131 startTransition(new Runnable() { 132 public void run() { 133 startSharedElementExit(); 134 } 135 }); 136 } else { 137 sharedElementTransitionComplete(); 138 } 139 } 140 141 private void startSharedElementExit() { 142 Transition transition = getSharedElementExitTransition(); 143 final ArrayList<View> sharedElementSnapshots = createSnapshots(mExitSharedElementBundle, 144 mSharedElementNames); 145 getDecor().getViewTreeObserver() 146 .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { 147 @Override 148 public boolean onPreDraw() { 149 getDecor().getViewTreeObserver().removeOnPreDrawListener(this); 150 setSharedElementState(mExitSharedElementBundle, sharedElementSnapshots); 151 return true; 152 } 153 }); 154 TransitionManager.beginDelayedTransition(getDecor(), transition); 155 getDecor().invalidate(); 156 } 157 158 private void hideSharedElements() { 159 if (!mIsHidden) { 160 setTransitionAlpha(mSharedElements, 0); 161 } 162 mSharedElementsHidden = true; 163 finishIfNecessary(); 164 } 165 166 public void startExit() { 167 if (!mIsExitStarted) { 168 mIsExitStarted = true; 169 if (getDecor() != null) { 170 getDecor().suppressLayout(true); 171 } 172 startTransition(new Runnable() { 173 @Override 174 public void run() { 175 beginTransitions(); 176 } 177 }); 178 } 179 } 180 181 public void startExit(int resultCode, Intent data) { 182 if (!mIsExitStarted) { 183 mIsExitStarted = true; 184 if (getDecor() != null) { 185 getDecor().suppressLayout(true); 186 } 187 mHandler = new Handler() { 188 @Override 189 public void handleMessage(Message msg) { 190 mIsCanceled = true; 191 finish(); 192 } 193 }; 194 mHandler.sendEmptyMessageDelayed(MSG_CANCEL, MAX_WAIT_MS); 195 if (getDecor().getBackground() == null) { 196 ColorDrawable black = new ColorDrawable(0xFF000000); 197 black.setAlpha(0); 198 getWindow().setBackgroundDrawable(black); 199 black.setAlpha(255); 200 } 201 ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(mActivity, this, 202 mAllSharedElementNames, resultCode, data); 203 mActivity.convertToTranslucent(new Activity.TranslucentConversionListener() { 204 @Override 205 public void onTranslucentConversionComplete(boolean drawComplete) { 206 if (!mIsCanceled) { 207 fadeOutBackground(); 208 } 209 } 210 }, options); 211 startTransition(new Runnable() { 212 @Override 213 public void run() { 214 startExitTransition(); 215 } 216 }); 217 } 218 } 219 220 private void startExitTransition() { 221 Transition transition = getExitTransition(); 222 if (transition != null) { 223 TransitionManager.beginDelayedTransition(getDecor(), transition); 224 mTransitioningViews.get(0).invalidate(); 225 } else { 226 transitionStarted(); 227 } 228 } 229 230 private void fadeOutBackground() { 231 if (mBackgroundAnimator == null) { 232 ViewGroup decor = getDecor(); 233 Drawable background; 234 if (decor != null && (background = decor.getBackground()) != null) { 235 mBackgroundAnimator = ObjectAnimator.ofInt(background, "alpha", 0); 236 mBackgroundAnimator.addListener(new AnimatorListenerAdapter() { 237 @Override 238 public void onAnimationEnd(Animator animation) { 239 mBackgroundAnimator = null; 240 if (!mIsCanceled) { 241 mIsBackgroundReady = true; 242 notifyComplete(); 243 } 244 } 245 }); 246 mBackgroundAnimator.setDuration(getFadeDuration()); 247 mBackgroundAnimator.start(); 248 } else { 249 mIsBackgroundReady = true; 250 } 251 } 252 } 253 254 private Transition getExitTransition() { 255 Transition viewsTransition = null; 256 if (!mTransitioningViews.isEmpty()) { 257 viewsTransition = configureTransition(getViewsTransition(), true); 258 } 259 if (viewsTransition == null) { 260 exitTransitionComplete(); 261 } else { 262 viewsTransition.addListener(new ContinueTransitionListener() { 263 @Override 264 public void onTransitionEnd(Transition transition) { 265 transition.removeListener(this); 266 exitTransitionComplete(); 267 if (mIsHidden) { 268 setTransitionAlpha(mTransitioningViews, 1); 269 } 270 } 271 272 @Override 273 public void onTransitionCancel(Transition transition) { 274 super.onTransitionCancel(transition); 275 } 276 }); 277 viewsTransition.forceVisibility(View.INVISIBLE, false); 278 } 279 return viewsTransition; 280 } 281 282 private Transition getSharedElementExitTransition() { 283 Transition sharedElementTransition = null; 284 if (!mSharedElements.isEmpty()) { 285 sharedElementTransition = configureTransition(getSharedElementTransition(), false); 286 } 287 if (sharedElementTransition == null) { 288 sharedElementTransitionComplete(); 289 } else { 290 sharedElementTransition.addListener(new ContinueTransitionListener() { 291 @Override 292 public void onTransitionEnd(Transition transition) { 293 transition.removeListener(this); 294 sharedElementTransitionComplete(); 295 if (mIsHidden) { 296 setTransitionAlpha(mSharedElements, 1); 297 } 298 } 299 }); 300 mSharedElements.get(0).invalidate(); 301 } 302 return sharedElementTransition; 303 } 304 305 private void beginTransitions() { 306 Transition sharedElementTransition = getSharedElementExitTransition(); 307 Transition viewsTransition = getExitTransition(); 308 309 Transition transition = mergeTransitions(sharedElementTransition, viewsTransition); 310 if (transition != null) { 311 TransitionManager.beginDelayedTransition(getDecor(), transition); 312 getDecor().invalidate(); 313 } else { 314 transitionStarted(); 315 } 316 } 317 318 private void exitTransitionComplete() { 319 mExitComplete = true; 320 notifyComplete(); 321 } 322 323 protected boolean isReadyToNotify() { 324 return mSharedElementBundle != null && mResultReceiver != null && mIsBackgroundReady; 325 } 326 327 private void sharedElementTransitionComplete() { 328 mSharedElementBundle = mExitSharedElementBundle == null 329 ? captureSharedElementState() : captureExitSharedElementsState(); 330 notifyComplete(); 331 } 332 333 private Bundle captureExitSharedElementsState() { 334 Bundle bundle = new Bundle(); 335 RectF bounds = new RectF(); 336 Matrix matrix = new Matrix(); 337 for (int i = 0; i < mSharedElements.size(); i++) { 338 String name = mSharedElementNames.get(i); 339 Bundle sharedElementState = mExitSharedElementBundle.getBundle(name); 340 if (sharedElementState != null) { 341 bundle.putBundle(name, sharedElementState); 342 } else { 343 View view = mSharedElements.get(i); 344 captureSharedElementState(view, name, bundle, matrix, bounds); 345 } 346 } 347 return bundle; 348 } 349 350 protected void notifyComplete() { 351 if (isReadyToNotify()) { 352 if (!mSharedElementNotified) { 353 mSharedElementNotified = true; 354 mResultReceiver.send(MSG_TAKE_SHARED_ELEMENTS, mSharedElementBundle); 355 } 356 if (!mExitNotified && mExitComplete) { 357 mExitNotified = true; 358 mResultReceiver.send(MSG_EXIT_TRANSITION_COMPLETE, null); 359 mResultReceiver = null; // done talking 360 if (!mIsReturning && getDecor() != null) { 361 getDecor().suppressLayout(false); 362 } 363 finishIfNecessary(); 364 } 365 } 366 } 367 368 private void finishIfNecessary() { 369 if (mIsReturning && mExitNotified && mActivity != null && (mSharedElements.isEmpty() || 370 mSharedElementsHidden)) { 371 finish(); 372 } 373 if (!mIsReturning && mExitNotified) { 374 mActivity = null; // don't need it anymore 375 } 376 } 377 378 private void finish() { 379 mActivity.mActivityTransitionState.clear(); 380 // Clear the state so that we can't hold any references accidentally and leak memory. 381 mHandler.removeMessages(MSG_CANCEL); 382 mHandler = null; 383 mActivity.finish(); 384 mActivity.overridePendingTransition(0, 0); 385 mActivity = null; 386 mSharedElementBundle = null; 387 if (mBackgroundAnimator != null) { 388 mBackgroundAnimator.cancel(); 389 mBackgroundAnimator = null; 390 } 391 mExitSharedElementBundle = null; 392 clearState(); 393 } 394 395 @Override 396 protected Transition getViewsTransition() { 397 if (mIsReturning) { 398 return getWindow().getReturnTransition(); 399 } else { 400 return getWindow().getExitTransition(); 401 } 402 } 403 404 protected Transition getSharedElementTransition() { 405 if (mIsReturning) { 406 return getWindow().getSharedElementReturnTransition(); 407 } else { 408 return getWindow().getSharedElementExitTransition(); 409 } 410 } 411} 412