ExitTransitionCoordinator.java revision b5ef7f8c6d4629b2998de6c0b27bc1a4779b3e49
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.Rect; 23import android.graphics.drawable.ColorDrawable; 24import android.graphics.drawable.Drawable; 25import android.os.Bundle; 26import android.os.Handler; 27import android.os.Message; 28import android.transition.Transition; 29import android.transition.TransitionManager; 30import android.util.ArrayMap; 31import android.view.View; 32import android.view.ViewGroup; 33import android.view.ViewGroupOverlay; 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), 76 isReturning); 77 viewsReady(mapSharedElements(accepted, mapped)); 78 stripOffscreenViews(); 79 mIsBackgroundReady = !isReturning; 80 mActivity = activity; 81 } 82 83 private static SharedElementListener getListener(Activity activity, boolean isReturning) { 84 return isReturning ? activity.mEnterTransitionListener : activity.mExitTransitionListener; 85 } 86 87 @Override 88 protected void onReceiveResult(int resultCode, Bundle resultData) { 89 switch (resultCode) { 90 case MSG_SET_REMOTE_RECEIVER: 91 mResultReceiver = resultData.getParcelable(KEY_REMOTE_RECEIVER); 92 if (mIsCanceled) { 93 mResultReceiver.send(MSG_CANCEL, null); 94 mResultReceiver = null; 95 } else { 96 if (mHandler != null) { 97 mHandler.removeMessages(MSG_CANCEL); 98 } 99 notifyComplete(); 100 } 101 break; 102 case MSG_HIDE_SHARED_ELEMENTS: 103 if (!mIsCanceled) { 104 hideSharedElements(); 105 } 106 break; 107 case MSG_START_EXIT_TRANSITION: 108 startExit(); 109 break; 110 case MSG_SHARED_ELEMENT_DESTINATION: 111 mExitSharedElementBundle = resultData; 112 sharedElementExitBack(); 113 break; 114 } 115 } 116 117 public void resetViews() { 118 setTransitionAlpha(mTransitioningViews, 1); 119 setTransitionAlpha(mSharedElements, 1); 120 mIsHidden = true; 121 clearState(); 122 } 123 124 private void sharedElementExitBack() { 125 if (!mSharedElements.isEmpty() && getSharedElementTransition() != null) { 126 startTransition(new Runnable() { 127 public void run() { 128 startSharedElementExit(); 129 } 130 }); 131 } else { 132 sharedElementTransitionComplete(); 133 } 134 } 135 136 private void startSharedElementExit() { 137 Transition transition = getSharedElementExitTransition(); 138 final ArrayList<View> sharedElementSnapshots = createSnapshots(mExitSharedElementBundle, 139 mSharedElementNames); 140 transition.addListener(new Transition.TransitionListenerAdapter() { 141 @Override 142 public void onTransitionEnd(Transition transition) { 143 transition.removeListener(this); 144 int count = mSharedElements.size(); 145 for (int i = 0; i < count; i++) { 146 View sharedElement = mSharedElements.get(i); 147 ((ViewGroup)sharedElement.getParent()).suppressLayout(true); 148 } 149 } 150 }); 151 getDecor().getViewTreeObserver() 152 .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { 153 @Override 154 public boolean onPreDraw() { 155 getDecor().getViewTreeObserver().removeOnPreDrawListener(this); 156 setSharedElementState(mExitSharedElementBundle, sharedElementSnapshots); 157 return true; 158 } 159 }); 160 TransitionManager.beginDelayedTransition(getDecor(), transition); 161 getDecor().invalidate(); 162 } 163 164 private void hideSharedElements() { 165 if (!mIsHidden) { 166 setTransitionAlpha(mSharedElements, 0); 167 } 168 mSharedElementsHidden = true; 169 finishIfNecessary(); 170 } 171 172 public void startExit() { 173 if (!mIsExitStarted) { 174 mIsExitStarted = true; 175 startTransition(new Runnable() { 176 @Override 177 public void run() { 178 beginTransitions(); 179 } 180 }); 181 } 182 } 183 184 public void startExit(int resultCode, Intent data) { 185 if (!mIsExitStarted) { 186 mIsExitStarted = true; 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 } 226 } 227 228 private void fadeOutBackground() { 229 if (mBackgroundAnimator == null) { 230 ViewGroup decor = getDecor(); 231 Drawable background; 232 if (decor != null && (background = decor.getBackground()) != null) { 233 mBackgroundAnimator = ObjectAnimator.ofInt(background, "alpha", 0); 234 mBackgroundAnimator.addListener(new AnimatorListenerAdapter() { 235 @Override 236 public void onAnimationEnd(Animator animation) { 237 mBackgroundAnimator = null; 238 if (!mIsCanceled) { 239 mIsBackgroundReady = true; 240 notifyComplete(); 241 } 242 } 243 }); 244 mBackgroundAnimator.setDuration(getFadeDuration()); 245 mBackgroundAnimator.start(); 246 } else { 247 mIsBackgroundReady = true; 248 } 249 } 250 } 251 252 private Transition getExitTransition() { 253 Transition viewsTransition = null; 254 if (!mTransitioningViews.isEmpty()) { 255 viewsTransition = configureTransition(getViewsTransition(), true); 256 } 257 if (viewsTransition == null) { 258 exitTransitionComplete(); 259 } else { 260 viewsTransition.addListener(new ContinueTransitionListener() { 261 @Override 262 public void onTransitionEnd(Transition transition) { 263 transition.removeListener(this); 264 exitTransitionComplete(); 265 if (mIsHidden) { 266 setTransitionAlpha(mTransitioningViews, 1); 267 } 268 } 269 270 @Override 271 public void onTransitionCancel(Transition transition) { 272 super.onTransitionCancel(transition); 273 } 274 }); 275 viewsTransition.forceVisibility(View.INVISIBLE, false); 276 } 277 return viewsTransition; 278 } 279 280 private Transition getSharedElementExitTransition() { 281 Transition sharedElementTransition = null; 282 if (!mSharedElements.isEmpty()) { 283 sharedElementTransition = configureTransition(getSharedElementTransition(), false); 284 } 285 if (sharedElementTransition == null) { 286 sharedElementTransitionComplete(); 287 } else { 288 sharedElementTransition.addListener(new ContinueTransitionListener() { 289 @Override 290 public void onTransitionEnd(Transition transition) { 291 transition.removeListener(this); 292 sharedElementTransitionComplete(); 293 if (mIsHidden) { 294 setTransitionAlpha(mSharedElements, 1); 295 } 296 } 297 }); 298 mSharedElements.get(0).invalidate(); 299 } 300 return sharedElementTransition; 301 } 302 303 private void beginTransitions() { 304 Transition sharedElementTransition = getSharedElementExitTransition(); 305 Transition viewsTransition = getExitTransition(); 306 307 Transition transition = mergeTransitions(sharedElementTransition, viewsTransition); 308 if (transition != null) { 309 TransitionManager.beginDelayedTransition(getDecor(), transition); 310 getDecor().invalidate(); 311 } 312 } 313 314 private void exitTransitionComplete() { 315 mExitComplete = true; 316 notifyComplete(); 317 } 318 319 protected boolean isReadyToNotify() { 320 return mSharedElementBundle != null && mResultReceiver != null && mIsBackgroundReady; 321 } 322 323 private void sharedElementTransitionComplete() { 324 mSharedElementBundle = mExitSharedElementBundle == null 325 ? captureSharedElementState() : captureExitSharedElementsState(); 326 notifyComplete(); 327 } 328 329 private Bundle captureExitSharedElementsState() { 330 Bundle bundle = new Bundle(); 331 Rect bounds = new Rect(); 332 for (int i = 0; i < mSharedElementNames.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, 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