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