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