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.support.v4.app; 17 18import android.content.Context; 19import android.graphics.Bitmap; 20import android.graphics.Canvas; 21import android.graphics.Matrix; 22import android.graphics.Rect; 23import android.graphics.RectF; 24import android.graphics.drawable.BitmapDrawable; 25import android.graphics.drawable.Drawable; 26import android.os.Bundle; 27import android.os.Parcelable; 28import android.view.View; 29import android.widget.ImageView; 30import android.widget.ImageView.ScaleType; 31 32import java.util.List; 33import java.util.Map; 34 35/** 36 * Listener provided in 37 * {@link FragmentActivity#setEnterSharedElementCallback(SharedElementCallback)} and 38 * {@link FragmentActivity#setExitSharedElementCallback(SharedElementCallback)} 39 * to monitor the Activity transitions. The events can be used to customize Activity 40 * Transition behavior. 41 */ 42public abstract class SharedElementCallback { 43 private Matrix mTempMatrix; 44 private static int MAX_IMAGE_SIZE = (1024 * 1024); 45 private static final String BUNDLE_SNAPSHOT_BITMAP = "sharedElement:snapshot:bitmap"; 46 private static final String BUNDLE_SNAPSHOT_IMAGE_SCALETYPE = "sharedElement:snapshot:imageScaleType"; 47 private static final String BUNDLE_SNAPSHOT_IMAGE_MATRIX = "sharedElement:snapshot:imageMatrix"; 48 49 /** 50 * In Activity Transitions, onSharedElementStart is called immediately before 51 * capturing the start of the shared element state on enter and reenter transitions and 52 * immediately before capturing the end of the shared element state for exit and return 53 * transitions. 54 * <p> 55 * In Fragment Transitions, onSharedElementStart is called immediately before capturing the 56 * start state of all shared element transitions. 57 * <p> 58 * This call can be used to adjust the transition start state by modifying the shared 59 * element Views. Note that no layout step will be executed between onSharedElementStart 60 * and the transition state capture. 61 * <p> 62 * For Activity Transitions, any changes made in {@link #onSharedElementEnd(List, List, List)} 63 * that are not updated during layout should be corrected in onSharedElementStart for exit and 64 * return transitions. For example, rotation or scale will not be affected by layout and 65 * if changed in {@link #onSharedElementEnd(List, List, List)}, it will also have to be reset 66 * in onSharedElementStart again to correct the end state. 67 * 68 * @param sharedElementNames The names of the shared elements that were accepted into 69 * the View hierarchy. 70 * @param sharedElements The shared elements that are part of the View hierarchy. 71 * @param sharedElementSnapshots The Views containing snap shots of the shared element 72 * from the launching Window. These elements will not 73 * be part of the scene, but will be positioned relative 74 * to the Window decor View. This list is null for Fragment 75 * Transitions. 76 */ 77 public void onSharedElementStart(List<String> sharedElementNames, 78 List<View> sharedElements, List<View> sharedElementSnapshots) {} 79 80 /** 81 * In Activity Transitions, onSharedElementEnd is called immediately before 82 * capturing the end of the shared element state on enter and reenter transitions and 83 * immediately before capturing the start of the shared element state for exit and return 84 * transitions. 85 * <p> 86 * In Fragment Transitions, onSharedElementEnd is called immediately before capturing the 87 * end state of all shared element transitions. 88 * <p> 89 * This call can be used to adjust the transition end state by modifying the shared 90 * element Views. Note that no layout step will be executed between onSharedElementEnd 91 * and the transition state capture. 92 * <p> 93 * Any changes made in {@link #onSharedElementStart(List, List, List)} that are not updated 94 * during layout should be corrected in onSharedElementEnd. For example, rotation or scale 95 * will not be affected by layout and if changed in 96 * {@link #onSharedElementStart(List, List, List)}, it will also have to be reset in 97 * onSharedElementEnd again to correct the end state. 98 * 99 * @param sharedElementNames The names of the shared elements that were accepted into 100 * the View hierarchy. 101 * @param sharedElements The shared elements that are part of the View hierarchy. 102 * @param sharedElementSnapshots The Views containing snap shots of the shared element 103 * from the launching Window. These elements will not 104 * be part of the scene, but will be positioned relative 105 * to the Window decor View. This list will be null for 106 * Fragment Transitions. 107 */ 108 public void onSharedElementEnd(List<String> sharedElementNames, 109 List<View> sharedElements, List<View> sharedElementSnapshots) {} 110 111 /** 112 * Called after {@link #onMapSharedElements(java.util.List, java.util.Map)} when 113 * transferring shared elements in. Any shared elements that have no mapping will be in 114 * <var>rejectedSharedElements</var>. The elements remaining in 115 * <var>rejectedSharedElements</var> will be transitioned out of the Scene. If a 116 * View is removed from <var>rejectedSharedElements</var>, it must be handled by the 117 * <code>SharedElementListener</code>. 118 * <p> 119 * Views in rejectedSharedElements will have their position and size set to the 120 * position of the calling shared element, relative to the Window decor View and contain 121 * snapshots of the View from the calling Activity or Fragment. This 122 * view may be safely added to the decor View's overlay to remain in position. 123 * </p> 124 * <p>This method is not called for Fragment Transitions. All rejected shared elements 125 * will be handled by the exit transition.</p> 126 * 127 * @param rejectedSharedElements Views containing visual information of shared elements 128 * that are not part of the entering scene. These Views 129 * are positioned relative to the Window decor View. A 130 * View removed from this list will not be transitioned 131 * automatically. 132 */ 133 public void onRejectSharedElements(List<View> rejectedSharedElements) {} 134 135 /** 136 * Lets the SharedElementCallback adjust the mapping of shared element names to 137 * Views. 138 * 139 * @param names The names of all shared elements transferred from the calling Activity 140 * or Fragment in the order they were provided. 141 * @param sharedElements The mapping of shared element names to Views. The best guess 142 * will be filled into sharedElements based on the transitionNames. 143 */ 144 public void onMapSharedElements(List<String> names, Map<String, View> sharedElements) {} 145 146 147 /** 148 * Creates a snapshot of a shared element to be used by the remote Activity and reconstituted 149 * with {@link #onCreateSnapshotView(android.content.Context, android.os.Parcelable)}. A 150 * null return value will mean that the remote Activity will have a null snapshot View in 151 * {@link #onSharedElementStart(java.util.List, java.util.List, java.util.List)} and 152 * {@link #onSharedElementEnd(java.util.List, java.util.List, java.util.List)}. 153 * 154 * <p>This is not called for Fragment Transitions.</p> 155 * 156 * @param sharedElement The shared element View to create a snapshot for. 157 * @param viewToGlobalMatrix A matrix containing a transform from the view to the screen 158 * coordinates. 159 * @param screenBounds The bounds of shared element in screen coordinate space. This is 160 * the bounds of the view with the viewToGlobalMatrix applied. 161 * @return A snapshot to send to the remote Activity to be reconstituted with 162 * {@link #onCreateSnapshotView(android.content.Context, android.os.Parcelable)} and passed 163 * into {@link #onSharedElementStart(java.util.List, java.util.List, java.util.List)} and 164 * {@link #onSharedElementEnd(java.util.List, java.util.List, java.util.List)}. 165 */ 166 public Parcelable onCaptureSharedElementSnapshot(View sharedElement, Matrix viewToGlobalMatrix, 167 RectF screenBounds) { 168 if (sharedElement instanceof ImageView) { 169 ImageView imageView = ((ImageView) sharedElement); 170 Drawable d = imageView.getDrawable(); 171 Drawable bg = imageView.getBackground(); 172 if (d != null && bg == null) { 173 Bitmap bitmap = createDrawableBitmap(d); 174 if (bitmap != null) { 175 Bundle bundle = new Bundle(); 176 bundle.putParcelable(BUNDLE_SNAPSHOT_BITMAP, bitmap); 177 bundle.putString(BUNDLE_SNAPSHOT_IMAGE_SCALETYPE, 178 imageView.getScaleType().toString()); 179 if (imageView.getScaleType() == ScaleType.MATRIX) { 180 Matrix matrix = imageView.getImageMatrix(); 181 float[] values = new float[9]; 182 matrix.getValues(values); 183 bundle.putFloatArray(BUNDLE_SNAPSHOT_IMAGE_MATRIX, values); 184 } 185 return bundle; 186 } 187 } 188 } 189 int bitmapWidth = Math.round(screenBounds.width()); 190 int bitmapHeight = Math.round(screenBounds.height()); 191 Bitmap bitmap = null; 192 if (bitmapWidth > 0 && bitmapHeight > 0) { 193 float scale = Math.min(1f, ((float) MAX_IMAGE_SIZE) / (bitmapWidth * bitmapHeight)); 194 bitmapWidth = (int) (bitmapWidth * scale); 195 bitmapHeight = (int) (bitmapHeight * scale); 196 if (mTempMatrix == null) { 197 mTempMatrix = new Matrix(); 198 } 199 mTempMatrix.set(viewToGlobalMatrix); 200 mTempMatrix.postTranslate(-screenBounds.left, -screenBounds.top); 201 mTempMatrix.postScale(scale, scale); 202 bitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888); 203 Canvas canvas = new Canvas(bitmap); 204 canvas.concat(mTempMatrix); 205 sharedElement.draw(canvas); 206 } 207 return bitmap; 208 } 209 210 /** 211 * Get a copy of bitmap of given drawable. 212 */ 213 private static Bitmap createDrawableBitmap(Drawable drawable) { 214 int width = drawable.getIntrinsicWidth(); 215 int height = drawable.getIntrinsicHeight(); 216 if (width <= 0 || height <= 0) { 217 return null; 218 } 219 float scale = Math.min(1f, ((float)MAX_IMAGE_SIZE) / (width * height)); 220 if (drawable instanceof BitmapDrawable && scale == 1f) { 221 // return same bitmap if scale down not needed 222 return ((BitmapDrawable) drawable).getBitmap(); 223 } 224 int bitmapWidth = (int) (width * scale); 225 int bitmapHeight = (int) (height * scale); 226 Bitmap bitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888); 227 Canvas canvas = new Canvas(bitmap); 228 Rect existingBounds = drawable.getBounds(); 229 int left = existingBounds.left; 230 int top = existingBounds.top; 231 int right = existingBounds.right; 232 int bottom = existingBounds.bottom; 233 drawable.setBounds(0, 0, bitmapWidth, bitmapHeight); 234 drawable.draw(canvas); 235 drawable.setBounds(left, top, right, bottom); 236 return bitmap; 237 } 238 239 /** 240 * Reconstitutes a snapshot View from a Parcelable returned in 241 * {@link #onCaptureSharedElementSnapshot(android.view.View, android.graphics.Matrix, 242 * android.graphics.RectF)} to be used in {@link #onSharedElementStart(java.util.List, 243 * java.util.List, java.util.List)} and {@link #onSharedElementEnd(java.util.List, 244 * java.util.List, java.util.List)}. The returned View will be sized and positioned after 245 * this call so that it is ready to be added to the decor View's overlay. 246 * 247 * <p>This is not called for Fragment Transitions.</p> 248 * 249 * @param context The Context used to create the snapshot View. 250 * @param snapshot The Parcelable returned by {@link #onCaptureSharedElementSnapshot( 251 * android.view.View, android.graphics.Matrix, android.graphics.RectF)}. 252 * @return A View to be sent in {@link #onSharedElementStart(java.util.List, java.util.List, 253 * java.util.List)} and {@link #onSharedElementEnd(java.util.List, java.util.List, 254 * java.util.List)}. A null value will produce a null snapshot value for those two methods. 255 */ 256 public View onCreateSnapshotView(Context context, Parcelable snapshot) { 257 ImageView view = null; 258 if (snapshot instanceof Bundle) { 259 Bundle bundle = (Bundle) snapshot; 260 Bitmap bitmap = (Bitmap) bundle.getParcelable(BUNDLE_SNAPSHOT_BITMAP); 261 if (bitmap == null) { 262 return null; 263 } 264 ImageView imageView = new ImageView(context); 265 view = imageView; 266 imageView.setImageBitmap(bitmap); 267 imageView.setScaleType( 268 ScaleType.valueOf(bundle.getString(BUNDLE_SNAPSHOT_IMAGE_SCALETYPE))); 269 if (imageView.getScaleType() == ScaleType.MATRIX) { 270 float[] values = bundle.getFloatArray(BUNDLE_SNAPSHOT_IMAGE_MATRIX); 271 Matrix matrix = new Matrix(); 272 matrix.setValues(values); 273 imageView.setImageMatrix(matrix); 274 } 275 } else if (snapshot instanceof Bitmap) { 276 Bitmap bitmap = (Bitmap) snapshot; 277 view = new ImageView(context); 278 view.setImageBitmap(bitmap); 279 } 280 return view; 281 } 282 283 /** 284 * Called during an Activity Transition when the shared elements have arrived at the 285 * final location and are ready to be transferred. This method is called for both the 286 * source and destination Activities. 287 * <p> 288 * When the shared elements are ready to be transferred, 289 * {@link OnSharedElementsReadyListener#onSharedElementsReady()} 290 * must be called to trigger the transfer. 291 * <p> 292 * The default behavior is to trigger the transfer immediately. 293 * 294 * @param sharedElementNames The names of the shared elements that are being transferred.. 295 * @param sharedElements The shared elements that are part of the View hierarchy. 296 * @param listener The listener to call when the shared elements are ready to be hidden 297 * in the source Activity or shown in the destination Activity. 298 */ 299 public void onSharedElementsArrived(List<String> sharedElementNames, 300 List<View> sharedElements, OnSharedElementsReadyListener listener) { 301 listener.onSharedElementsReady(); 302 } 303 304 /** 305 * Listener to be called after {@link 306 * SharedElementCallback#onSharedElementsArrived(List, List, OnSharedElementsReadyListener)} 307 * when the shared elements are ready to be hidden in the source Activity and shown in the 308 * destination Activity. 309 */ 310 public interface OnSharedElementsReadyListener { 311 312 /** 313 * Call this method during or after the OnSharedElementsReadyListener has been received 314 * in {@link SharedElementCallback#onSharedElementsArrived(List, List, 315 * OnSharedElementsReadyListener)} to indicate that the shared elements are ready to be 316 * hidden in the source and shown in the destination Activity. 317 */ 318 void onSharedElementsReady(); 319 } 320} 321