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