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 static org.junit.Assert.assertEquals; 20import static org.junit.Assert.assertNotNull; 21import static org.junit.Assert.assertNull; 22import static org.mockito.Matchers.any; 23import static org.mockito.Mockito.never; 24import static org.mockito.Mockito.times; 25import static org.mockito.Mockito.verify; 26 27import android.animation.Animator; 28import android.animation.AnimatorListenerAdapter; 29import android.graphics.Matrix; 30import android.graphics.drawable.Drawable; 31import android.support.test.InstrumentationRegistry; 32import android.support.test.filters.MediumTest; 33import android.util.DisplayMetrics; 34import android.util.TypedValue; 35import android.view.ViewGroup; 36import android.widget.ImageView; 37 38import androidx.annotation.NonNull; 39import androidx.core.app.ActivityCompat; 40import androidx.transition.test.R; 41 42import org.junit.Test; 43 44@MediumTest 45public class ChangeImageTransformTest extends BaseTransitionTest { 46 47 private ChangeImageTransform mChangeImageTransform; 48 private Matrix mStartMatrix; 49 private Matrix mEndMatrix; 50 private Drawable mImage; 51 private ImageView mImageView; 52 53 @Override 54 Transition createTransition() { 55 mChangeImageTransform = new CaptureMatrix(); 56 mChangeImageTransform.setDuration(100); 57 mTransition = mChangeImageTransform; 58 resetListener(); 59 return mChangeImageTransform; 60 } 61 62 @Test 63 public void testCenterToFitXY() throws Throwable { 64 transformImage(ImageView.ScaleType.CENTER, ImageView.ScaleType.FIT_XY); 65 verifyMatrixMatches(centerMatrix(), mStartMatrix); 66 verifyMatrixMatches(fitXYMatrix(), mEndMatrix); 67 } 68 69 @Test 70 public void testCenterCropToFitCenter() throws Throwable { 71 transformImage(ImageView.ScaleType.CENTER_CROP, ImageView.ScaleType.FIT_CENTER); 72 verifyMatrixMatches(centerCropMatrix(), mStartMatrix); 73 verifyMatrixMatches(fitCenterMatrix(), mEndMatrix); 74 } 75 76 @Test 77 public void testCenterInsideToFitEnd() throws Throwable { 78 transformImage(ImageView.ScaleType.CENTER_INSIDE, ImageView.ScaleType.FIT_END); 79 // CENTER_INSIDE and CENTER are the same when the image is smaller than the View 80 verifyMatrixMatches(centerMatrix(), mStartMatrix); 81 verifyMatrixMatches(fitEndMatrix(), mEndMatrix); 82 } 83 84 @Test 85 public void testFitStartToCenter() throws Throwable { 86 transformImage(ImageView.ScaleType.FIT_START, ImageView.ScaleType.CENTER); 87 verifyMatrixMatches(fitStartMatrix(), mStartMatrix); 88 verifyMatrixMatches(centerMatrix(), mEndMatrix); 89 } 90 91 private Matrix centerMatrix() { 92 int imageWidth = mImage.getIntrinsicWidth(); 93 int imageViewWidth = mImageView.getWidth(); 94 float tx = Math.round((imageViewWidth - imageWidth) / 2f); 95 96 int imageHeight = mImage.getIntrinsicHeight(); 97 int imageViewHeight = mImageView.getHeight(); 98 float ty = Math.round((imageViewHeight - imageHeight) / 2f); 99 100 Matrix matrix = new Matrix(); 101 matrix.postTranslate(tx, ty); 102 return matrix; 103 } 104 105 private Matrix fitXYMatrix() { 106 int imageWidth = mImage.getIntrinsicWidth(); 107 int imageViewWidth = mImageView.getWidth(); 108 float scaleX = ((float) imageViewWidth) / imageWidth; 109 110 int imageHeight = mImage.getIntrinsicHeight(); 111 int imageViewHeight = mImageView.getHeight(); 112 float scaleY = ((float) imageViewHeight) / imageHeight; 113 114 Matrix matrix = new Matrix(); 115 matrix.postScale(scaleX, scaleY); 116 return matrix; 117 } 118 119 private Matrix centerCropMatrix() { 120 int imageWidth = mImage.getIntrinsicWidth(); 121 int imageViewWidth = mImageView.getWidth(); 122 float scaleX = ((float) imageViewWidth) / imageWidth; 123 124 int imageHeight = mImage.getIntrinsicHeight(); 125 int imageViewHeight = mImageView.getHeight(); 126 float scaleY = ((float) imageViewHeight) / imageHeight; 127 128 float maxScale = Math.max(scaleX, scaleY); 129 130 float width = imageWidth * maxScale; 131 float height = imageHeight * maxScale; 132 int tx = Math.round((imageViewWidth - width) / 2f); 133 int ty = Math.round((imageViewHeight - height) / 2f); 134 135 Matrix matrix = new Matrix(); 136 matrix.postScale(maxScale, maxScale); 137 matrix.postTranslate(tx, ty); 138 return matrix; 139 } 140 141 private Matrix fitCenterMatrix() { 142 int imageWidth = mImage.getIntrinsicWidth(); 143 int imageViewWidth = mImageView.getWidth(); 144 float scaleX = ((float) imageViewWidth) / imageWidth; 145 146 int imageHeight = mImage.getIntrinsicHeight(); 147 int imageViewHeight = mImageView.getHeight(); 148 float scaleY = ((float) imageViewHeight) / imageHeight; 149 150 float minScale = Math.min(scaleX, scaleY); 151 152 float width = imageWidth * minScale; 153 float height = imageHeight * minScale; 154 float tx = (imageViewWidth - width) / 2f; 155 float ty = (imageViewHeight - height) / 2f; 156 157 Matrix matrix = new Matrix(); 158 matrix.postScale(minScale, minScale); 159 matrix.postTranslate(tx, ty); 160 return matrix; 161 } 162 163 private Matrix fitStartMatrix() { 164 int imageWidth = mImage.getIntrinsicWidth(); 165 int imageViewWidth = mImageView.getWidth(); 166 float scaleX = ((float) imageViewWidth) / imageWidth; 167 168 int imageHeight = mImage.getIntrinsicHeight(); 169 int imageViewHeight = mImageView.getHeight(); 170 float scaleY = ((float) imageViewHeight) / imageHeight; 171 172 float minScale = Math.min(scaleX, scaleY); 173 174 Matrix matrix = new Matrix(); 175 matrix.postScale(minScale, minScale); 176 return matrix; 177 } 178 179 private Matrix fitEndMatrix() { 180 int imageWidth = mImage.getIntrinsicWidth(); 181 int imageViewWidth = mImageView.getWidth(); 182 float scaleX = ((float) imageViewWidth) / imageWidth; 183 184 int imageHeight = mImage.getIntrinsicHeight(); 185 int imageViewHeight = mImageView.getHeight(); 186 float scaleY = ((float) imageViewHeight) / imageHeight; 187 188 float minScale = Math.min(scaleX, scaleY); 189 190 float width = imageWidth * minScale; 191 float height = imageHeight * minScale; 192 float tx = imageViewWidth - width; 193 float ty = imageViewHeight - height; 194 195 Matrix matrix = new Matrix(); 196 matrix.postScale(minScale, minScale); 197 matrix.postTranslate(tx, ty); 198 return matrix; 199 } 200 201 private void verifyMatrixMatches(Matrix expected, Matrix matrix) { 202 if (expected == null) { 203 assertNull(matrix); 204 return; 205 } 206 assertNotNull(matrix); 207 float[] expectedValues = new float[9]; 208 expected.getValues(expectedValues); 209 210 float[] values = new float[9]; 211 matrix.getValues(values); 212 213 for (int i = 0; i < values.length; i++) { 214 final float expectedValue = expectedValues[i]; 215 final float value = values[i]; 216 assertEquals("Value [" + i + "]", expectedValue, value, 0.01f); 217 } 218 } 219 220 private void transformImage(ImageView.ScaleType startScale, final ImageView.ScaleType endScale) 221 throws Throwable { 222 final ImageView imageView = enterImageViewScene(startScale); 223 rule.runOnUiThread(new Runnable() { 224 @Override 225 public void run() { 226 TransitionManager.beginDelayedTransition(mRoot, mChangeImageTransform); 227 imageView.setScaleType(endScale); 228 } 229 }); 230 waitForStart(); 231 verify(mListener, (startScale == endScale) ? times(1) : never()) 232 .onTransitionEnd(any(Transition.class)); 233 waitForEnd(); 234 } 235 236 private ImageView enterImageViewScene(final ImageView.ScaleType scaleType) throws Throwable { 237 enterScene(R.layout.scene4); 238 final ViewGroup container = (ViewGroup) rule.getActivity().findViewById(R.id.holder); 239 final ImageView[] imageViews = new ImageView[1]; 240 rule.runOnUiThread(new Runnable() { 241 @Override 242 public void run() { 243 mImageView = new ImageView(rule.getActivity()); 244 mImage = ActivityCompat.getDrawable(rule.getActivity(), 245 android.R.drawable.ic_media_play); 246 mImageView.setImageDrawable(mImage); 247 mImageView.setScaleType(scaleType); 248 imageViews[0] = mImageView; 249 container.addView(mImageView); 250 ViewGroup.LayoutParams layoutParams = mImageView.getLayoutParams(); 251 DisplayMetrics metrics = rule.getActivity().getResources().getDisplayMetrics(); 252 float size = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50, metrics); 253 layoutParams.width = Math.round(size); 254 layoutParams.height = Math.round(size * 2); 255 mImageView.setLayoutParams(layoutParams); 256 } 257 }); 258 InstrumentationRegistry.getInstrumentation().waitForIdleSync(); 259 return imageViews[0]; 260 } 261 262 private class CaptureMatrix extends ChangeImageTransform { 263 264 @Override 265 public Animator createAnimator(@NonNull ViewGroup sceneRoot, TransitionValues startValues, 266 TransitionValues endValues) { 267 Animator animator = super.createAnimator(sceneRoot, startValues, endValues); 268 assertNotNull(animator); 269 animator.addListener(new CaptureMatrixListener((ImageView) endValues.view)); 270 return animator; 271 } 272 273 } 274 275 private class CaptureMatrixListener extends AnimatorListenerAdapter { 276 277 private final ImageView mImageView; 278 279 CaptureMatrixListener(ImageView view) { 280 mImageView = view; 281 } 282 283 @Override 284 public void onAnimationStart(Animator animation) { 285 mStartMatrix = copyMatrix(); 286 } 287 288 @Override 289 public void onAnimationEnd(Animator animation) { 290 mEndMatrix = copyMatrix(); 291 } 292 293 private Matrix copyMatrix() { 294 Matrix matrix = mImageView.getImageMatrix(); 295 if (matrix != null) { 296 matrix = new Matrix(matrix); 297 } 298 return matrix; 299 } 300 301 } 302 303} 304