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