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