1/* 2 * Copyright (C) 2017 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 17package androidx.transition; 18 19import android.animation.Animator; 20import android.animation.AnimatorSet; 21import android.animation.TypeEvaluator; 22import android.graphics.Bitmap; 23import android.graphics.Canvas; 24import android.graphics.Matrix; 25import android.graphics.Picture; 26import android.graphics.RectF; 27import android.os.Build; 28import android.view.View; 29import android.view.ViewGroup; 30import android.widget.ImageView; 31 32class TransitionUtils { 33 34 private static final int MAX_IMAGE_SIZE = 1024 * 1024; 35 private static final boolean HAS_IS_ATTACHED_TO_WINDOW = 36 Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; 37 private static final boolean HAS_OVERLAY = 38 Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2; 39 private static final boolean HAS_PICTURE_BITMAP = 40 Build.VERSION.SDK_INT >= Build.VERSION_CODES.P; 41 42 /** 43 * Creates a View using the bitmap copy of <code>view</code>. If <code>view</code> is large, 44 * the copy will use a scaled bitmap of the given view. 45 * 46 * @param sceneRoot The ViewGroup in which the view copy will be displayed. 47 * @param view The view to create a copy of. 48 * @param parent The parent of view. 49 */ 50 static View copyViewImage(ViewGroup sceneRoot, View view, View parent) { 51 Matrix matrix = new Matrix(); 52 matrix.setTranslate(-parent.getScrollX(), -parent.getScrollY()); 53 ViewUtils.transformMatrixToGlobal(view, matrix); 54 ViewUtils.transformMatrixToLocal(sceneRoot, matrix); 55 RectF bounds = new RectF(0, 0, view.getWidth(), view.getHeight()); 56 matrix.mapRect(bounds); 57 int left = Math.round(bounds.left); 58 int top = Math.round(bounds.top); 59 int right = Math.round(bounds.right); 60 int bottom = Math.round(bounds.bottom); 61 62 ImageView copy = new ImageView(view.getContext()); 63 copy.setScaleType(ImageView.ScaleType.CENTER_CROP); 64 Bitmap bitmap = createViewBitmap(view, matrix, bounds, sceneRoot); 65 if (bitmap != null) { 66 copy.setImageBitmap(bitmap); 67 } 68 int widthSpec = View.MeasureSpec.makeMeasureSpec(right - left, View.MeasureSpec.EXACTLY); 69 int heightSpec = View.MeasureSpec.makeMeasureSpec(bottom - top, View.MeasureSpec.EXACTLY); 70 copy.measure(widthSpec, heightSpec); 71 copy.layout(left, top, right, bottom); 72 return copy; 73 } 74 75 /** 76 * Creates a Bitmap of the given view, using the Matrix matrix to transform to the local 77 * coordinates. <code>matrix</code> will be modified during the bitmap creation. 78 * 79 * <p>If the bitmap is large, it will be scaled uniformly down to at most 1MB size.</p> 80 * 81 * @param view The view to create a bitmap for. 82 * @param matrix The matrix converting the view local coordinates to the coordinates that 83 * the bitmap will be displayed in. <code>matrix</code> will be modified before 84 * returning. 85 * @param bounds The bounds of the bitmap in the destination coordinate system (where the 86 * view should be presented. Typically, this is matrix.mapRect(viewBounds); 87 * @return A bitmap of the given view or null if bounds has no width or height. 88 */ 89 private static Bitmap createViewBitmap(View view, Matrix matrix, RectF bounds, 90 ViewGroup sceneRoot) { 91 final boolean addToOverlay; 92 final boolean sceneRootIsAttached; 93 if (HAS_IS_ATTACHED_TO_WINDOW) { 94 addToOverlay = !view.isAttachedToWindow(); 95 sceneRootIsAttached = sceneRoot == null ? false : sceneRoot.isAttachedToWindow(); 96 } else { 97 addToOverlay = false; 98 sceneRootIsAttached = false; 99 } 100 ViewGroup parent = null; 101 int indexInParent = 0; 102 if (HAS_OVERLAY && addToOverlay) { 103 if (!sceneRootIsAttached) { 104 return null; 105 } 106 parent = (ViewGroup) view.getParent(); 107 indexInParent = parent.indexOfChild(view); 108 sceneRoot.getOverlay().add(view); 109 } 110 Bitmap bitmap = null; 111 int bitmapWidth = Math.round(bounds.width()); 112 int bitmapHeight = Math.round(bounds.height()); 113 if (bitmapWidth > 0 && bitmapHeight > 0) { 114 float scale = Math.min(1f, ((float) MAX_IMAGE_SIZE) / (bitmapWidth * bitmapHeight)); 115 bitmapWidth = Math.round(bitmapWidth * scale); 116 bitmapHeight = Math.round(bitmapHeight * scale); 117 matrix.postTranslate(-bounds.left, -bounds.top); 118 matrix.postScale(scale, scale); 119 120 if (HAS_PICTURE_BITMAP) { 121 // Hardware rendering 122 final Picture picture = new Picture(); 123 final Canvas canvas = picture.beginRecording(bitmapWidth, bitmapHeight); 124 canvas.concat(matrix); 125 view.draw(canvas); 126 picture.endRecording(); 127 bitmap = Bitmap.createBitmap(picture); 128 } else { 129 // Software rendering 130 bitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888); 131 Canvas canvas = new Canvas(bitmap); 132 canvas.concat(matrix); 133 view.draw(canvas); 134 } 135 } 136 if (HAS_OVERLAY && addToOverlay) { 137 sceneRoot.getOverlay().remove(view); 138 parent.addView(view, indexInParent); 139 } 140 return bitmap; 141 } 142 143 static Animator mergeAnimators(Animator animator1, Animator animator2) { 144 if (animator1 == null) { 145 return animator2; 146 } else if (animator2 == null) { 147 return animator1; 148 } else { 149 AnimatorSet animatorSet = new AnimatorSet(); 150 animatorSet.playTogether(animator1, animator2); 151 return animatorSet; 152 } 153 } 154 155 static class MatrixEvaluator implements TypeEvaluator<Matrix> { 156 157 final float[] mTempStartValues = new float[9]; 158 159 final float[] mTempEndValues = new float[9]; 160 161 final Matrix mTempMatrix = new Matrix(); 162 163 @Override 164 public Matrix evaluate(float fraction, Matrix startValue, Matrix endValue) { 165 startValue.getValues(mTempStartValues); 166 endValue.getValues(mTempEndValues); 167 for (int i = 0; i < 9; i++) { 168 float diff = mTempEndValues[i] - mTempStartValues[i]; 169 mTempEndValues[i] = mTempStartValues[i] + (fraction * diff); 170 } 171 mTempMatrix.setValues(mTempEndValues); 172 return mTempMatrix; 173 } 174 175 } 176 177 private TransitionUtils() { 178 } 179} 180