All Downloads are FREE. Search and download functionalities are using the official Maven repository.

android.transition.ChangeTransform Maven / Gradle / Ivy

Go to download

A library jar that provides APIs for Applications written for the Google Android Platform.

There is a newer version: 14-robolectric-10818077
Show newest version
/*
 * 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.FloatArrayEvaluator;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Matrix;
import android.graphics.Path;
import android.graphics.PointF;
import android.util.AttributeSet;
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.
 *
 * 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_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 PROPNAME_INTERMEDIATE_PARENT_MATRIX =
            "android:changeTransform:intermediateParentMatrix";
    private static final String PROPNAME_INTERMEDIATE_MATRIX =
            "android:changeTransform:intermediateMatrix";

    private static final String[] sTransitionProperties = {
            PROPNAME_MATRIX,
            PROPNAME_TRANSFORMS,
            PROPNAME_PARENT_MATRIX,
    };

    /**
     * This property sets the animation matrix properties that are not translations.
     */
    private static final Property NON_TRANSLATIONS_PROPERTY =
            new Property(float[].class, "nonTranslations") {
                @Override
                public float[] get(PathAnimatorMatrix object) {
                    return null;
                }

                @Override
                public void set(PathAnimatorMatrix object, float[] value) {
                    object.setValues(value);
                }
            };

    /**
     * This property sets the translation animation matrix properties.
     */
    private static final Property TRANSLATIONS_PROPERTY =
            new Property(PointF.class, "translations") {
                @Override
                public PointF get(PathAnimatorMatrix object) {
                    return null;
                }

                @Override
                public void set(PathAnimatorMatrix object, PointF value) {
                    object.setTranslation(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 true.
     *
     * 

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.

* * @return true when a changed parent should execute the transition * inside the scene root's overlay or false if a parent change only * affects the transform of the transitioning view. * @attr ref android.R.styleable#ChangeTransform_reparentWithOverlay */ 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 true. * *

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.

* * @return true when a changed parent should execute the transition * inside the scene root's overlay or false if a parent change only * affects the transform of the transitioning view. * @attr ref android.R.styleable#ChangeTransform_reparentWithOverlay */ 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. * @attr ref android.R.styleable#ChangeTransform_reparent */ 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. * @attr ref android.R.styleable#ChangeTransform_reparent */ public void setReparent(boolean reparent) { mReparent = reparent; } @Override public String[] getTransitionProperties() { return sTransitionProperties; } private void captureValues(TransitionValues transitionValues) { View view = transitionValues.view; if (view.getVisibility() == View.GONE) { return; } 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); transitionValues.values.put(PROPNAME_INTERMEDIATE_MATRIX, view.getTag(R.id.transitionTransform)); transitionValues.values.put(PROPNAME_INTERMEDIATE_PARENT_MATRIX, view.getTag(R.id.parentMatrix)); } return; } @Override public void captureStartValues(TransitionValues transitionValues) { captureValues(transitionValues); } @Override public void captureEndValues(TransitionValues transitionValues) { captureValues(transitionValues); } @Override public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, TransitionValues endValues) { if (startValues == null || endValues == null || !startValues.values.containsKey(PROPNAME_PARENT) || !endValues.values.containsKey(PROPNAME_PARENT)) { return null; } 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.values.get(PROPNAME_INTERMEDIATE_MATRIX); if (startMatrix != null) { startValues.values.put(PROPNAME_MATRIX, startMatrix); } Matrix startParentMatrix = (Matrix) startValues.values.get(PROPNAME_INTERMEDIATE_PARENT_MATRIX); 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, handleParentChange); if (handleParentChange && transformAnimator != null && mUseOverlay) { createGhostView(sceneRoot, startValues, endValues); } return transformAnimator; } private ObjectAnimator createTransformAnimator(TransitionValues startValues, TransitionValues endValues, final boolean handleParentChange) { 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); final float[] startMatrixValues = new float[9]; startMatrix.getValues(startMatrixValues); final float[] endMatrixValues = new float[9]; endMatrix.getValues(endMatrixValues); final PathAnimatorMatrix pathAnimatorMatrix = new PathAnimatorMatrix(view, startMatrixValues); PropertyValuesHolder valuesProperty = PropertyValuesHolder.ofObject( NON_TRANSLATIONS_PROPERTY, new FloatArrayEvaluator(new float[9]), startMatrixValues, endMatrixValues); Path path = getPathMotion().getPath(startMatrixValues[Matrix.MTRANS_X], startMatrixValues[Matrix.MTRANS_Y], endMatrixValues[Matrix.MTRANS_X], endMatrixValues[Matrix.MTRANS_Y]); PropertyValuesHolder translationProperty = PropertyValuesHolder.ofObject( TRANSLATIONS_PROPERTY, null, path); ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(pathAnimatorMatrix, valuesProperty, translationProperty); final Matrix finalEndMatrix = endMatrix; AnimatorListenerAdapter listener = new AnimatorListenerAdapter() { private boolean mIsCanceled; private Matrix mTempMatrix = new Matrix(); @Override public void onAnimationCancel(Animator animation) { mIsCanceled = true; } @Override public void onAnimationEnd(Animator animation) { if (!mIsCanceled) { if (handleParentChange && mUseOverlay) { setCurrentMatrix(finalEndMatrix); } else { view.setTagInternal(R.id.transitionTransform, null); view.setTagInternal(R.id.parentMatrix, null); } } view.setAnimationMatrix(null); transforms.restore(view); } @Override public void onAnimationPause(Animator animation) { Matrix currentMatrix = pathAnimatorMatrix.getMatrix(); setCurrentMatrix(currentMatrix); } @Override public void onAnimationResume(Animator animation) { setIdentityTransforms(view); } private void setCurrentMatrix(Matrix currentMatrix) { mTempMatrix.set(currentMatrix); view.setTagInternal(R.id.transitionTransform, mTempMatrix); transforms.restore(view); } }; animator.addListener(listener); animator.addPauseListener(listener); return animator; } 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 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); Transition outerTransition = this; while (outerTransition.mParent != null) { outerTransition = outerTransition.mParent; } GhostListener listener = new GhostListener(view, startValues.view, ghostView); outerTransition.addListener(listener); 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 View mStartView; private GhostView mGhostView; public GhostListener(View view, View startView, GhostView ghostView) { mView = view; mStartView = startView; mGhostView = ghostView; } @Override public void onTransitionEnd(Transition transition) { transition.removeListener(this); GhostView.removeGhost(mView); mView.setTagInternal(R.id.transitionTransform, null); mView.setTagInternal(R.id.parentMatrix, null); mStartView.setTransitionAlpha(1); } @Override public void onTransitionPause(Transition transition) { mGhostView.setVisibility(View.INVISIBLE); } @Override public void onTransitionResume(Transition transition) { mGhostView.setVisibility(View.VISIBLE); } } /** * PathAnimatorMatrix allows the translations and the rest of the matrix to be set * separately. This allows the PathMotion to affect the translations while scale * and rotation are evaluated separately. */ private static class PathAnimatorMatrix { private final Matrix mMatrix = new Matrix(); private final View mView; private final float[] mValues; private float mTranslationX; private float mTranslationY; public PathAnimatorMatrix(View view, float[] values) { mView = view; mValues = values.clone(); mTranslationX = mValues[Matrix.MTRANS_X]; mTranslationY = mValues[Matrix.MTRANS_Y]; setAnimationMatrix(); } public void setValues(float[] values) { System.arraycopy(values, 0, mValues, 0, values.length); setAnimationMatrix(); } public void setTranslation(PointF translation) { mTranslationX = translation.x; mTranslationY = translation.y; setAnimationMatrix(); } private void setAnimationMatrix() { mValues[Matrix.MTRANS_X] = mTranslationX; mValues[Matrix.MTRANS_Y] = mTranslationY; mMatrix.setValues(mValues); mView.setAnimationMatrix(mMatrix); } public Matrix getMatrix() { return mMatrix; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy