ExitTransitionCoordinator.java revision d80154fba1c23834b5139aa764f9426bbf38a721
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.Intent; 22import android.graphics.Bitmap; 23import android.graphics.Canvas; 24import android.graphics.drawable.ColorDrawable; 25import android.graphics.drawable.Drawable; 26import android.os.Bundle; 27import android.os.Handler; 28import android.os.Message; 29import android.transition.Transition; 30import android.transition.TransitionManager; 31import android.view.View; 32import android.widget.ImageView; 33 34import java.util.ArrayList; 35 36/** 37 * This ActivityTransitionCoordinator is created in ActivityOptions#makeSceneTransitionAnimation 38 * to govern the exit of the Scene and the shared elements when calling an Activity as well as 39 * the reentry of the Scene when coming back from the called Activity. 40 */ 41class ExitTransitionCoordinator extends ActivityTransitionCoordinator { 42 private static final String TAG = "ExitTransitionCoordinator"; 43 private static final long MAX_WAIT_MS = 1000; 44 45 private boolean mExitComplete; 46 47 private Bundle mSharedElementBundle; 48 49 private boolean mExitNotified; 50 51 private boolean mSharedElementNotified; 52 53 private Activity mActivity; 54 55 private boolean mIsBackgroundReady; 56 57 private boolean mIsCanceled; 58 59 private Handler mHandler; 60 61 private boolean mIsReturning; 62 63 private ObjectAnimator mBackgroundAnimator; 64 65 private boolean mIsHidden; 66 67 public ExitTransitionCoordinator(Activity activity, ArrayList<String> names, 68 ArrayList<String> accepted, ArrayList<String> mapped, boolean isReturning) { 69 super(activity.getWindow(), names, accepted, mapped, getListener(activity, isReturning)); 70 mIsReturning = isReturning; 71 mIsBackgroundReady = !isReturning; 72 mActivity = activity; 73 } 74 75 private static SharedElementListener getListener(Activity activity, boolean isReturning) { 76 return isReturning ? activity.mEnterTransitionListener : activity.mExitTransitionListener; 77 } 78 79 @Override 80 protected void onReceiveResult(int resultCode, Bundle resultData) { 81 switch (resultCode) { 82 case MSG_SET_REMOTE_RECEIVER: 83 mResultReceiver = resultData.getParcelable(KEY_REMOTE_RECEIVER); 84 if (mIsCanceled) { 85 mResultReceiver.send(MSG_CANCEL, null); 86 mResultReceiver = null; 87 } else { 88 if (mHandler != null) { 89 mHandler.removeMessages(MSG_CANCEL); 90 } 91 notifyComplete(); 92 } 93 break; 94 case MSG_HIDE_SHARED_ELEMENTS: 95 if (!mIsCanceled) { 96 hideSharedElements(); 97 } 98 break; 99 case MSG_START_EXIT_TRANSITION: 100 startExit(); 101 break; 102 case MSG_ACTIVITY_STOPPED: 103 setViewVisibility(mTransitioningViews, View.VISIBLE); 104 setViewVisibility(mSharedElements, View.VISIBLE); 105 mIsHidden = true; 106 break; 107 } 108 } 109 110 private void hideSharedElements() { 111 setViewVisibility(mSharedElements, View.INVISIBLE); 112 } 113 114 public void startExit() { 115 beginTransition(); 116 setViewVisibility(mTransitioningViews, View.INVISIBLE); 117 } 118 119 public void startExit(int resultCode, Intent data) { 120 mHandler = new Handler() { 121 @Override 122 public void handleMessage(Message msg) { 123 mIsCanceled = true; 124 mActivity.finish(); 125 mActivity = null; 126 } 127 }; 128 mHandler.sendEmptyMessageDelayed(MSG_CANCEL, MAX_WAIT_MS); 129 if (getDecor().getBackground() == null) { 130 ColorDrawable black = new ColorDrawable(0xFF000000); 131 black.setAlpha(0); 132 getWindow().setBackgroundDrawable(black); 133 black.setAlpha(255); 134 } 135 ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(mActivity, this, 136 mAllSharedElementNames, resultCode, data); 137 mActivity.convertToTranslucent(new Activity.TranslucentConversionListener() { 138 @Override 139 public void onTranslucentConversionComplete(boolean drawComplete) { 140 if (!mIsCanceled) { 141 fadeOutBackground(); 142 } 143 } 144 }, options); 145 startExit(); 146 } 147 148 private void fadeOutBackground() { 149 if (mBackgroundAnimator == null) { 150 Drawable background = getDecor().getBackground(); 151 mBackgroundAnimator = ObjectAnimator.ofInt(background, "alpha", 0); 152 mBackgroundAnimator.addListener(new AnimatorListenerAdapter() { 153 @Override 154 public void onAnimationEnd(Animator animation) { 155 mBackgroundAnimator = null; 156 if (!mIsCanceled) { 157 mIsBackgroundReady = true; 158 notifyComplete(); 159 } 160 } 161 }); 162 mBackgroundAnimator.setDuration(FADE_BACKGROUND_DURATION_MS); 163 mBackgroundAnimator.start(); 164 } 165 } 166 167 private void beginTransition() { 168 Transition sharedElementTransition = configureTransition(getSharedElementTransition()); 169 Transition viewsTransition = configureTransition(getViewsTransition()); 170 viewsTransition = addTargets(viewsTransition, mTransitioningViews); 171 if (sharedElementTransition == null || mSharedElements.isEmpty()) { 172 sharedElementTransitionComplete(); 173 sharedElementTransition = null; 174 } else { 175 sharedElementTransition.addListener(new Transition.TransitionListenerAdapter() { 176 @Override 177 public void onTransitionEnd(Transition transition) { 178 sharedElementTransitionComplete(); 179 } 180 }); 181 } 182 if (viewsTransition == null || mTransitioningViews.isEmpty()) { 183 exitTransitionComplete(); 184 viewsTransition = null; 185 } else { 186 viewsTransition.addListener(new Transition.TransitionListenerAdapter() { 187 @Override 188 public void onTransitionEnd(Transition transition) { 189 exitTransitionComplete(); 190 if (mIsHidden) { 191 setViewVisibility(mTransitioningViews, View.VISIBLE); 192 } 193 } 194 }); 195 } 196 197 Transition transition = mergeTransitions(sharedElementTransition, viewsTransition); 198 TransitionManager.beginDelayedTransition(getDecor(), transition); 199 if (viewsTransition == null && sharedElementTransition != null) { 200 mSharedElements.get(0).requestLayout(); 201 } 202 } 203 204 private void exitTransitionComplete() { 205 mExitComplete = true; 206 notifyComplete(); 207 } 208 209 protected boolean isReadyToNotify() { 210 return mSharedElementBundle != null && mResultReceiver != null && mIsBackgroundReady; 211 } 212 213 private void sharedElementTransitionComplete() { 214 Bundle bundle = new Bundle(); 215 int[] tempLoc = new int[2]; 216 for (int i = 0; i < mSharedElementNames.size(); i++) { 217 View sharedElement = mSharedElements.get(i); 218 String name = mSharedElementNames.get(i); 219 captureSharedElementState(sharedElement, name, bundle, tempLoc); 220 } 221 mSharedElementBundle = bundle; 222 notifyComplete(); 223 } 224 225 protected void notifyComplete() { 226 if (isReadyToNotify()) { 227 if (!mSharedElementNotified) { 228 mSharedElementNotified = true; 229 mResultReceiver.send(MSG_TAKE_SHARED_ELEMENTS, mSharedElementBundle); 230 } 231 if (!mExitNotified && mExitComplete) { 232 mExitNotified = true; 233 mResultReceiver.send(MSG_EXIT_TRANSITION_COMPLETE, null); 234 mResultReceiver = null; // done talking 235 if (mIsReturning) { 236 mActivity.finish(); 237 mActivity.overridePendingTransition(0, 0); 238 } 239 mActivity = null; 240 } 241 } 242 } 243 244 @Override 245 protected Transition getViewsTransition() { 246 if (mIsReturning) { 247 return getWindow().getEnterTransition(); 248 } else { 249 return getWindow().getExitTransition(); 250 } 251 } 252 253 protected Transition getSharedElementTransition() { 254 if (mIsReturning) { 255 return getWindow().getSharedElementEnterTransition(); 256 } else { 257 return getWindow().getSharedElementExitTransition(); 258 } 259 } 260 261 /** 262 * Captures placement information for Views with a shared element name for 263 * Activity Transitions. 264 * 265 * @param view The View to capture the placement information for. 266 * @param name The shared element name in the target Activity to apply the placement 267 * information for. 268 * @param transitionArgs Bundle to store shared element placement information. 269 * @param tempLoc A temporary int[2] for capturing the current location of views. 270 */ 271 private static void captureSharedElementState(View view, String name, Bundle transitionArgs, 272 int[] tempLoc) { 273 Bundle sharedElementBundle = new Bundle(); 274 view.getLocationOnScreen(tempLoc); 275 float scaleX = view.getScaleX(); 276 sharedElementBundle.putInt(KEY_SCREEN_X, tempLoc[0]); 277 int width = Math.round(view.getWidth() * scaleX); 278 sharedElementBundle.putInt(KEY_WIDTH, width); 279 280 float scaleY = view.getScaleY(); 281 sharedElementBundle.putInt(KEY_SCREEN_Y, tempLoc[1]); 282 int height = Math.round(view.getHeight() * scaleY); 283 sharedElementBundle.putInt(KEY_HEIGHT, height); 284 285 sharedElementBundle.putFloat(KEY_TRANSLATION_Z, view.getTranslationZ()); 286 287 Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 288 Canvas canvas = new Canvas(bitmap); 289 view.draw(canvas); 290 sharedElementBundle.putParcelable(KEY_BITMAP, bitmap); 291 292 if (view instanceof ImageView) { 293 ImageView imageView = (ImageView) view; 294 int scaleTypeInt = scaleTypeToInt(imageView.getScaleType()); 295 sharedElementBundle.putInt(KEY_SCALE_TYPE, scaleTypeInt); 296 if (imageView.getScaleType() == ImageView.ScaleType.MATRIX) { 297 float[] matrix = new float[9]; 298 imageView.getImageMatrix().getValues(matrix); 299 sharedElementBundle.putFloatArray(KEY_IMAGE_MATRIX, matrix); 300 } 301 } 302 303 transitionArgs.putBundle(name, sharedElementBundle); 304 } 305 306 private static int scaleTypeToInt(ImageView.ScaleType scaleType) { 307 for (int i = 0; i < SCALE_TYPE_VALUES.length; i++) { 308 if (scaleType == SCALE_TYPE_VALUES[i]) { 309 return i; 310 } 311 } 312 return -1; 313 } 314} 315