package com.example.cyandev.androidplayground; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.TypeEvaluator; import android.graphics.Matrix; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v7.app.AppCompatActivity; import android.util.Property; import android.view.View; import android.view.animation.DecelerateInterpolator; import android.widget.ImageView; public class ImageTransitionActivity extends AppCompatActivity { private ImageView mThumbnailImageView; private ImageView mFullImageView; private boolean mFullShown = false; private final static Property<View, Rect> BOUNDS = new Property<View, Rect>(Rect.class, "bounds") { @Override public void set(View object, Rect value) { object.layout(value.left, value.top, value.right, value.bottom); } @Override public Rect get(View object) { return new Rect(object.getLeft(), object.getTop(), object.getRight(), object.getBottom()); } }; private final static Property<ImageView, Matrix> IMAGE_MATRIX = new Property<ImageView, Matrix>(Matrix.class, "imageMatrix") { @Override public void set(ImageView object, Matrix value) { object.setImageMatrix(value); } @Override public Matrix get(ImageView object) { return object.getImageMatrix(); } }; private static class RectEvaluator implements TypeEvaluator<Rect> { private Rect mTmpRect = new Rect(); @Override public Rect evaluate(float fraction, Rect startValue, Rect endValue) { mTmpRect.left = (int) (startValue.left + (endValue.left - startValue.left) * fraction); mTmpRect.top = (int) (startValue.top + (endValue.top - startValue.top) * fraction); mTmpRect.right = (int) (startValue.right + (endValue.right - startValue.right) * fraction); mTmpRect.bottom = (int) (startValue.bottom + (endValue.bottom - startValue.bottom) * fraction); return mTmpRect; } } private static class MatrixEvaluator implements TypeEvaluator<Matrix> { private float[] mTmpStartValues = new float[9]; private float[] mTmpEndValues = new float[9]; private Matrix mTmpMatrix = new Matrix(); @Override public Matrix evaluate(float fraction, Matrix startValue, Matrix endValue) { startValue.getValues(mTmpStartValues); endValue.getValues(mTmpEndValues); for (int i = 0; i < 9; i++) { float diff = mTmpEndValues[i] - mTmpStartValues[i]; mTmpEndValues[i] = mTmpStartValues[i] + (fraction * diff); } mTmpMatrix.setValues(mTmpEndValues); return mTmpMatrix; } } @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_image_transition); mThumbnailImageView = (ImageView) findViewById(R.id.thumbnail_image); mFullImageView = (ImageView) findViewById(R.id.full_image); View.OnClickListener onClickListener = new View.OnClickListener() { @Override public void onClick(View v) { mFullShown = !mFullShown; createAnimator(mFullShown); } }; mThumbnailImageView.setOnClickListener(onClickListener); mFullImageView.setOnClickListener(onClickListener); } private Rect getDrawableIntrinsicBounds(Drawable d) { Rect rect = new Rect(); rect.right = d.getIntrinsicWidth(); rect.bottom = d.getIntrinsicHeight(); return rect; } private void createAnimator(final boolean in) { int[] thumbnailOrigin = new int[2]; int[] fullOrigin = new int[2]; mThumbnailImageView.getLocationInWindow(thumbnailOrigin); mFullImageView.getLocationInWindow(fullOrigin); int thumbnailLeft = mFullImageView.getLeft() + (thumbnailOrigin[0] - fullOrigin[0]); int thumbnailTop = mFullImageView.getTop() + (thumbnailOrigin[1] - fullOrigin[1]); Rect thumbnailBounds = new Rect(thumbnailLeft, thumbnailTop, thumbnailLeft + mThumbnailImageView.getWidth(), thumbnailTop + mThumbnailImageView.getHeight()); Rect fullBounds = new Rect(mFullImageView.getLeft(), mFullImageView.getTop(), mFullImageView.getRight(), mFullImageView.getBottom()); Matrix thumbnailMatrix = mThumbnailImageView.getImageMatrix(); Matrix fullMatrix = new Matrix(); fullMatrix.setRectToRect(new RectF(getDrawableIntrinsicBounds(mFullImageView.getDrawable())), new RectF(0, 0, fullBounds.width(), fullBounds.height()), Matrix.ScaleToFit.CENTER); // Temporarily uses `MATRIX` type, because we want to animate the matrix by ourselves. mFullImageView.setScaleType(ImageView.ScaleType.MATRIX); mFullImageView.setImageMatrix(in ? thumbnailMatrix : fullMatrix); mFullImageView.post(new Runnable() { @Override public void run() { if (in) { mThumbnailImageView.setVisibility(View.INVISIBLE); } mFullImageView.setVisibility(View.VISIBLE); } }); Animator boundsAnimator = ObjectAnimator.ofObject(mFullImageView, BOUNDS, new RectEvaluator(), in ? thumbnailBounds : fullBounds, in ? fullBounds : thumbnailBounds); Animator matrixAnimator = ObjectAnimator.ofObject(mFullImageView, IMAGE_MATRIX, new MatrixEvaluator(), in ? thumbnailMatrix : fullMatrix, in ? fullMatrix : thumbnailMatrix); final Runnable resetRunnable = new Runnable() { @Override public void run() { if (!in) { mFullImageView.setVisibility(View.INVISIBLE); mThumbnailImageView.setVisibility(View.VISIBLE); } mFullImageView.requestLayout(); // Animation is finished, reset the scale type to `FIT_CENTER`, which looks the same // as the end state. mFullImageView.setScaleType(ImageView.ScaleType.FIT_CENTER); } }; AnimatorSet animator = new AnimatorSet(); animator.playTogether(boundsAnimator, matrixAnimator); animator.setDuration(800); animator.setInterpolator(new DecelerateInterpolator(3.f)); animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationCancel(Animator animation) { resetRunnable.run(); } @Override public void onAnimationEnd(Animator animation) { resetRunnable.run(); } }); animator.start(); } }