Loading api/current.txt +7 −1 Original line number Diff line number Diff line Loading @@ -1028,6 +1028,8 @@ package android { field public static final int readPermission = 16842759; // 0x1010007 field public static final int recognitionService = 16843932; // 0x101049c field public static final int relinquishTaskIdentity = 16843894; // 0x1010476 field public static final int reparent = 16843965; // 0x10104bd field public static final int reparentWithOverlay = 16843966; // 0x10104be field public static final int repeatCount = 16843199; // 0x10101bf field public static final int repeatMode = 16843200; // 0x10101c0 field public static final int reqFiveWayNav = 16843314; // 0x1010232 Loading Loading @@ -31803,7 +31805,7 @@ package android.transition { ctor public ChangeBounds(android.content.Context, android.util.AttributeSet); method public void captureEndValues(android.transition.TransitionValues); method public void captureStartValues(android.transition.TransitionValues); method public void setReparent(boolean); method public deprecated void setReparent(boolean); method public void setResizeClip(boolean); } Loading @@ -31826,6 +31828,10 @@ package android.transition { ctor public ChangeTransform(android.content.Context, android.util.AttributeSet); method public void captureEndValues(android.transition.TransitionValues); method public void captureStartValues(android.transition.TransitionValues); method public boolean getReparent(); method public boolean getReparentWithOverlay(); method public void setReparent(boolean); method public void setReparentWithOverlay(boolean); } public class CircularPropagation extends android.transition.VisibilityPropagation { core/java/android/transition/ChangeBounds.java +2 −0 Original line number Diff line number Diff line Loading @@ -107,6 +107,8 @@ public class ChangeBounds extends Transition { * * @param reparent true if the transition should track the parent * container of target views and animate parent changes. * @deprecated Use {@link android.transition.ChangeTransform} to handle * transitions between different parents. */ public void setReparent(boolean reparent) { mReparent = reparent; Loading core/java/android/transition/ChangeImageTransform.java +1 −24 Original line number Diff line number Diff line Loading @@ -16,7 +16,6 @@ package android.transition; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.animation.TypeEvaluator; import android.content.Context; Loading Loading @@ -196,28 +195,6 @@ public class ChangeImageTransform extends Transition { private ObjectAnimator createMatrixAnimator(final ImageView imageView, Matrix startMatrix, final Matrix endMatrix) { return ObjectAnimator.ofObject(imageView, ANIMATED_TRANSFORM_PROPERTY, new MatrixEvaluator(), startMatrix, endMatrix); new TransitionUtils.MatrixEvaluator(), startMatrix, endMatrix); } private static class MatrixEvaluator implements TypeEvaluator<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) { 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; } } } core/java/android/transition/ChangeTransform.java +352 −85 Original line number Diff line number Diff line Loading @@ -16,72 +16,134 @@ package android.transition; import android.animation.Animator; import android.animation.FloatArrayEvaluator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Matrix; import android.util.AttributeSet; import android.util.FloatProperty; import android.util.Property; import android.view.GhostView; import android.view.View; import android.view.ViewGroup; import com.android.internal.R; /** * This Transition captures scale and rotation for Views before and after the * scene change and animates those changes during the transition. * * <p>ChangeTransform does not work when the pivot changes between scenes, so either the * pivot must be set to prevent automatic pivot adjustment or the View's size must be unchanged.</p> * A change in parent is handled as well by capturing the transforms from * the parent before and after the scene change and animating those during the * transition. */ public class ChangeTransform extends Transition { private static final String TAG = "ChangeTransform"; private static final String PROPNAME_SCALE_X = "android:changeTransform:scaleX"; private static final String PROPNAME_SCALE_Y = "android:changeTransform:scaleY"; private static final String PROPNAME_ROTATION_X = "android:changeTransform:rotationX"; private static final String PROPNAME_ROTATION_Y = "android:changeTransform:rotationY"; private static final String PROPNAME_ROTATION_Z = "android:changeTransform:rotationZ"; private static final String PROPNAME_PIVOT_X = "android:changeTransform:pivotX"; private static final String PROPNAME_PIVOT_Y = "android:changeTransform:pivotY"; private static final String PROPNAME_MATRIX = "android:changeTransform:matrix"; private static final String PROPNAME_TRANSFORMS = "android:changeTransform:transforms"; private static final String PROPNAME_PARENT = "android:changeTransform:parent"; private static final String PROPNAME_PARENT_MATRIX = "android:changeTransform:parentMatrix"; private static final String[] sTransitionProperties = { PROPNAME_SCALE_X, PROPNAME_SCALE_Y, PROPNAME_ROTATION_X, PROPNAME_ROTATION_Y, PROPNAME_ROTATION_Z, PROPNAME_MATRIX, PROPNAME_TRANSFORMS, PROPNAME_PARENT_MATRIX, }; private static final FloatProperty<View>[] sChangedProperties = new FloatProperty[] { (FloatProperty) View.SCALE_X, (FloatProperty) View.SCALE_Y, (FloatProperty) View.ROTATION_X, (FloatProperty) View.ROTATION_Y, (FloatProperty) View.ROTATION, }; private static Property<View, float[]> TRANSFORMS = new Property<View, float[]>(float[].class, "transforms") { private static final Property<View, Matrix> ANIMATION_MATRIX_PROPERTY = new Property<View, Matrix>(Matrix.class, "animationMatrix") { @Override public float[] get(View object) { public Matrix get(View object) { return null; } @Override public void set(View view, float[] values) { for (int i = 0; i < values.length; i++) { float value = values[i]; if (!Float.isNaN(value)) { sChangedProperties[i].setValue(view, value); } } public void set(View object, Matrix value) { object.setAnimationMatrix(value); } }; private boolean mUseOverlay = true; private boolean mReparent = true; private Matrix mTempMatrix = new Matrix(); public ChangeTransform() {} public ChangeTransform(Context context, AttributeSet attrs) { super(context, attrs); TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ChangeTransform); mUseOverlay = a.getBoolean(R.styleable.ChangeTransform_reparentWithOverlay, true); mReparent = a.getBoolean(R.styleable.ChangeTransform_reparent, true); a.recycle(); } /** * Returns whether changes to parent should use an overlay or not. When the parent * change doesn't use an overlay, it affects the transforms of the child. The * default value is <code>true</code>. * * <p>Note: when Overlays are not used when a parent changes, a view can be clipped when * it moves outside the bounds of its parent. Setting * {@link android.view.ViewGroup#setClipChildren(boolean)} and * {@link android.view.ViewGroup#setClipToPadding(boolean)} can help. Also, when * Overlays are not used and the parent is animating its location, the position of the * child view will be relative to its parent's final position, so it may appear to "jump" * at the beginning.</p> * * @return <code>true</code> when a changed parent should execute the transition * inside the scene root's overlay or <code>false</code> if a parent change only * affects the transform of the transitioning view. */ public boolean getReparentWithOverlay() { return mUseOverlay; } /** * Sets whether changes to parent should use an overlay or not. When the parent * change doesn't use an overlay, it affects the transforms of the child. The * default value is <code>true</code>. * * <p>Note: when Overlays are not used when a parent changes, a view can be clipped when * it moves outside the bounds of its parent. Setting * {@link android.view.ViewGroup#setClipChildren(boolean)} and * {@link android.view.ViewGroup#setClipToPadding(boolean)} can help. Also, when * Overlays are not used and the parent is animating its location, the position of the * child view will be relative to its parent's final position, so it may appear to "jump" * at the beginning.</p> * * @return <code>true</code> when a changed parent should execute the transition * inside the scene root's overlay or <code>false</code> if a parent change only * affects the transform of the transitioning view. */ public void setReparentWithOverlay(boolean reparentWithOverlay) { mUseOverlay = reparentWithOverlay; } /** * Returns whether parent changes will be tracked by the ChangeTransform. If parent * changes are tracked, then the transform will adjust to the transforms of the * different parents. If they aren't tracked, only the transforms of the transitioning * view will be tracked. Default is true. * * @return whether parent changes will be tracked by the ChangeTransform. */ public boolean getReparent() { return mReparent; } /** * Sets whether parent changes will be tracked by the ChangeTransform. If parent * changes are tracked, then the transform will adjust to the transforms of the * different parents. If they aren't tracked, only the transforms of the transitioning * view will be tracked. Default is true. * * @param reparent Set to true to track parent changes or false to only track changes * of the transitioning view without considering the parent change. */ public void setReparent(boolean reparent) { mReparent = reparent; } @Override Loading @@ -89,19 +151,29 @@ public class ChangeTransform extends Transition { return sTransitionProperties; } private void captureValues(TransitionValues values) { View view = values.view; private void captureValues(TransitionValues transitionValues) { View view = transitionValues.view; if (view.getVisibility() == View.GONE) { return; } values.values.put(PROPNAME_SCALE_X, view.getScaleX()); values.values.put(PROPNAME_SCALE_Y, view.getScaleY()); values.values.put(PROPNAME_PIVOT_X, view.getPivotX()); values.values.put(PROPNAME_PIVOT_Y, view.getPivotY()); values.values.put(PROPNAME_ROTATION_X, view.getRotationX()); values.values.put(PROPNAME_ROTATION_Y, view.getRotationY()); values.values.put(PROPNAME_ROTATION_Z, view.getRotation()); transitionValues.values.put(PROPNAME_PARENT, view.getParent()); Transforms transforms = new Transforms(view); transitionValues.values.put(PROPNAME_TRANSFORMS, transforms); Matrix matrix = view.getMatrix(); if (matrix == null || matrix.isIdentity()) { matrix = null; } else { matrix = new Matrix(matrix); } transitionValues.values.put(PROPNAME_MATRIX, matrix); if (mReparent) { Matrix parentMatrix = new Matrix(); ViewGroup parent = (ViewGroup) view.getParent(); parent.transformMatrixToGlobal(parentMatrix); parentMatrix.preTranslate(-parent.getScrollX(), -parent.getScrollY()); transitionValues.values.put(PROPNAME_PARENT_MATRIX, parentMatrix); } return; } @Override Loading @@ -115,57 +187,252 @@ public class ChangeTransform extends Transition { } @Override public Animator createAnimator(final ViewGroup sceneRoot, TransitionValues startValues, public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, TransitionValues endValues) { if (startValues == null || endValues == null || !startValues.values.containsKey(PROPNAME_SCALE_X) || !endValues.values.containsKey(PROPNAME_SCALE_X) || !isPivotSame(startValues, endValues) || !isChanged(startValues, endValues)) { if (startValues == null || endValues == null || !startValues.values.containsKey(PROPNAME_PARENT) || !endValues.values.containsKey(PROPNAME_PARENT)) { return null; } float[] start = createValues(startValues); float[] end = createValues(endValues); for (int i = 0; i < start.length; i++) { if (start[i] == end[i]) { start[i] = Float.NaN; end[i] = Float.NaN; ViewGroup startParent = (ViewGroup) startValues.values.get(PROPNAME_PARENT); ViewGroup endParent = (ViewGroup) endValues.values.get(PROPNAME_PARENT); boolean handleParentChange = mReparent && !parentsMatch(startParent, endParent); Matrix startMatrix = (Matrix) startValues.view.getTag(R.id.transitionTransform); if (startMatrix != null) { startValues.values.put(PROPNAME_MATRIX, startMatrix); } Matrix startParentMatrix = (Matrix) startValues.view.getTag(R.id.parentMatrix); if (startParentMatrix != null) { startValues.values.put(PROPNAME_PARENT_MATRIX, startParentMatrix); } // First handle the parent change: if (handleParentChange) { setMatricesForParent(startValues, endValues); } // Next handle the normal matrix transform: ObjectAnimator transformAnimator = createTransformAnimator(startValues, endValues); if (handleParentChange && transformAnimator != null && mUseOverlay) { createGhostView(sceneRoot, startValues, endValues); } return transformAnimator; } private ObjectAnimator createTransformAnimator(TransitionValues startValues, TransitionValues endValues) { Matrix startMatrix = (Matrix) startValues.values.get(PROPNAME_MATRIX); Matrix endMatrix = (Matrix) endValues.values.get(PROPNAME_MATRIX); if (startMatrix == null) { startMatrix = Matrix.IDENTITY_MATRIX; } if (endMatrix == null) { endMatrix = Matrix.IDENTITY_MATRIX; } if (startMatrix.equals(endMatrix)) { return null; } final Transforms transforms = (Transforms) endValues.values.get(PROPNAME_TRANSFORMS); // clear the transform properties so that we can use the animation matrix instead final View view = endValues.view; setIdentityTransforms(view); ObjectAnimator animator = ObjectAnimator.ofObject(view, ANIMATION_MATRIX_PROPERTY, new TransitionUtils.MatrixEvaluator(), startMatrix, endMatrix); AnimatorListenerAdapter listener = new AnimatorListenerAdapter() { private boolean mIsCanceled; private Matrix mTempMatrix; @Override public void onAnimationCancel(Animator animation) { mIsCanceled = true; } @Override public void onAnimationEnd(Animator animation) { if (!mIsCanceled) { view.setTagInternal(R.id.transitionTransform, null); view.setTagInternal(R.id.parentMatrix, null); } ANIMATION_MATRIX_PROPERTY.set(view, null); transforms.restore(view); } @Override public void onAnimationPause(Animator animation) { ValueAnimator animator = (ValueAnimator) animation; Matrix currentMatrix = (Matrix) animator.getAnimatedValue(); if (mTempMatrix == null) { mTempMatrix = new Matrix(currentMatrix); } else { sChangedProperties[i].setValue(endValues.view, start[i]); mTempMatrix.set(currentMatrix); } view.setTagInternal(R.id.transitionTransform, mTempMatrix); transforms.restore(view); } FloatArrayEvaluator evaluator = new FloatArrayEvaluator(new float[start.length]); return ObjectAnimator.ofObject(endValues.view, TRANSFORMS, evaluator, start, end); @Override public void onAnimationResume(Animator animation) { setIdentityTransforms(view); } }; private static float[] createValues(TransitionValues transitionValues) { float[] values = new float[sChangedProperties.length]; for (int i = 0; i < values.length; i++) { values[i] = (Float) transitionValues.values.get(sTransitionProperties[i]); animator.addListener(listener); animator.addPauseListener(listener); return animator; } return values; private boolean parentsMatch(ViewGroup startParent, ViewGroup endParent) { boolean parentsMatch = false; if (!isValidTarget(startParent) || !isValidTarget(endParent)) { parentsMatch = startParent == endParent; } else { TransitionValues endValues = getMatchedTransitionValues(startParent, true); if (endValues != null) { parentsMatch = endParent == endValues.view; } } return parentsMatch; } private static boolean isPivotSame(TransitionValues startValues, TransitionValues endValues) { float startPivotX = (Float) startValues.values.get(PROPNAME_PIVOT_X); float startPivotY = (Float) startValues.values.get(PROPNAME_PIVOT_Y); float endPivotX = (Float) endValues.values.get(PROPNAME_PIVOT_X); float endPivotY = (Float) endValues.values.get(PROPNAME_PIVOT_Y); private void createGhostView(final ViewGroup sceneRoot, TransitionValues startValues, TransitionValues endValues) { View view = endValues.view; Matrix endMatrix = (Matrix) endValues.values.get(PROPNAME_PARENT_MATRIX); Matrix localEndMatrix = new Matrix(endMatrix); sceneRoot.transformMatrixToLocal(localEndMatrix); GhostView ghostView = GhostView.addGhost(view, sceneRoot, localEndMatrix); // We don't support pivot changes, because they could be automatically set // and we can't end the state in an automatic state. return startPivotX == endPivotX && startPivotY == endPivotY; Transition outerTransition = this; while (outerTransition.mParent != null) { outerTransition = outerTransition.mParent; } GhostListener listener = new GhostListener(view, ghostView, endMatrix); outerTransition.addListener(listener); private static boolean isChanged(TransitionValues startValues, TransitionValues endValues) { for (int i = 0; i < sChangedProperties.length; i++) { Object start = startValues.values.get(sTransitionProperties[i]); Object end = endValues.values.get(sTransitionProperties[i]); if (!start.equals(end)) { return true; if (startValues.view != endValues.view) { startValues.view.setTransitionAlpha(0); } view.setTransitionAlpha(1); } private void setMatricesForParent(TransitionValues startValues, TransitionValues endValues) { Matrix endParentMatrix = (Matrix) endValues.values.get(PROPNAME_PARENT_MATRIX); endValues.view.setTagInternal(R.id.parentMatrix, endParentMatrix); Matrix toLocal = mTempMatrix; toLocal.reset(); endParentMatrix.invert(toLocal); Matrix startLocal = (Matrix) startValues.values.get(PROPNAME_MATRIX); if (startLocal == null) { startLocal = new Matrix(); startValues.values.put(PROPNAME_MATRIX, startLocal); } Matrix startParentMatrix = (Matrix) startValues.values.get(PROPNAME_PARENT_MATRIX); startLocal.postConcat(startParentMatrix); startLocal.postConcat(toLocal); } private static void setIdentityTransforms(View view) { setTransforms(view, 0, 0, 0, 1, 1, 0, 0, 0); } private static void setTransforms(View view, float translationX, float translationY, float translationZ, float scaleX, float scaleY, float rotationX, float rotationY, float rotationZ) { view.setTranslationX(translationX); view.setTranslationY(translationY); view.setTranslationZ(translationZ); view.setScaleX(scaleX); view.setScaleY(scaleY); view.setRotationX(rotationX); view.setRotationY(rotationY); view.setRotation(rotationZ); } private static class Transforms { public final float translationX; public final float translationY; public final float translationZ; public final float scaleX; public final float scaleY; public final float rotationX; public final float rotationY; public final float rotationZ; public Transforms(View view) { translationX = view.getTranslationX(); translationY = view.getTranslationY(); translationZ = view.getTranslationZ(); scaleX = view.getScaleX(); scaleY = view.getScaleY(); rotationX = view.getRotationX(); rotationY = view.getRotationY(); rotationZ = view.getRotation(); } public void restore(View view) { setTransforms(view, translationX, translationY, translationZ, scaleX, scaleY, rotationX, rotationY, rotationZ); } @Override public boolean equals(Object that) { if (!(that instanceof Transforms)) { return false; } Transforms thatTransform = (Transforms) that; return thatTransform.translationX == translationX && thatTransform.translationY == translationY && thatTransform.translationZ == translationZ && thatTransform.scaleX == scaleX && thatTransform.scaleY == scaleY && thatTransform.rotationX == rotationX && thatTransform.rotationY == rotationY && thatTransform.rotationZ == rotationZ; } } private static class GhostListener extends Transition.TransitionListenerAdapter { private View mView; private GhostView mGhostView; private Matrix mEndMatrix; public GhostListener(View view, GhostView ghostView, Matrix endMatrix) { mView = view; mGhostView = ghostView; mEndMatrix = endMatrix; } @Override public void onTransitionEnd(Transition transition) { transition.removeListener(this); GhostView.removeGhost(mView); } @Override public void onTransitionPause(Transition transition) { mGhostView.setVisibility(View.INVISIBLE); } @Override public void onTransitionResume(Transition transition) { mGhostView.setVisibility(View.VISIBLE); } } } Loading
api/current.txt +7 −1 Original line number Diff line number Diff line Loading @@ -1028,6 +1028,8 @@ package android { field public static final int readPermission = 16842759; // 0x1010007 field public static final int recognitionService = 16843932; // 0x101049c field public static final int relinquishTaskIdentity = 16843894; // 0x1010476 field public static final int reparent = 16843965; // 0x10104bd field public static final int reparentWithOverlay = 16843966; // 0x10104be field public static final int repeatCount = 16843199; // 0x10101bf field public static final int repeatMode = 16843200; // 0x10101c0 field public static final int reqFiveWayNav = 16843314; // 0x1010232 Loading Loading @@ -31803,7 +31805,7 @@ package android.transition { ctor public ChangeBounds(android.content.Context, android.util.AttributeSet); method public void captureEndValues(android.transition.TransitionValues); method public void captureStartValues(android.transition.TransitionValues); method public void setReparent(boolean); method public deprecated void setReparent(boolean); method public void setResizeClip(boolean); } Loading @@ -31826,6 +31828,10 @@ package android.transition { ctor public ChangeTransform(android.content.Context, android.util.AttributeSet); method public void captureEndValues(android.transition.TransitionValues); method public void captureStartValues(android.transition.TransitionValues); method public boolean getReparent(); method public boolean getReparentWithOverlay(); method public void setReparent(boolean); method public void setReparentWithOverlay(boolean); } public class CircularPropagation extends android.transition.VisibilityPropagation {
core/java/android/transition/ChangeBounds.java +2 −0 Original line number Diff line number Diff line Loading @@ -107,6 +107,8 @@ public class ChangeBounds extends Transition { * * @param reparent true if the transition should track the parent * container of target views and animate parent changes. * @deprecated Use {@link android.transition.ChangeTransform} to handle * transitions between different parents. */ public void setReparent(boolean reparent) { mReparent = reparent; Loading
core/java/android/transition/ChangeImageTransform.java +1 −24 Original line number Diff line number Diff line Loading @@ -16,7 +16,6 @@ package android.transition; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.animation.TypeEvaluator; import android.content.Context; Loading Loading @@ -196,28 +195,6 @@ public class ChangeImageTransform extends Transition { private ObjectAnimator createMatrixAnimator(final ImageView imageView, Matrix startMatrix, final Matrix endMatrix) { return ObjectAnimator.ofObject(imageView, ANIMATED_TRANSFORM_PROPERTY, new MatrixEvaluator(), startMatrix, endMatrix); new TransitionUtils.MatrixEvaluator(), startMatrix, endMatrix); } private static class MatrixEvaluator implements TypeEvaluator<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) { 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; } } }
core/java/android/transition/ChangeTransform.java +352 −85 Original line number Diff line number Diff line Loading @@ -16,72 +16,134 @@ package android.transition; import android.animation.Animator; import android.animation.FloatArrayEvaluator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Matrix; import android.util.AttributeSet; import android.util.FloatProperty; import android.util.Property; import android.view.GhostView; import android.view.View; import android.view.ViewGroup; import com.android.internal.R; /** * This Transition captures scale and rotation for Views before and after the * scene change and animates those changes during the transition. * * <p>ChangeTransform does not work when the pivot changes between scenes, so either the * pivot must be set to prevent automatic pivot adjustment or the View's size must be unchanged.</p> * A change in parent is handled as well by capturing the transforms from * the parent before and after the scene change and animating those during the * transition. */ public class ChangeTransform extends Transition { private static final String TAG = "ChangeTransform"; private static final String PROPNAME_SCALE_X = "android:changeTransform:scaleX"; private static final String PROPNAME_SCALE_Y = "android:changeTransform:scaleY"; private static final String PROPNAME_ROTATION_X = "android:changeTransform:rotationX"; private static final String PROPNAME_ROTATION_Y = "android:changeTransform:rotationY"; private static final String PROPNAME_ROTATION_Z = "android:changeTransform:rotationZ"; private static final String PROPNAME_PIVOT_X = "android:changeTransform:pivotX"; private static final String PROPNAME_PIVOT_Y = "android:changeTransform:pivotY"; private static final String PROPNAME_MATRIX = "android:changeTransform:matrix"; private static final String PROPNAME_TRANSFORMS = "android:changeTransform:transforms"; private static final String PROPNAME_PARENT = "android:changeTransform:parent"; private static final String PROPNAME_PARENT_MATRIX = "android:changeTransform:parentMatrix"; private static final String[] sTransitionProperties = { PROPNAME_SCALE_X, PROPNAME_SCALE_Y, PROPNAME_ROTATION_X, PROPNAME_ROTATION_Y, PROPNAME_ROTATION_Z, PROPNAME_MATRIX, PROPNAME_TRANSFORMS, PROPNAME_PARENT_MATRIX, }; private static final FloatProperty<View>[] sChangedProperties = new FloatProperty[] { (FloatProperty) View.SCALE_X, (FloatProperty) View.SCALE_Y, (FloatProperty) View.ROTATION_X, (FloatProperty) View.ROTATION_Y, (FloatProperty) View.ROTATION, }; private static Property<View, float[]> TRANSFORMS = new Property<View, float[]>(float[].class, "transforms") { private static final Property<View, Matrix> ANIMATION_MATRIX_PROPERTY = new Property<View, Matrix>(Matrix.class, "animationMatrix") { @Override public float[] get(View object) { public Matrix get(View object) { return null; } @Override public void set(View view, float[] values) { for (int i = 0; i < values.length; i++) { float value = values[i]; if (!Float.isNaN(value)) { sChangedProperties[i].setValue(view, value); } } public void set(View object, Matrix value) { object.setAnimationMatrix(value); } }; private boolean mUseOverlay = true; private boolean mReparent = true; private Matrix mTempMatrix = new Matrix(); public ChangeTransform() {} public ChangeTransform(Context context, AttributeSet attrs) { super(context, attrs); TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ChangeTransform); mUseOverlay = a.getBoolean(R.styleable.ChangeTransform_reparentWithOverlay, true); mReparent = a.getBoolean(R.styleable.ChangeTransform_reparent, true); a.recycle(); } /** * Returns whether changes to parent should use an overlay or not. When the parent * change doesn't use an overlay, it affects the transforms of the child. The * default value is <code>true</code>. * * <p>Note: when Overlays are not used when a parent changes, a view can be clipped when * it moves outside the bounds of its parent. Setting * {@link android.view.ViewGroup#setClipChildren(boolean)} and * {@link android.view.ViewGroup#setClipToPadding(boolean)} can help. Also, when * Overlays are not used and the parent is animating its location, the position of the * child view will be relative to its parent's final position, so it may appear to "jump" * at the beginning.</p> * * @return <code>true</code> when a changed parent should execute the transition * inside the scene root's overlay or <code>false</code> if a parent change only * affects the transform of the transitioning view. */ public boolean getReparentWithOverlay() { return mUseOverlay; } /** * Sets whether changes to parent should use an overlay or not. When the parent * change doesn't use an overlay, it affects the transforms of the child. The * default value is <code>true</code>. * * <p>Note: when Overlays are not used when a parent changes, a view can be clipped when * it moves outside the bounds of its parent. Setting * {@link android.view.ViewGroup#setClipChildren(boolean)} and * {@link android.view.ViewGroup#setClipToPadding(boolean)} can help. Also, when * Overlays are not used and the parent is animating its location, the position of the * child view will be relative to its parent's final position, so it may appear to "jump" * at the beginning.</p> * * @return <code>true</code> when a changed parent should execute the transition * inside the scene root's overlay or <code>false</code> if a parent change only * affects the transform of the transitioning view. */ public void setReparentWithOverlay(boolean reparentWithOverlay) { mUseOverlay = reparentWithOverlay; } /** * Returns whether parent changes will be tracked by the ChangeTransform. If parent * changes are tracked, then the transform will adjust to the transforms of the * different parents. If they aren't tracked, only the transforms of the transitioning * view will be tracked. Default is true. * * @return whether parent changes will be tracked by the ChangeTransform. */ public boolean getReparent() { return mReparent; } /** * Sets whether parent changes will be tracked by the ChangeTransform. If parent * changes are tracked, then the transform will adjust to the transforms of the * different parents. If they aren't tracked, only the transforms of the transitioning * view will be tracked. Default is true. * * @param reparent Set to true to track parent changes or false to only track changes * of the transitioning view without considering the parent change. */ public void setReparent(boolean reparent) { mReparent = reparent; } @Override Loading @@ -89,19 +151,29 @@ public class ChangeTransform extends Transition { return sTransitionProperties; } private void captureValues(TransitionValues values) { View view = values.view; private void captureValues(TransitionValues transitionValues) { View view = transitionValues.view; if (view.getVisibility() == View.GONE) { return; } values.values.put(PROPNAME_SCALE_X, view.getScaleX()); values.values.put(PROPNAME_SCALE_Y, view.getScaleY()); values.values.put(PROPNAME_PIVOT_X, view.getPivotX()); values.values.put(PROPNAME_PIVOT_Y, view.getPivotY()); values.values.put(PROPNAME_ROTATION_X, view.getRotationX()); values.values.put(PROPNAME_ROTATION_Y, view.getRotationY()); values.values.put(PROPNAME_ROTATION_Z, view.getRotation()); transitionValues.values.put(PROPNAME_PARENT, view.getParent()); Transforms transforms = new Transforms(view); transitionValues.values.put(PROPNAME_TRANSFORMS, transforms); Matrix matrix = view.getMatrix(); if (matrix == null || matrix.isIdentity()) { matrix = null; } else { matrix = new Matrix(matrix); } transitionValues.values.put(PROPNAME_MATRIX, matrix); if (mReparent) { Matrix parentMatrix = new Matrix(); ViewGroup parent = (ViewGroup) view.getParent(); parent.transformMatrixToGlobal(parentMatrix); parentMatrix.preTranslate(-parent.getScrollX(), -parent.getScrollY()); transitionValues.values.put(PROPNAME_PARENT_MATRIX, parentMatrix); } return; } @Override Loading @@ -115,57 +187,252 @@ public class ChangeTransform extends Transition { } @Override public Animator createAnimator(final ViewGroup sceneRoot, TransitionValues startValues, public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, TransitionValues endValues) { if (startValues == null || endValues == null || !startValues.values.containsKey(PROPNAME_SCALE_X) || !endValues.values.containsKey(PROPNAME_SCALE_X) || !isPivotSame(startValues, endValues) || !isChanged(startValues, endValues)) { if (startValues == null || endValues == null || !startValues.values.containsKey(PROPNAME_PARENT) || !endValues.values.containsKey(PROPNAME_PARENT)) { return null; } float[] start = createValues(startValues); float[] end = createValues(endValues); for (int i = 0; i < start.length; i++) { if (start[i] == end[i]) { start[i] = Float.NaN; end[i] = Float.NaN; ViewGroup startParent = (ViewGroup) startValues.values.get(PROPNAME_PARENT); ViewGroup endParent = (ViewGroup) endValues.values.get(PROPNAME_PARENT); boolean handleParentChange = mReparent && !parentsMatch(startParent, endParent); Matrix startMatrix = (Matrix) startValues.view.getTag(R.id.transitionTransform); if (startMatrix != null) { startValues.values.put(PROPNAME_MATRIX, startMatrix); } Matrix startParentMatrix = (Matrix) startValues.view.getTag(R.id.parentMatrix); if (startParentMatrix != null) { startValues.values.put(PROPNAME_PARENT_MATRIX, startParentMatrix); } // First handle the parent change: if (handleParentChange) { setMatricesForParent(startValues, endValues); } // Next handle the normal matrix transform: ObjectAnimator transformAnimator = createTransformAnimator(startValues, endValues); if (handleParentChange && transformAnimator != null && mUseOverlay) { createGhostView(sceneRoot, startValues, endValues); } return transformAnimator; } private ObjectAnimator createTransformAnimator(TransitionValues startValues, TransitionValues endValues) { Matrix startMatrix = (Matrix) startValues.values.get(PROPNAME_MATRIX); Matrix endMatrix = (Matrix) endValues.values.get(PROPNAME_MATRIX); if (startMatrix == null) { startMatrix = Matrix.IDENTITY_MATRIX; } if (endMatrix == null) { endMatrix = Matrix.IDENTITY_MATRIX; } if (startMatrix.equals(endMatrix)) { return null; } final Transforms transforms = (Transforms) endValues.values.get(PROPNAME_TRANSFORMS); // clear the transform properties so that we can use the animation matrix instead final View view = endValues.view; setIdentityTransforms(view); ObjectAnimator animator = ObjectAnimator.ofObject(view, ANIMATION_MATRIX_PROPERTY, new TransitionUtils.MatrixEvaluator(), startMatrix, endMatrix); AnimatorListenerAdapter listener = new AnimatorListenerAdapter() { private boolean mIsCanceled; private Matrix mTempMatrix; @Override public void onAnimationCancel(Animator animation) { mIsCanceled = true; } @Override public void onAnimationEnd(Animator animation) { if (!mIsCanceled) { view.setTagInternal(R.id.transitionTransform, null); view.setTagInternal(R.id.parentMatrix, null); } ANIMATION_MATRIX_PROPERTY.set(view, null); transforms.restore(view); } @Override public void onAnimationPause(Animator animation) { ValueAnimator animator = (ValueAnimator) animation; Matrix currentMatrix = (Matrix) animator.getAnimatedValue(); if (mTempMatrix == null) { mTempMatrix = new Matrix(currentMatrix); } else { sChangedProperties[i].setValue(endValues.view, start[i]); mTempMatrix.set(currentMatrix); } view.setTagInternal(R.id.transitionTransform, mTempMatrix); transforms.restore(view); } FloatArrayEvaluator evaluator = new FloatArrayEvaluator(new float[start.length]); return ObjectAnimator.ofObject(endValues.view, TRANSFORMS, evaluator, start, end); @Override public void onAnimationResume(Animator animation) { setIdentityTransforms(view); } }; private static float[] createValues(TransitionValues transitionValues) { float[] values = new float[sChangedProperties.length]; for (int i = 0; i < values.length; i++) { values[i] = (Float) transitionValues.values.get(sTransitionProperties[i]); animator.addListener(listener); animator.addPauseListener(listener); return animator; } return values; private boolean parentsMatch(ViewGroup startParent, ViewGroup endParent) { boolean parentsMatch = false; if (!isValidTarget(startParent) || !isValidTarget(endParent)) { parentsMatch = startParent == endParent; } else { TransitionValues endValues = getMatchedTransitionValues(startParent, true); if (endValues != null) { parentsMatch = endParent == endValues.view; } } return parentsMatch; } private static boolean isPivotSame(TransitionValues startValues, TransitionValues endValues) { float startPivotX = (Float) startValues.values.get(PROPNAME_PIVOT_X); float startPivotY = (Float) startValues.values.get(PROPNAME_PIVOT_Y); float endPivotX = (Float) endValues.values.get(PROPNAME_PIVOT_X); float endPivotY = (Float) endValues.values.get(PROPNAME_PIVOT_Y); private void createGhostView(final ViewGroup sceneRoot, TransitionValues startValues, TransitionValues endValues) { View view = endValues.view; Matrix endMatrix = (Matrix) endValues.values.get(PROPNAME_PARENT_MATRIX); Matrix localEndMatrix = new Matrix(endMatrix); sceneRoot.transformMatrixToLocal(localEndMatrix); GhostView ghostView = GhostView.addGhost(view, sceneRoot, localEndMatrix); // We don't support pivot changes, because they could be automatically set // and we can't end the state in an automatic state. return startPivotX == endPivotX && startPivotY == endPivotY; Transition outerTransition = this; while (outerTransition.mParent != null) { outerTransition = outerTransition.mParent; } GhostListener listener = new GhostListener(view, ghostView, endMatrix); outerTransition.addListener(listener); private static boolean isChanged(TransitionValues startValues, TransitionValues endValues) { for (int i = 0; i < sChangedProperties.length; i++) { Object start = startValues.values.get(sTransitionProperties[i]); Object end = endValues.values.get(sTransitionProperties[i]); if (!start.equals(end)) { return true; if (startValues.view != endValues.view) { startValues.view.setTransitionAlpha(0); } view.setTransitionAlpha(1); } private void setMatricesForParent(TransitionValues startValues, TransitionValues endValues) { Matrix endParentMatrix = (Matrix) endValues.values.get(PROPNAME_PARENT_MATRIX); endValues.view.setTagInternal(R.id.parentMatrix, endParentMatrix); Matrix toLocal = mTempMatrix; toLocal.reset(); endParentMatrix.invert(toLocal); Matrix startLocal = (Matrix) startValues.values.get(PROPNAME_MATRIX); if (startLocal == null) { startLocal = new Matrix(); startValues.values.put(PROPNAME_MATRIX, startLocal); } Matrix startParentMatrix = (Matrix) startValues.values.get(PROPNAME_PARENT_MATRIX); startLocal.postConcat(startParentMatrix); startLocal.postConcat(toLocal); } private static void setIdentityTransforms(View view) { setTransforms(view, 0, 0, 0, 1, 1, 0, 0, 0); } private static void setTransforms(View view, float translationX, float translationY, float translationZ, float scaleX, float scaleY, float rotationX, float rotationY, float rotationZ) { view.setTranslationX(translationX); view.setTranslationY(translationY); view.setTranslationZ(translationZ); view.setScaleX(scaleX); view.setScaleY(scaleY); view.setRotationX(rotationX); view.setRotationY(rotationY); view.setRotation(rotationZ); } private static class Transforms { public final float translationX; public final float translationY; public final float translationZ; public final float scaleX; public final float scaleY; public final float rotationX; public final float rotationY; public final float rotationZ; public Transforms(View view) { translationX = view.getTranslationX(); translationY = view.getTranslationY(); translationZ = view.getTranslationZ(); scaleX = view.getScaleX(); scaleY = view.getScaleY(); rotationX = view.getRotationX(); rotationY = view.getRotationY(); rotationZ = view.getRotation(); } public void restore(View view) { setTransforms(view, translationX, translationY, translationZ, scaleX, scaleY, rotationX, rotationY, rotationZ); } @Override public boolean equals(Object that) { if (!(that instanceof Transforms)) { return false; } Transforms thatTransform = (Transforms) that; return thatTransform.translationX == translationX && thatTransform.translationY == translationY && thatTransform.translationZ == translationZ && thatTransform.scaleX == scaleX && thatTransform.scaleY == scaleY && thatTransform.rotationX == rotationX && thatTransform.rotationY == rotationY && thatTransform.rotationZ == rotationZ; } } private static class GhostListener extends Transition.TransitionListenerAdapter { private View mView; private GhostView mGhostView; private Matrix mEndMatrix; public GhostListener(View view, GhostView ghostView, Matrix endMatrix) { mView = view; mGhostView = ghostView; mEndMatrix = endMatrix; } @Override public void onTransitionEnd(Transition transition) { transition.removeListener(this); GhostView.removeGhost(mView); } @Override public void onTransitionPause(Transition transition) { mGhostView.setVisibility(View.INVISIBLE); } @Override public void onTransitionResume(Transition transition) { mGhostView.setVisibility(View.VISIBLE); } } }