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