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 17 package androidx.transition; 18 19 import android.animation.Animator; 20 import android.animation.ObjectAnimator; 21 import android.animation.TypeEvaluator; 22 import android.content.Context; 23 import android.graphics.Matrix; 24 import android.graphics.Rect; 25 import android.graphics.drawable.Drawable; 26 import android.util.AttributeSet; 27 import android.util.Property; 28 import android.view.View; 29 import android.view.ViewGroup; 30 import android.widget.ImageView; 31 32 import androidx.annotation.NonNull; 33 34 import java.util.Map; 35 36 /** 37 * This Transition captures an ImageView's matrix before and after the 38 * scene change and animates it during the transition. 39 * 40 * <p>In combination with ChangeBounds, ChangeImageTransform allows ImageViews 41 * that change size, shape, or {@link android.widget.ImageView.ScaleType} to animate contents 42 * smoothly.</p> 43 */ 44 public class ChangeImageTransform extends Transition { 45 46 private static final String PROPNAME_MATRIX = "android:changeImageTransform:matrix"; 47 private static final String PROPNAME_BOUNDS = "android:changeImageTransform:bounds"; 48 49 private static final String[] sTransitionProperties = { 50 PROPNAME_MATRIX, 51 PROPNAME_BOUNDS, 52 }; 53 54 private static final TypeEvaluator<Matrix> NULL_MATRIX_EVALUATOR = new TypeEvaluator<Matrix>() { 55 @Override 56 public Matrix evaluate(float fraction, Matrix startValue, Matrix endValue) { 57 return null; 58 } 59 }; 60 61 private static final Property<ImageView, Matrix> ANIMATED_TRANSFORM_PROPERTY = 62 new Property<ImageView, Matrix>(Matrix.class, "animatedTransform") { 63 @Override 64 public void set(ImageView view, Matrix matrix) { 65 ImageViewUtils.animateTransform(view, matrix); 66 } 67 68 @Override 69 public Matrix get(ImageView object) { 70 return null; 71 } 72 }; 73 74 public ChangeImageTransform() { 75 } 76 77 public ChangeImageTransform(Context context, AttributeSet attrs) { 78 super(context, attrs); 79 } 80 81 private void captureValues(TransitionValues transitionValues) { 82 View view = transitionValues.view; 83 if (!(view instanceof ImageView) || view.getVisibility() != View.VISIBLE) { 84 return; 85 } 86 ImageView imageView = (ImageView) view; 87 Drawable drawable = imageView.getDrawable(); 88 if (drawable == null) { 89 return; 90 } 91 Map<String, Object> values = transitionValues.values; 92 93 int left = view.getLeft(); 94 int top = view.getTop(); 95 int right = view.getRight(); 96 int bottom = view.getBottom(); 97 98 Rect bounds = new Rect(left, top, right, bottom); 99 values.put(PROPNAME_BOUNDS, bounds); 100 values.put(PROPNAME_MATRIX, copyImageMatrix(imageView)); 101 } 102 103 @Override 104 public void captureStartValues(@NonNull TransitionValues transitionValues) { 105 captureValues(transitionValues); 106 } 107 108 @Override 109 public void captureEndValues(@NonNull TransitionValues transitionValues) { 110 captureValues(transitionValues); 111 } 112 113 @Override 114 public String[] getTransitionProperties() { 115 return sTransitionProperties; 116 } 117 118 /** 119 * Creates an Animator for ImageViews moving, changing dimensions, and/or changing 120 * {@link android.widget.ImageView.ScaleType}. 121 * 122 * @param sceneRoot The root of the transition hierarchy. 123 * @param startValues The values for a specific target in the start scene. 124 * @param endValues The values for the target in the end scene. 125 * @return An Animator to move an ImageView or null if the View is not an ImageView, 126 * the Drawable changed, the View is not VISIBLE, or there was no change. 127 */ 128 @Override 129 public Animator createAnimator(@NonNull ViewGroup sceneRoot, TransitionValues startValues, 130 final TransitionValues endValues) { 131 if (startValues == null || endValues == null) { 132 return null; 133 } 134 Rect startBounds = (Rect) startValues.values.get(PROPNAME_BOUNDS); 135 Rect endBounds = (Rect) endValues.values.get(PROPNAME_BOUNDS); 136 if (startBounds == null || endBounds == null) { 137 return null; 138 } 139 140 Matrix startMatrix = (Matrix) startValues.values.get(PROPNAME_MATRIX); 141 Matrix endMatrix = (Matrix) endValues.values.get(PROPNAME_MATRIX); 142 143 boolean matricesEqual = (startMatrix == null && endMatrix == null) 144 || (startMatrix != null && startMatrix.equals(endMatrix)); 145 146 if (startBounds.equals(endBounds) && matricesEqual) { 147 return null; 148 } 149 150 final ImageView imageView = (ImageView) endValues.view; 151 Drawable drawable = imageView.getDrawable(); 152 int drawableWidth = drawable.getIntrinsicWidth(); 153 int drawableHeight = drawable.getIntrinsicHeight(); 154 155 ImageViewUtils.startAnimateTransform(imageView); 156 157 ObjectAnimator animator; 158 if (drawableWidth == 0 || drawableHeight == 0) { 159 animator = createNullAnimator(imageView); 160 } else { 161 if (startMatrix == null) { 162 startMatrix = MatrixUtils.IDENTITY_MATRIX; 163 } 164 if (endMatrix == null) { 165 endMatrix = MatrixUtils.IDENTITY_MATRIX; 166 } 167 ANIMATED_TRANSFORM_PROPERTY.set(imageView, startMatrix); 168 animator = createMatrixAnimator(imageView, startMatrix, endMatrix); 169 } 170 171 ImageViewUtils.reserveEndAnimateTransform(imageView, animator); 172 173 return animator; 174 } 175 176 private ObjectAnimator createNullAnimator(ImageView imageView) { 177 return ObjectAnimator.ofObject(imageView, ANIMATED_TRANSFORM_PROPERTY, 178 NULL_MATRIX_EVALUATOR, null, null); 179 } 180 181 private ObjectAnimator createMatrixAnimator(final ImageView imageView, Matrix startMatrix, 182 final Matrix endMatrix) { 183 return ObjectAnimator.ofObject(imageView, ANIMATED_TRANSFORM_PROPERTY, 184 new TransitionUtils.MatrixEvaluator(), startMatrix, endMatrix); 185 } 186 187 private static Matrix copyImageMatrix(ImageView view) { 188 switch (view.getScaleType()) { 189 case FIT_XY: 190 return fitXYMatrix(view); 191 case CENTER_CROP: 192 return centerCropMatrix(view); 193 default: 194 return new Matrix(view.getImageMatrix()); 195 } 196 } 197 198 /** 199 * Calculates the image transformation matrix for an ImageView with ScaleType FIT_XY. This 200 * needs to be manually calculated as the platform does not give us the value for this case. 201 */ 202 private static Matrix fitXYMatrix(ImageView view) { 203 final Drawable image = view.getDrawable(); 204 final Matrix matrix = new Matrix(); 205 matrix.postScale( 206 ((float) view.getWidth()) / image.getIntrinsicWidth(), 207 ((float) view.getHeight()) / image.getIntrinsicHeight()); 208 return matrix; 209 } 210 211 /** 212 * Calculates the image transformation matrix for an ImageView with ScaleType CENTER_CROP. This 213 * needs to be manually calculated for consistent behavior across all the API levels. 214 */ 215 private static Matrix centerCropMatrix(ImageView view) { 216 final Drawable image = view.getDrawable(); 217 final int imageWidth = image.getIntrinsicWidth(); 218 final int imageViewWidth = view.getWidth(); 219 final float scaleX = ((float) imageViewWidth) / imageWidth; 220 221 final int imageHeight = image.getIntrinsicHeight(); 222 final int imageViewHeight = view.getHeight(); 223 final float scaleY = ((float) imageViewHeight) / imageHeight; 224 225 final float maxScale = Math.max(scaleX, scaleY); 226 227 final float width = imageWidth * maxScale; 228 final float height = imageHeight * maxScale; 229 final int tx = Math.round((imageViewWidth - width) / 2f); 230 final int ty = Math.round((imageViewHeight - height) / 2f); 231 232 final Matrix matrix = new Matrix(); 233 matrix.postScale(maxScale, maxScale); 234 matrix.postTranslate(tx, ty); 235 return matrix; 236 } 237 238 } 239