| /* |
| * 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; |
| } |
| } |
| } |