EnterTransitionCoordinator.java revision 3cc716c445688228311bead8641c467e91effab9
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 viewsReady(ArrayList<String> accepted, ArrayList<String> localNames) { 83 if (mIsReadyForTransition) { 84 return; 85 } 86 super.viewsReady(accepted, localNames); 87 88 mIsReadyForTransition = true; 89 if (mIsReturning) { 90 mHandler = new Handler() { 91 @Override 92 public void handleMessage(Message msg) { 93 cancel(); 94 } 95 }; 96 mHandler.sendEmptyMessageDelayed(MSG_CANCEL, MAX_WAIT_MS); 97 send(MSG_SEND_SHARED_ELEMENT_DESTINATION, null); 98 } 99 setViewVisibility(mSharedElements, View.INVISIBLE); 100 if (getViewsTransition() != null) { 101 setViewVisibility(mTransitioningViews, View.INVISIBLE); 102 } 103 if (mSharedElementsBundle != null) { 104 onTakeSharedElements(); 105 } 106 } 107 108 private void sendSharedElementDestination() { 109 ViewGroup decor = getDecor(); 110 boolean allReady = !decor.isLayoutRequested(); 111 if (allReady) { 112 for (int i = 0; i < mSharedElements.size(); i++) { 113 if (mSharedElements.get(i).isLayoutRequested()) { 114 allReady = false; 115 break; 116 } 117 } 118 } 119 if (allReady) { 120 Bundle state = captureSharedElementState(); 121 mResultReceiver.send(MSG_SHARED_ELEMENT_DESTINATION, state); 122 } else { 123 getDecor().getViewTreeObserver() 124 .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { 125 @Override 126 public boolean onPreDraw() { 127 getDecor().getViewTreeObserver().removeOnPreDrawListener(this); 128 Bundle state = captureSharedElementState(); 129 mResultReceiver.send(MSG_SHARED_ELEMENT_DESTINATION, state); 130 return true; 131 } 132 }); 133 } 134 if (allowOverlappingTransitions()) { 135 startEnterTransitionOnly(); 136 } 137 } 138 139 private static SharedElementListener getListener(Activity activity, boolean isReturning) { 140 return isReturning ? activity.mExitTransitionListener : activity.mEnterTransitionListener; 141 } 142 143 @Override 144 protected void onReceiveResult(int resultCode, Bundle resultData) { 145 switch (resultCode) { 146 case MSG_TAKE_SHARED_ELEMENTS: 147 if (!mIsCanceled) { 148 if (mHandler != null) { 149 mHandler.removeMessages(MSG_CANCEL); 150 } 151 mSharedElementsBundle = resultData; 152 onTakeSharedElements(); 153 } 154 break; 155 case MSG_EXIT_TRANSITION_COMPLETE: 156 if (!mIsCanceled) { 157 mIsExitTransitionComplete = true; 158 if (mSharedElementTransitionStarted) { 159 onRemoteExitTransitionComplete(); 160 } 161 } 162 break; 163 case MSG_CANCEL: 164 cancel(); 165 break; 166 case MSG_SEND_SHARED_ELEMENT_DESTINATION: 167 sendSharedElementDestination(); 168 break; 169 } 170 } 171 172 private void cancel() { 173 if (!mIsCanceled) { 174 mIsCanceled = true; 175 if (getViewsTransition() == null) { 176 setViewVisibility(mSharedElements, View.VISIBLE); 177 } else { 178 mTransitioningViews.addAll(mSharedElements); 179 } 180 mSharedElementNames.clear(); 181 mSharedElements.clear(); 182 mAllSharedElementNames.clear(); 183 startSharedElementTransition(null); 184 onRemoteExitTransitionComplete(); 185 } 186 } 187 188 public boolean isReturning() { 189 return mIsReturning; 190 } 191 192 protected void prepareEnter() { 193 mActivity.overridePendingTransition(0, 0); 194 if (!mIsReturning) { 195 mWasOpaque = mActivity.convertToTranslucent(null, null); 196 Drawable background = getDecor().getBackground(); 197 if (background != null) { 198 getWindow().setBackgroundDrawable(null); 199 background = background.mutate(); 200 background.setAlpha(0); 201 getWindow().setBackgroundDrawable(background); 202 } 203 } else { 204 mActivity = null; // all done with it now. 205 } 206 } 207 208 @Override 209 protected Transition getViewsTransition() { 210 if (mIsReturning) { 211 return getWindow().getExitTransition(); 212 } else { 213 return getWindow().getEnterTransition(); 214 } 215 } 216 217 protected Transition getSharedElementTransition() { 218 if (mIsReturning) { 219 return getWindow().getSharedElementExitTransition(); 220 } else { 221 return getWindow().getSharedElementEnterTransition(); 222 } 223 } 224 225 private void startSharedElementTransition(Bundle sharedElementState) { 226 setEpicenter(); 227 // Remove rejected shared elements 228 ArrayList<String> rejectedNames = new ArrayList<String>(mAllSharedElementNames); 229 rejectedNames.removeAll(mSharedElementNames); 230 ArrayList<View> rejectedSnapshots = createSnapshots(sharedElementState, rejectedNames); 231 mListener.handleRejectedSharedElements(rejectedSnapshots); 232 startRejectedAnimations(rejectedSnapshots); 233 234 // Now start shared element transition 235 ArrayList<View> sharedElementSnapshots = createSnapshots(sharedElementState, 236 mSharedElementNames); 237 setViewVisibility(mSharedElements, View.VISIBLE); 238 ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>> originalImageViewState = 239 setSharedElementState(sharedElementState, sharedElementSnapshots); 240 requestLayoutForSharedElements(); 241 242 boolean startEnterTransition = allowOverlappingTransitions() && !mIsReturning; 243 boolean startSharedElementTransition = true; 244 Transition transition = beginTransition(startEnterTransition, startSharedElementTransition); 245 246 if (startEnterTransition) { 247 startEnterTransition(transition); 248 } 249 250 setOriginalImageViewState(originalImageViewState); 251 252 if (mResultReceiver != null) { 253 mResultReceiver.send(MSG_HIDE_SHARED_ELEMENTS, null); 254 } 255 mResultReceiver = null; // all done sending messages. 256 } 257 258 private void onTakeSharedElements() { 259 if (!mIsReadyForTransition || mSharedElementsBundle == null) { 260 return; 261 } 262 final Bundle sharedElementState = mSharedElementsBundle; 263 mSharedElementsBundle = null; 264 getDecor().getViewTreeObserver() 265 .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { 266 @Override 267 public boolean onPreDraw() { 268 getDecor().getViewTreeObserver().removeOnPreDrawListener(this); 269 startTransition(new Runnable() { 270 @Override 271 public void run() { 272 startSharedElementTransition(sharedElementState); 273 } 274 }); 275 return false; 276 } 277 }); 278 getDecor().invalidate(); 279 } 280 281 private void requestLayoutForSharedElements() { 282 int numSharedElements = mSharedElements.size(); 283 for (int i = 0; i < numSharedElements; i++) { 284 mSharedElements.get(i).requestLayout(); 285 } 286 } 287 288 private Transition beginTransition(boolean startEnterTransition, 289 boolean startSharedElementTransition) { 290 Transition sharedElementTransition = null; 291 if (startSharedElementTransition && !mSharedElementNames.isEmpty()) { 292 sharedElementTransition = configureTransition(getSharedElementTransition()); 293 } 294 Transition viewsTransition = null; 295 if (startEnterTransition && !mTransitioningViews.isEmpty()) { 296 viewsTransition = configureTransition(getViewsTransition()); 297 viewsTransition = addTargets(viewsTransition, mTransitioningViews); 298 } 299 300 Transition transition = mergeTransitions(sharedElementTransition, viewsTransition); 301 if (startSharedElementTransition) { 302 if (transition == null) { 303 sharedElementTransitionStarted(); 304 } else { 305 transition.addListener(new ContinueTransitionListener() { 306 @Override 307 public void onTransitionStart(Transition transition) { 308 super.onTransitionStart(transition); 309 transition.removeListener(this); 310 sharedElementTransitionStarted(); 311 } 312 }); 313 } 314 } 315 if (transition != null) { 316 if (sharedElementTransition == null) { 317 transition.addListener(new ContinueTransitionListener()); 318 } 319 TransitionManager.beginDelayedTransition(getDecor(), transition); 320 if (startSharedElementTransition && !mSharedElementNames.isEmpty()) { 321 mSharedElements.get(0).invalidate(); 322 } else if (startEnterTransition && !mTransitioningViews.isEmpty()) { 323 mTransitioningViews.get(0).invalidate(); 324 } 325 } else { 326 transitionStarted(); 327 } 328 return transition; 329 } 330 331 private void sharedElementTransitionStarted() { 332 mSharedElementTransitionStarted = true; 333 if (mIsExitTransitionComplete) { 334 send(MSG_EXIT_TRANSITION_COMPLETE, null); 335 } 336 } 337 338 private void startEnterTransition(Transition transition) { 339 setViewVisibility(mTransitioningViews, View.VISIBLE); 340 if (!mIsReturning) { 341 Drawable background = getDecor().getBackground(); 342 if (background != null) { 343 background = background.mutate(); 344 mBackgroundAnimator = ObjectAnimator.ofInt(background, "alpha", 255); 345 mBackgroundAnimator.setDuration(getFadeDuration()); 346 mBackgroundAnimator.addListener(new AnimatorListenerAdapter() { 347 @Override 348 public void onAnimationEnd(Animator animation) { 349 makeOpaque(); 350 } 351 }); 352 mBackgroundAnimator.start(); 353 } else if (transition != null) { 354 transition.addListener(new Transition.TransitionListenerAdapter() { 355 @Override 356 public void onTransitionEnd(Transition transition) { 357 transition.removeListener(this); 358 makeOpaque(); 359 } 360 }); 361 } else { 362 makeOpaque(); 363 } 364 } 365 } 366 367 public void stop() { 368 makeOpaque(); 369 mHasStopped = true; 370 mIsCanceled = true; 371 mResultReceiver = null; 372 if (mBackgroundAnimator != null) { 373 mBackgroundAnimator.cancel(); 374 mBackgroundAnimator = null; 375 } 376 } 377 378 private void makeOpaque() { 379 if (!mHasStopped && mActivity != null) { 380 if (mWasOpaque) { 381 mActivity.convertFromTranslucent(); 382 } 383 mActivity = null; 384 } 385 } 386 387 private boolean allowOverlappingTransitions() { 388 return mIsReturning ? getWindow().getAllowExitTransitionOverlap() 389 : getWindow().getAllowEnterTransitionOverlap(); 390 } 391 392 private void startRejectedAnimations(final ArrayList<View> rejectedSnapshots) { 393 if (rejectedSnapshots == null || rejectedSnapshots.isEmpty()) { 394 return; 395 } 396 ViewGroupOverlay overlay = getDecor().getOverlay(); 397 ObjectAnimator animator = null; 398 int numRejected = rejectedSnapshots.size(); 399 for (int i = 0; i < numRejected; i++) { 400 View snapshot = rejectedSnapshots.get(i); 401 overlay.add(snapshot); 402 animator = ObjectAnimator.ofFloat(snapshot, View.ALPHA, 1, 0); 403 animator.start(); 404 } 405 animator.addListener(new AnimatorListenerAdapter() { 406 @Override 407 public void onAnimationEnd(Animator animation) { 408 ViewGroupOverlay overlay = getDecor().getOverlay(); 409 int numRejected = rejectedSnapshots.size(); 410 for (int i = 0; i < numRejected; i++) { 411 overlay.remove(rejectedSnapshots.get(i)); 412 } 413 } 414 }); 415 } 416 417 protected void onRemoteExitTransitionComplete() { 418 if (!allowOverlappingTransitions()) { 419 startEnterTransitionOnly(); 420 } 421 } 422 423 private void startEnterTransitionOnly() { 424 startTransition(new Runnable() { 425 @Override 426 public void run() { 427 setEpicenter(); 428 boolean startEnterTransition = true; 429 boolean startSharedElementTransition = false; 430 Transition transition = beginTransition(startEnterTransition, 431 startSharedElementTransition); 432 startEnterTransition(transition); 433 } 434 }); 435 } 436} 437