/* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.support.transition; import android.animation.Animator; import android.animation.ObjectAnimator; import android.animation.TypeEvaluator; import android.content.Context; import android.graphics.Matrix; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.support.annotation.NonNull; import android.util.AttributeSet; import android.util.Property; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import java.util.Map; /** * This Transition captures an ImageView's matrix before and after the * scene change and animates it during the transition. * *

In combination with ChangeBounds, ChangeImageTransform allows ImageViews * that change size, shape, or {@link android.widget.ImageView.ScaleType} to animate contents * smoothly.

*/ public class ChangeImageTransform extends Transition { private static final String PROPNAME_MATRIX = "android:changeImageTransform:matrix"; private static final String PROPNAME_BOUNDS = "android:changeImageTransform:bounds"; private static final String[] sTransitionProperties = { PROPNAME_MATRIX, PROPNAME_BOUNDS, }; private static final TypeEvaluator NULL_MATRIX_EVALUATOR = new TypeEvaluator() { @Override public Matrix evaluate(float fraction, Matrix startValue, Matrix endValue) { return null; } }; private static final Property ANIMATED_TRANSFORM_PROPERTY = new Property(Matrix.class, "animatedTransform") { @Override public void set(ImageView view, Matrix matrix) { ImageViewUtils.animateTransform(view, matrix); } @Override public Matrix get(ImageView object) { return null; } }; public ChangeImageTransform() { } public ChangeImageTransform(Context context, AttributeSet attrs) { super(context, attrs); } private void captureValues(TransitionValues transitionValues) { View view = transitionValues.view; if (!(view instanceof ImageView) || view.getVisibility() != View.VISIBLE) { return; } ImageView imageView = (ImageView) view; Drawable drawable = imageView.getDrawable(); if (drawable == null) { return; } Map values = transitionValues.values; int left = view.getLeft(); int top = view.getTop(); int right = view.getRight(); int bottom = view.getBottom(); Rect bounds = new Rect(left, top, right, bottom); values.put(PROPNAME_BOUNDS, bounds); values.put(PROPNAME_MATRIX, copyImageMatrix(imageView)); } @Override public void captureStartValues(@NonNull TransitionValues transitionValues) { captureValues(transitionValues); } @Override public void captureEndValues(@NonNull TransitionValues transitionValues) { captureValues(transitionValues); } @Override public String[] getTransitionProperties() { return sTransitionProperties; } /** * Creates an Animator for ImageViews moving, changing dimensions, and/or changing * {@link android.widget.ImageView.ScaleType}. * * @param sceneRoot The root of the transition hierarchy. * @param startValues The values for a specific target in the start scene. * @param endValues The values for the target in the end scene. * @return An Animator to move an ImageView or null if the View is not an ImageView, * the Drawable changed, the View is not VISIBLE, or there was no change. */ @Override public Animator createAnimator(@NonNull ViewGroup sceneRoot, TransitionValues startValues, final TransitionValues endValues) { if (startValues == null || endValues == null) { return null; } Rect startBounds = (Rect) startValues.values.get(PROPNAME_BOUNDS); Rect endBounds = (Rect) endValues.values.get(PROPNAME_BOUNDS); if (startBounds == null || endBounds == null) { return null; } Matrix startMatrix = (Matrix) startValues.values.get(PROPNAME_MATRIX); Matrix endMatrix = (Matrix) endValues.values.get(PROPNAME_MATRIX); boolean matricesEqual = (startMatrix == null && endMatrix == null) || (startMatrix != null && startMatrix.equals(endMatrix)); if (startBounds.equals(endBounds) && matricesEqual) { return null; } final ImageView imageView = (ImageView) endValues.view; Drawable drawable = imageView.getDrawable(); int drawableWidth = drawable.getIntrinsicWidth(); int drawableHeight = drawable.getIntrinsicHeight(); ImageViewUtils.startAnimateTransform(imageView); ObjectAnimator animator; if (drawableWidth == 0 || drawableHeight == 0) { animator = createNullAnimator(imageView); } else { if (startMatrix == null) { startMatrix = MatrixUtils.IDENTITY_MATRIX; } if (endMatrix == null) { endMatrix = MatrixUtils.IDENTITY_MATRIX; } ANIMATED_TRANSFORM_PROPERTY.set(imageView, startMatrix); animator = createMatrixAnimator(imageView, startMatrix, endMatrix); } ImageViewUtils.reserveEndAnimateTransform(imageView, animator); return animator; } private ObjectAnimator createNullAnimator(ImageView imageView) { return ObjectAnimator.ofObject(imageView, ANIMATED_TRANSFORM_PROPERTY, NULL_MATRIX_EVALUATOR, null, null); } private ObjectAnimator createMatrixAnimator(final ImageView imageView, Matrix startMatrix, final Matrix endMatrix) { return ObjectAnimator.ofObject(imageView, ANIMATED_TRANSFORM_PROPERTY, new TransitionUtils.MatrixEvaluator(), startMatrix, endMatrix); } private static Matrix copyImageMatrix(ImageView view) { switch (view.getScaleType()) { case FIT_XY: return fitXYMatrix(view); case CENTER_CROP: return centerCropMatrix(view); default: return new Matrix(view.getImageMatrix()); } } /** * Calculates the image transformation matrix for an ImageView with ScaleType FIT_XY. This * needs to be manually calculated as the platform does not give us the value for this case. */ private static Matrix fitXYMatrix(ImageView view) { final Drawable image = view.getDrawable(); final Matrix matrix = new Matrix(); matrix.postScale( ((float) view.getWidth()) / image.getIntrinsicWidth(), ((float) view.getHeight()) / image.getIntrinsicHeight()); return matrix; } /** * Calculates the image transformation matrix for an ImageView with ScaleType CENTER_CROP. This * needs to be manually calculated for consistent behavior across all the API levels. */ private static Matrix centerCropMatrix(ImageView view) { final Drawable image = view.getDrawable(); final int imageWidth = image.getIntrinsicWidth(); final int imageViewWidth = view.getWidth(); final float scaleX = ((float) imageViewWidth) / imageWidth; final int imageHeight = image.getIntrinsicHeight(); final int imageViewHeight = view.getHeight(); final float scaleY = ((float) imageViewHeight) / imageHeight; final float maxScale = Math.max(scaleX, scaleY); final float width = imageWidth * maxScale; final float height = imageHeight * maxScale; final int tx = Math.round((imageViewWidth - width) / 2f); final int ty = Math.round((imageViewHeight - height) / 2f); final Matrix matrix = new Matrix(); matrix.postScale(maxScale, maxScale); matrix.postTranslate(tx, ty); return matrix; } }