Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 99db6030 authored by George Mount's avatar George Mount Committed by Android (Google) Code Review
Browse files

Merge "Add Parent Change to ChangeTransform" into lmp-dev

parents b68654ce c94e2b39
Loading
Loading
Loading
Loading
+7 −1
Original line number Diff line number Diff line
@@ -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
@@ -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);
  }
@@ -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 {
+2 −0
Original line number Diff line number Diff line
@@ -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;
+1 −24
Original line number Diff line number Diff line
@@ -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;
@@ -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;
        }
    }

}
+352 −85
Original line number Diff line number Diff line
@@ -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
@@ -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
@@ -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