EnterTransitionCoordinator.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.graphics.Matrix; 22import android.graphics.drawable.Drawable; 23import android.os.Bundle; 24import android.os.Handler; 25import android.os.Message; 26import android.os.ResultReceiver; 27import android.transition.Transition; 28import android.transition.TransitionManager; 29import android.util.ArrayMap; 30import android.util.Pair; 31import android.view.View; 32import android.view.ViewGroup; 33import android.view.ViewGroupOverlay; 34import android.view.ViewTreeObserver; 35import android.widget.ImageView; 36 37import java.util.ArrayList; 38 39/** 40 * This ActivityTransitionCoordinator is created by the Activity to manage 41 * the enter scene and shared element transfer into the Scene, either during 42 * launch of an Activity or returning from a launched Activity. 43 */ 44class EnterTransitionCoordinator extends ActivityTransitionCoordinator { 45 private static final String TAG = "EnterTransitionCoordinator"; 46 47 private static final long MAX_WAIT_MS = 1000; 48 49 private boolean mSharedElementTransitionStarted; 50 private Activity mActivity; 51 private boolean mHasStopped; 52 private Handler mHandler; 53 private boolean mIsCanceled; 54 private ObjectAnimator mBackgroundAnimator; 55 private boolean mIsExitTransitionComplete; 56 private boolean mIsReadyForTransition; 57 private Bundle mSharedElementsBundle; 58 private boolean mWasOpaque; 59 60 public EnterTransitionCoordinator(Activity activity, ResultReceiver resultReceiver, 61 ArrayList<String> sharedElementNames, boolean isReturning) { 62 super(activity.getWindow(), sharedElementNames, 63 getListener(activity, isReturning), isReturning); 64 mActivity = activity; 65 setResultReceiver(resultReceiver); 66 prepareEnter(); 67 Bundle resultReceiverBundle = new Bundle(); 68 resultReceiverBundle.putParcelable(KEY_REMOTE_RECEIVER, this); 69 mResultReceiver.send(MSG_SET_REMOTE_RECEIVER, resultReceiverBundle); 70 getDecor().getViewTreeObserver().addOnPreDrawListener( 71 new ViewTreeObserver.OnPreDrawListener() { 72 @Override 73 public boolean onPreDraw() { 74 if (mIsReadyForTransition) { 75 getDecor().getViewTreeObserver().removeOnPreDrawListener(this); 76 } 77 return mIsReadyForTransition; 78 } 79 }); 80 } 81 82 public void viewInstancesReady(ArrayList<String> accepted, ArrayList<View> localViews) { 83 if (mIsReadyForTransition) { 84 return; 85 } 86 viewsReady(mapSharedElements(accepted, localViews)); 87 } 88 89 public void namedViewsReady(ArrayList<String> accepted, ArrayList<String> localNames) { 90 if (mIsReadyForTransition) { 91 return; 92 } 93 94 viewsReady(mapNamedElements(accepted, localNames)); 95 } 96 97 @Override 98 protected void viewsReady(ArrayMap<String, View> sharedElements) { 99 super.viewsReady(sharedElements); 100 mIsReadyForTransition = true; 101 if (mIsReturning) { 102 mHandler = new Handler() { 103 @Override 104 public void handleMessage(Message msg) { 105 cancel(); 106 } 107 }; 108 mHandler.sendEmptyMessageDelayed(MSG_CANCEL, MAX_WAIT_MS); 109 send(MSG_SEND_SHARED_ELEMENT_DESTINATION, null); 110 } 111 setViewVisibility(mSharedElements, View.INVISIBLE); 112 if (getViewsTransition() != null) { 113 setViewVisibility(mTransitioningViews, View.INVISIBLE); 114 } 115 if (mSharedElementsBundle != null) { 116 onTakeSharedElements(); 117 } 118 } 119 120 private ArrayMap<String, View> mapNamedElements(ArrayList<String> accepted, 121 ArrayList<String> localNames) { 122 ArrayMap<String, View> sharedElements = new ArrayMap<String, View>(); 123 getDecor().findNamedViews(sharedElements); 124 if (accepted != null) { 125 for (int i = 0; i < localNames.size(); i++) { 126 String localName = localNames.get(i); 127 String acceptedName = accepted.get(i); 128 if (localName != null && !localName.equals(acceptedName)) { 129 View view = sharedElements.remove(localName); 130 if (view != null) { 131 sharedElements.put(acceptedName, view); 132 } 133 } 134 } 135 } 136 return sharedElements; 137 } 138 139 private void sendSharedElementDestination() { 140 ViewGroup decor = getDecor(); 141 boolean allReady = !decor.isLayoutRequested(); 142 if (allReady) { 143 for (int i = 0; i < mSharedElements.size(); i++) { 144 if (mSharedElements.get(i).isLayoutRequested()) { 145 allReady = false; 146 break; 147 } 148 } 149 } 150 if (allReady) { 151 Bundle state = captureSharedElementState(); 152 mResultReceiver.send(MSG_SHARED_ELEMENT_DESTINATION, state); 153 } else { 154 getDecor().getViewTreeObserver() 155 .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { 156 @Override 157 public boolean onPreDraw() { 158 getDecor().getViewTreeObserver().removeOnPreDrawListener(this); 159 Bundle state = captureSharedElementState(); 160 mResultReceiver.send(MSG_SHARED_ELEMENT_DESTINATION, state); 161 return true; 162 } 163 }); 164 } 165 if (allowOverlappingTransitions()) { 166 startEnterTransitionOnly(); 167 } 168 } 169 170 private static SharedElementListener getListener(Activity activity, boolean isReturning) { 171 return isReturning ? activity.mExitTransitionListener : activity.mEnterTransitionListener; 172 } 173 174 @Override 175 protected void onReceiveResult(int resultCode, Bundle resultData) { 176 switch (resultCode) { 177 case MSG_TAKE_SHARED_ELEMENTS: 178 if (!mIsCanceled) { 179 if (mHandler != null) { 180 mHandler.removeMessages(MSG_CANCEL); 181 } 182 mSharedElementsBundle = resultData; 183 onTakeSharedElements(); 184 } 185 break; 186 case MSG_EXIT_TRANSITION_COMPLETE: 187 if (!mIsCanceled) { 188 mIsExitTransitionComplete = true; 189 if (mSharedElementTransitionStarted) { 190 onRemoteExitTransitionComplete(); 191 } 192 } 193 break; 194 case MSG_CANCEL: 195 cancel(); 196 break; 197 case MSG_SEND_SHARED_ELEMENT_DESTINATION: 198 sendSharedElementDestination(); 199 break; 200 } 201 } 202 203 private void cancel() { 204 if (!mIsCanceled) { 205 mIsCanceled = true; 206 if (getViewsTransition() == null) { 207 setViewVisibility(mSharedElements, View.VISIBLE); 208 } else { 209 mTransitioningViews.addAll(mSharedElements); 210 } 211 mSharedElementNames.clear(); 212 mSharedElements.clear(); 213 mAllSharedElementNames.clear(); 214 startSharedElementTransition(null); 215 onRemoteExitTransitionComplete(); 216 } 217 } 218 219 public boolean isReturning() { 220 return mIsReturning; 221 } 222 223 protected void prepareEnter() { 224 mActivity.overridePendingTransition(0, 0); 225 if (!mIsReturning) { 226 mWasOpaque = mActivity.convertToTranslucent(null, null); 227 Drawable background = getDecor().getBackground(); 228 if (background != null) { 229 getWindow().setBackgroundDrawable(null); 230 background = background.mutate(); 231 background.setAlpha(0); 232 getWindow().setBackgroundDrawable(background); 233 } 234 } else { 235 mActivity = null; // all done with it now. 236 } 237 } 238 239 @Override 240 protected Transition getViewsTransition() { 241 if (mIsReturning) { 242 return getWindow().getExitTransition(); 243 } else { 244 return getWindow().getEnterTransition(); 245 } 246 } 247 248 protected Transition getSharedElementTransition() { 249 if (mIsReturning) { 250 return getWindow().getSharedElementExitTransition(); 251 } else { 252 return getWindow().getSharedElementEnterTransition(); 253 } 254 } 255 256 private void startSharedElementTransition(Bundle sharedElementState) { 257 // Remove rejected shared elements 258 ArrayList<String> rejectedNames = new ArrayList<String>(mAllSharedElementNames); 259 rejectedNames.removeAll(mSharedElementNames); 260 ArrayList<View> rejectedSnapshots = createSnapshots(sharedElementState, rejectedNames); 261 mListener.handleRejectedSharedElements(rejectedSnapshots); 262 startRejectedAnimations(rejectedSnapshots); 263 264 // Now start shared element transition 265 ArrayList<View> sharedElementSnapshots = createSnapshots(sharedElementState, 266 mSharedElementNames); 267 setViewVisibility(mSharedElements, View.VISIBLE); 268 ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>> originalImageViewState = 269 setSharedElementState(sharedElementState, sharedElementSnapshots); 270 requestLayoutForSharedElements(); 271 272 boolean startEnterTransition = allowOverlappingTransitions() && !mIsReturning; 273 boolean startSharedElementTransition = true; 274 Transition transition = beginTransition(startEnterTransition, startSharedElementTransition); 275 276 if (startEnterTransition) { 277 startEnterTransition(transition); 278 } 279 280 setOriginalImageViewState(originalImageViewState); 281 282 if (mResultReceiver != null) { 283 mResultReceiver.send(MSG_HIDE_SHARED_ELEMENTS, null); 284 } 285 mResultReceiver = null; // all done sending messages. 286 } 287 288 @Override 289 protected void stripOffscreenViews() { 290 setViewVisibility(mTransitioningViews, View.VISIBLE); 291 super.stripOffscreenViews(); 292 setViewVisibility(mTransitioningViews, View.INVISIBLE); 293 } 294 295 private void onTakeSharedElements() { 296 if (!mIsReadyForTransition || mSharedElementsBundle == null) { 297 return; 298 } 299 final Bundle sharedElementState = mSharedElementsBundle; 300 mSharedElementsBundle = null; 301 getDecor().getViewTreeObserver() 302 .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { 303 @Override 304 public boolean onPreDraw() { 305 getDecor().getViewTreeObserver().removeOnPreDrawListener(this); 306 startTransition(new Runnable() { 307 @Override 308 public void run() { 309 startSharedElementTransition(sharedElementState); 310 } 311 }); 312 return false; 313 } 314 }); 315 getDecor().invalidate(); 316 } 317 318 private void requestLayoutForSharedElements() { 319 int numSharedElements = mSharedElements.size(); 320 for (int i = 0; i < numSharedElements; i++) { 321 mSharedElements.get(i).requestLayout(); 322 } 323 } 324 325 private Transition beginTransition(boolean startEnterTransition, 326 boolean startSharedElementTransition) { 327 Transition sharedElementTransition = null; 328 if (startSharedElementTransition && !mSharedElementNames.isEmpty()) { 329 sharedElementTransition = configureTransition(getSharedElementTransition(), false); 330 } 331 Transition viewsTransition = null; 332 if (startEnterTransition && !mTransitioningViews.isEmpty()) { 333 viewsTransition = configureTransition(getViewsTransition(), true); 334 if (viewsTransition != null) { 335 stripOffscreenViews(); 336 } 337 } 338 339 Transition transition = mergeTransitions(sharedElementTransition, viewsTransition); 340 if (startSharedElementTransition) { 341 if (transition == null) { 342 sharedElementTransitionStarted(); 343 } else { 344 transition.addListener(new ContinueTransitionListener() { 345 @Override 346 public void onTransitionStart(Transition transition) { 347 super.onTransitionStart(transition); 348 transition.removeListener(this); 349 sharedElementTransitionStarted(); 350 } 351 }); 352 } 353 } 354 if (transition != null) { 355 if (sharedElementTransition == null) { 356 transition.addListener(new ContinueTransitionListener()); 357 } 358 TransitionManager.beginDelayedTransition(getDecor(), transition); 359 if (startSharedElementTransition && !mSharedElementNames.isEmpty()) { 360 mSharedElements.get(0).invalidate(); 361 } else if (startEnterTransition && !mTransitioningViews.isEmpty()) { 362 mTransitioningViews.get(0).invalidate(); 363 } 364 } else { 365 transitionStarted(); 366 } 367 return transition; 368 } 369 370 private void sharedElementTransitionStarted() { 371 mSharedElementTransitionStarted = true; 372 if (mIsExitTransitionComplete) { 373 send(MSG_EXIT_TRANSITION_COMPLETE, null); 374 } 375 } 376 377 private void startEnterTransition(Transition transition) { 378 setViewVisibility(mTransitioningViews, View.VISIBLE); 379 if (!mIsReturning) { 380 Drawable background = getDecor().getBackground(); 381 if (background != null) { 382 background = background.mutate(); 383 mBackgroundAnimator = ObjectAnimator.ofInt(background, "alpha", 255); 384 mBackgroundAnimator.setDuration(getFadeDuration()); 385 mBackgroundAnimator.addListener(new AnimatorListenerAdapter() { 386 @Override 387 public void onAnimationEnd(Animator animation) { 388 makeOpaque(); 389 } 390 }); 391 mBackgroundAnimator.start(); 392 } else if (transition != null) { 393 transition.addListener(new Transition.TransitionListenerAdapter() { 394 @Override 395 public void onTransitionEnd(Transition transition) { 396 transition.removeListener(this); 397 makeOpaque(); 398 } 399 }); 400 } else { 401 makeOpaque(); 402 } 403 } 404 } 405 406 public void stop() { 407 makeOpaque(); 408 mHasStopped = true; 409 mIsCanceled = true; 410 mResultReceiver = null; 411 if (mBackgroundAnimator != null) { 412 mBackgroundAnimator.cancel(); 413 mBackgroundAnimator = null; 414 } 415 } 416 417 private void makeOpaque() { 418 if (!mHasStopped && mActivity != null) { 419 if (mWasOpaque) { 420 mActivity.convertFromTranslucent(); 421 } 422 mActivity = null; 423 } 424 } 425 426 private boolean allowOverlappingTransitions() { 427 return mIsReturning ? getWindow().getAllowExitTransitionOverlap() 428 : getWindow().getAllowEnterTransitionOverlap(); 429 } 430 431 private void startRejectedAnimations(final ArrayList<View> rejectedSnapshots) { 432 if (rejectedSnapshots == null || rejectedSnapshots.isEmpty()) { 433 return; 434 } 435 ViewGroupOverlay overlay = getDecor().getOverlay(); 436 ObjectAnimator animator = null; 437 int numRejected = rejectedSnapshots.size(); 438 for (int i = 0; i < numRejected; i++) { 439 View snapshot = rejectedSnapshots.get(i); 440 overlay.add(snapshot); 441 animator = ObjectAnimator.ofFloat(snapshot, View.ALPHA, 1, 0); 442 animator.start(); 443 } 444 animator.addListener(new AnimatorListenerAdapter() { 445 @Override 446 public void onAnimationEnd(Animator animation) { 447 ViewGroupOverlay overlay = getDecor().getOverlay(); 448 int numRejected = rejectedSnapshots.size(); 449 for (int i = 0; i < numRejected; i++) { 450 overlay.remove(rejectedSnapshots.get(i)); 451 } 452 } 453 }); 454 } 455 456 protected void onRemoteExitTransitionComplete() { 457 if (!allowOverlappingTransitions()) { 458 startEnterTransitionOnly(); 459 } 460 } 461 462 private void startEnterTransitionOnly() { 463 startTransition(new Runnable() { 464 @Override 465 public void run() { 466 boolean startEnterTransition = true; 467 boolean startSharedElementTransition = false; 468 Transition transition = beginTransition(startEnterTransition, 469 startSharedElementTransition); 470 startEnterTransition(transition); 471 } 472 }); 473 } 474} 475