blob: d68e97190a36f4cd98fe40462751f9a49a7855a7 [file] [log] [blame]
/*
* Copyright (C) 2014 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.transition;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.animation.RectEvaluator;
import android.animation.TypeEvaluator;
import android.animation.ValueAnimator;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.util.FloatMath;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroupOverlay;
import android.view.ViewParent;
import android.widget.ImageView;
import java.util.ArrayList;
import java.util.Map;
/**
* Transitions ImageViews, including size, scaleType, and matrix. The ImageView drawable
* must remain the same between both start and end states, but the
* {@link ImageView#setScaleType(android.widget.ImageView.ScaleType)} may
* differ.
*/
public class MoveImage extends Transition {
private static final String TAG = "MoveImage";
private static final String PROPNAME_MATRIX = "android:moveImage:matrix";
private static final String PROPNAME_BOUNDS = "android:moveImage:bounds";
private static final String PROPNAME_CLIP = "android:moveImage:clip";
private static final String PROPNAME_DRAWABLE = "android:moveImage:drawable";
private int[] mTempLoc = new int[2];
private static final String[] sTransitionProperties = {
PROPNAME_MATRIX,
PROPNAME_BOUNDS,
PROPNAME_CLIP,
PROPNAME_DRAWABLE,
};
private void captureValues(TransitionValues transitionValues) {
View view = transitionValues.view;
if (!(view instanceof ImageView) || view.getVisibility() != View.VISIBLE) {
return;
}
Map<String, Object> values = transitionValues.values;
ViewGroup parent = (ViewGroup) view.getParent();
parent.getLocationInWindow(mTempLoc);
int paddingLeft = view.getPaddingLeft();
int paddingTop = view.getPaddingTop();
int paddingRight = view.getPaddingRight();
int paddingBottom = view.getPaddingBottom();
int left = mTempLoc[0] + paddingLeft + view.getLeft() + Math.round(view.getTranslationX());
int top = mTempLoc[1] + paddingTop + view.getTop() + Math.round(view.getTranslationY());
int right = left + view.getWidth() - paddingRight - paddingLeft;
int bottom = top + view.getHeight() - paddingTop - paddingBottom;
Rect bounds = new Rect(left, top, right, bottom);
values.put(PROPNAME_BOUNDS, bounds);
ImageView imageView = (ImageView) view;
Matrix matrix = getMatrix(imageView);
values.put(PROPNAME_MATRIX, matrix);
values.put(PROPNAME_CLIP, findClip(imageView));
values.put(PROPNAME_DRAWABLE, imageView.getDrawable());
}
@Override
public void captureStartValues(TransitionValues transitionValues) {
captureValues(transitionValues);
}
@Override
public void captureEndValues(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(ViewGroup sceneRoot, TransitionValues startValues,
TransitionValues endValues) {
if (startValues == null || endValues == null
|| startValues.values.get(PROPNAME_BOUNDS) == null
|| endValues.values.get(PROPNAME_BOUNDS) == null
|| startValues.values.get(PROPNAME_DRAWABLE)
!= endValues.values.get(PROPNAME_DRAWABLE)) {
return null;
}
ArrayList<PropertyValuesHolder> changes = new ArrayList<PropertyValuesHolder>();
Matrix startMatrix = (Matrix) startValues.values.get(PROPNAME_MATRIX);
Matrix endMatrix = (Matrix) endValues.values.get(PROPNAME_MATRIX);
if (!startMatrix.equals(endMatrix)) {
changes.add(PropertyValuesHolder.ofObject(MatrixClippedDrawable.MATRIX_PROPERTY,
new MatrixEvaluator(), startMatrix, endMatrix));
}
sceneRoot.getLocationInWindow(mTempLoc);
int rootX = mTempLoc[0];
int rootY = mTempLoc[1];
final ImageView imageView = (ImageView) endValues.view;
Drawable drawable = imageView.getDrawable();
Rect startBounds = new Rect((Rect) startValues.values.get(PROPNAME_BOUNDS));
Rect endBounds = new Rect((Rect) endValues.values.get(PROPNAME_BOUNDS));
startBounds.offset(-rootX, -rootY);
endBounds.offset(-rootX, -rootY);
if (!startBounds.equals(endBounds)) {
changes.add(PropertyValuesHolder.ofObject("bounds", new RectEvaluator(new Rect()),
startBounds, endBounds));
}
Rect startClip = (Rect) startValues.values.get(PROPNAME_CLIP);
Rect endClip = (Rect) endValues.values.get(PROPNAME_CLIP);
if (startClip != null || endClip != null) {
startClip = nonNullClip(startClip, sceneRoot, rootX, rootY);
endClip = nonNullClip(endClip, sceneRoot, rootX, rootY);
expandClip(startBounds, startMatrix, startClip, endClip);
expandClip(endBounds, endMatrix, endClip, startClip);
boolean clipped = !startClip.contains(startBounds) || !endClip.contains(endBounds);
if (!clipped) {
startClip = null;
} else if (!startClip.equals(endClip)) {
changes.add(PropertyValuesHolder.ofObject(MatrixClippedDrawable.CLIP_PROPERTY,
new RectEvaluator(), startClip, endClip));
}
}
if (changes.isEmpty()) {
return null;
}
drawable = drawable.getConstantState().newDrawable();
final MatrixClippedDrawable matrixClippedDrawable = new MatrixClippedDrawable(drawable);
matrixClippedDrawable.setMatrix(startMatrix);
matrixClippedDrawable.setBounds(startBounds);
matrixClippedDrawable.setClipRect(startClip);
imageView.setVisibility(View.INVISIBLE);
final ViewGroupOverlay overlay = sceneRoot.getOverlay();
overlay.add(matrixClippedDrawable);
ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(matrixClippedDrawable,
changes.toArray(new PropertyValuesHolder[changes.size()]));
AnimatorListenerAdapter listener = new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
imageView.setVisibility(View.VISIBLE);
overlay.remove(matrixClippedDrawable);
}
@Override
public void onAnimationPause(Animator animation) {
imageView.setVisibility(View.VISIBLE);
overlay.remove(matrixClippedDrawable);
}
@Override
public void onAnimationResume(Animator animation) {
imageView.setVisibility(View.INVISIBLE);
overlay.add(matrixClippedDrawable);
}
};
animator.addListener(listener);
animator.addPauseListener(listener);
return animator;
}
private static Rect nonNullClip(Rect clip, ViewGroup sceneRoot, int rootX, int rootY) {
if (clip != null) {
clip = new Rect(clip);
clip.offset(-rootX, -rootY);
} else {
clip = new Rect(0, 0, sceneRoot.getWidth(), sceneRoot.getHeight());
}
return clip;
}
private static void expandClip(Rect bounds, Matrix matrix, Rect clip, Rect otherClip) {
RectF boundsF = new RectF(bounds);
matrix.mapRect(boundsF);
clip.left = expandMinDimension(boundsF.left, clip.left, otherClip.left);
clip.top = expandMinDimension(boundsF.top, clip.top, otherClip.top);
clip.right = expandMaxDimension(boundsF.right, clip.right, otherClip.right);
clip.bottom = expandMaxDimension(boundsF.bottom, clip.bottom, otherClip.bottom);
}
private static int expandMinDimension(float boundsDimension, int clipDimension,
int otherClipDimension) {
if (clipDimension > boundsDimension) {
// Already clipped in that dimension, return the clipped value
return clipDimension;
}
return Math.min(clipDimension, otherClipDimension);
}
private static int expandMaxDimension(float boundsDimension, int clipDimension,
int otherClipDimension) {
return -expandMinDimension(-boundsDimension, -clipDimension, -otherClipDimension);
}
private static Matrix getMatrix(ImageView imageView) {
Drawable drawable = imageView.getDrawable();
int drawableWidth = drawable.getIntrinsicWidth();
int drawableHeight = drawable.getIntrinsicHeight();
ImageView.ScaleType scaleType = imageView.getScaleType();
if (drawableWidth <= 0 || drawableHeight <= 0 || scaleType == ImageView.ScaleType.FIT_XY) {
return null;
}
return new Matrix(imageView.getImageMatrix());
}
private Rect findClip(ImageView imageView) {
if (imageView.getCropToPadding()) {
Rect clip = getClip(imageView);
clip.left += imageView.getPaddingLeft();
clip.right -= imageView.getPaddingRight();
clip.top += imageView.getPaddingTop();
clip.bottom -= imageView.getPaddingBottom();
return clip;
} else {
View view = imageView;
ViewParent viewParent;
while ((viewParent = view.getParent()) instanceof ViewGroup) {
ViewGroup viewGroup = (ViewGroup) viewParent;
if (viewGroup.getClipChildren()) {
Rect clip = getClip(view);
return clip;
}
view = viewGroup;
}
}
return null;
}
private Rect getClip(View clipView) {
Rect clipBounds = clipView.getClipBounds();
if (clipBounds == null) {
clipBounds = new Rect(clipView.getLeft(), clipView.getTop(),
clipView.getRight(), clipView.getBottom());
}
ViewParent parent = clipView.getParent();
if (parent instanceof ViewGroup) {
ViewGroup parentViewGroup = (ViewGroup) parent;
parentViewGroup.getLocationInWindow(mTempLoc);
clipBounds.offset(mTempLoc[0], mTempLoc[1]);
}
return clipBounds;
}
@Override
public Transition clone() {
MoveImage clone = (MoveImage) super.clone();
clone.mTempLoc = new int[2];
return clone;
}
private static class MatrixEvaluator implements TypeEvaluator<Matrix> {
static final Matrix sIdentity = new Matrix();
float[] mTempStartValues = new float[9];
float[] mTempEndValues = new float[9];
Matrix mTempMatrix = new Matrix();
@Override
public Matrix evaluate(float fraction, Matrix startValue, Matrix endValue) {
if (startValue == null && endValue == null) {
return null;
}
if (startValue == null) {
startValue = sIdentity;
} else if (endValue == null) {
endValue = sIdentity;
}
startValue.getValues(mTempStartValues);
endValue.getValues(mTempEndValues);
for (int i = 0; i < 9; i++) {
float diff = mTempEndValues[i] - mTempStartValues[i];
mTempEndValues[i] = mTempStartValues[i] + (fraction * diff);
}
mTempMatrix.setValues(mTempEndValues);
return mTempMatrix;
}
}
}