ExitTransitionCoordinator.java revision 7bf379c8af399626d5b8b568fe1d4f96f56badcc
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 clearState(); 121 } 122 123 private void sharedElementExitBack() { 124 if (!mSharedElements.isEmpty() && getSharedElementTransition() != null) { 125 startTransition(new Runnable() { 126 public void run() { 127 startSharedElementExit(); 128 } 129 }); 130 } else { 131 sharedElementTransitionComplete(); 132 } 133 } 134 135 private void startSharedElementExit() { 136 Transition transition = getSharedElementExitTransition(); 137 final ArrayList<View> sharedElementSnapshots = createSnapshots(mExitSharedElementBundle, 138 mSharedElementNames); 139 transition.addListener(new Transition.TransitionListenerAdapter() { 140 @Override 141 public void onTransitionEnd(Transition transition) { 142 transition.removeListener(this); 143 int count = mSharedElements.size(); 144 for (int i = 0; i < count; i++) { 145 View sharedElement = mSharedElements.get(i); 146 ((ViewGroup)sharedElement.getParent()).suppressLayout(true); 147 } 148 } 149 }); 150 getDecor().getViewTreeObserver() 151 .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { 152 @Override 153 public boolean onPreDraw() { 154 getDecor().getViewTreeObserver().removeOnPreDrawListener(this); 155 setSharedElementState(mExitSharedElementBundle, sharedElementSnapshots); 156 return true; 157 } 158 }); 159 TransitionManager.beginDelayedTransition(getDecor(), transition); 160 getDecor().invalidate(); 161 } 162 163 private void hideSharedElements() { 164 if (!mIsHidden) { 165 setTransitionAlpha(mSharedElements, 0); 166 } 167 mSharedElementsHidden = true; 168 finishIfNecessary(); 169 } 170 171 public void startExit() { 172 if (!mIsExitStarted) { 173 mIsExitStarted = true; 174 startTransition(new Runnable() { 175 @Override 176 public void run() { 177 beginTransitions(); 178 } 179 }); 180 } 181 } 182 183 public void startExit(int resultCode, Intent data) { 184 if (!mIsExitStarted) { 185 mIsExitStarted = true; 186 mHandler = new Handler() { 187 @Override 188 public void handleMessage(Message msg) { 189 mIsCanceled = true; 190 finish(); 191 } 192 }; 193 mHandler.sendEmptyMessageDelayed(MSG_CANCEL, MAX_WAIT_MS); 194 if (getDecor().getBackground() == null) { 195 ColorDrawable black = new ColorDrawable(0xFF000000); 196 black.setAlpha(0); 197 getWindow().setBackgroundDrawable(black); 198 black.setAlpha(255); 199 } 200 ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(mActivity, this, 201 mAllSharedElementNames, resultCode, data); 202 mActivity.convertToTranslucent(new Activity.TranslucentConversionListener() { 203 @Override 204 public void onTranslucentConversionComplete(boolean drawComplete) { 205 if (!mIsCanceled) { 206 fadeOutBackground(); 207 } 208 } 209 }, options); 210 startTransition(new Runnable() { 211 @Override 212 public void run() { 213 startExitTransition(); 214 } 215 }); 216 } 217 } 218 219 private void startExitTransition() { 220 Transition transition = getExitTransition(); 221 if (transition != null) { 222 TransitionManager.beginDelayedTransition(getDecor(), transition); 223 mTransitioningViews.get(0).invalidate(); 224 } 225 } 226 227 private void fadeOutBackground() { 228 if (mBackgroundAnimator == null) { 229 ViewGroup decor = getDecor(); 230 Drawable background; 231 if (decor != null && (background = decor.getBackground()) != null) { 232 mBackgroundAnimator = ObjectAnimator.ofInt(background, "alpha", 0); 233 mBackgroundAnimator.addListener(new AnimatorListenerAdapter() { 234 @Override 235 public void onAnimationEnd(Animator animation) { 236 mBackgroundAnimator = null; 237 if (!mIsCanceled) { 238 mIsBackgroundReady = true; 239 notifyComplete(); 240 } 241 } 242 }); 243 mBackgroundAnimator.setDuration(getFadeDuration()); 244 mBackgroundAnimator.start(); 245 } else { 246 mIsBackgroundReady = true; 247 } 248 } 249 } 250 251 private Transition getExitTransition() { 252 Transition viewsTransition = null; 253 if (!mTransitioningViews.isEmpty()) { 254 viewsTransition = configureTransition(getViewsTransition(), true); 255 } 256 if (viewsTransition == null) { 257 exitTransitionComplete(); 258 } else { 259 viewsTransition.addListener(new ContinueTransitionListener() { 260 @Override 261 public void onTransitionEnd(Transition transition) { 262 transition.removeListener(this); 263 exitTransitionComplete(); 264 if (mIsHidden) { 265 setTransitionAlpha(mTransitioningViews, 1); 266 } 267 } 268 269 @Override 270 public void onTransitionCancel(Transition transition) { 271 super.onTransitionCancel(transition); 272 } 273 }); 274 viewsTransition.forceVisibility(View.INVISIBLE, false); 275 } 276 return viewsTransition; 277 } 278 279 private Transition getSharedElementExitTransition() { 280 Transition sharedElementTransition = null; 281 if (!mSharedElements.isEmpty()) { 282 sharedElementTransition = configureTransition(getSharedElementTransition(), false); 283 } 284 if (sharedElementTransition == null) { 285 sharedElementTransitionComplete(); 286 } else { 287 sharedElementTransition.addListener(new ContinueTransitionListener() { 288 @Override 289 public void onTransitionEnd(Transition transition) { 290 transition.removeListener(this); 291 sharedElementTransitionComplete(); 292 if (mIsHidden) { 293 setTransitionAlpha(mSharedElements, 1); 294 } 295 } 296 }); 297 mSharedElements.get(0).invalidate(); 298 } 299 return sharedElementTransition; 300 } 301 302 private void beginTransitions() { 303 Transition sharedElementTransition = getSharedElementExitTransition(); 304 Transition viewsTransition = getExitTransition(); 305 306 Transition transition = mergeTransitions(sharedElementTransition, viewsTransition); 307 if (transition != null) { 308 TransitionManager.beginDelayedTransition(getDecor(), transition); 309 getDecor().invalidate(); 310 } 311 } 312 313 private void exitTransitionComplete() { 314 mExitComplete = true; 315 notifyComplete(); 316 } 317 318 protected boolean isReadyToNotify() { 319 return mSharedElementBundle != null && mResultReceiver != null && mIsBackgroundReady; 320 } 321 322 private void sharedElementTransitionComplete() { 323 mSharedElementBundle = mExitSharedElementBundle == null 324 ? captureSharedElementState() : captureExitSharedElementsState(); 325 notifyComplete(); 326 } 327 328 private Bundle captureExitSharedElementsState() { 329 Bundle bundle = new Bundle(); 330 RectF bounds = new RectF(); 331 Matrix matrix = new Matrix(); 332 for (int i = 0; i < mSharedElements.size(); i++) { 333 String name = mSharedElementNames.get(i); 334 Bundle sharedElementState = mExitSharedElementBundle.getBundle(name); 335 if (sharedElementState != null) { 336 bundle.putBundle(name, sharedElementState); 337 } else { 338 View view = mSharedElements.get(i); 339 captureSharedElementState(view, name, bundle, matrix, bounds); 340 } 341 } 342 return bundle; 343 } 344 345 protected void notifyComplete() { 346 if (isReadyToNotify()) { 347 if (!mSharedElementNotified) { 348 mSharedElementNotified = true; 349 mResultReceiver.send(MSG_TAKE_SHARED_ELEMENTS, mSharedElementBundle); 350 } 351 if (!mExitNotified && mExitComplete) { 352 mExitNotified = true; 353 mResultReceiver.send(MSG_EXIT_TRANSITION_COMPLETE, null); 354 mResultReceiver = null; // done talking 355 finishIfNecessary(); 356 } 357 } 358 } 359 360 private void finishIfNecessary() { 361 if (mIsReturning && mExitNotified && mActivity != null && (mSharedElements.isEmpty() || 362 mSharedElementsHidden)) { 363 finish(); 364 } 365 if (!mIsReturning && mExitNotified) { 366 mActivity = null; // don't need it anymore 367 } 368 } 369 370 private void finish() { 371 mActivity.mActivityTransitionState.clear(); 372 // Clear the state so that we can't hold any references accidentally and leak memory. 373 mHandler.removeMessages(MSG_CANCEL); 374 mHandler = null; 375 mActivity.finish(); 376 mActivity.overridePendingTransition(0, 0); 377 mActivity = null; 378 mSharedElementBundle = null; 379 if (mBackgroundAnimator != null) { 380 mBackgroundAnimator.cancel(); 381 mBackgroundAnimator = null; 382 } 383 mExitSharedElementBundle = null; 384 clearState(); 385 } 386 387 @Override 388 protected Transition getViewsTransition() { 389 if (mIsReturning) { 390 return getWindow().getEnterTransition(); 391 } else { 392 return getWindow().getExitTransition(); 393 } 394 } 395 396 protected Transition getSharedElementTransition() { 397 if (mIsReturning) { 398 return getWindow().getSharedElementEnterTransition(); 399 } else { 400 return getWindow().getSharedElementExitTransition(); 401 } 402 } 403} 404