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