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