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