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

Commit 990205ea authored by George Mount's avatar George Mount
Browse files

Don't use overlay to transition ImageViews.

Bug 15744992

MoveImage used an overlay to transition ImageViews. This
caused strange problems when ImageViews were contained in
other Views. The new ChangeImageTransform does a smooth
transition for changes in scale type and bounds.

Change-Id: Ia5021f4828f8f818a8699b3bdd38437aeba1cfc8
parent 16ffa8d3
Loading
Loading
Loading
Loading
+6 −6
Original line number Diff line number Diff line
@@ -30466,6 +30466,12 @@ package android.transition {
    method public void captureStartValues(android.transition.TransitionValues);
  }
  public class ChangeImageTransform extends android.transition.Transition {
    ctor public ChangeImageTransform();
    method public void captureEndValues(android.transition.TransitionValues);
    method public void captureStartValues(android.transition.TransitionValues);
  }
  public class ChangeTransform extends android.transition.Transition {
    ctor public ChangeTransform();
    method public void captureEndValues(android.transition.TransitionValues);
@@ -30489,12 +30495,6 @@ package android.transition {
    field public static final int OUT = 2; // 0x2
  }
  public class MoveImage extends android.transition.Transition {
    ctor public MoveImage();
    method public void captureEndValues(android.transition.TransitionValues);
    method public void captureStartValues(android.transition.TransitionValues);
  }
  public final class Scene {
    ctor public Scene(android.view.ViewGroup);
    ctor public Scene(android.view.ViewGroup, android.view.View);
+234 −0
Original line number Diff line number Diff line
/*
 * 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.TypeEvaluator;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.Property;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;

import java.util.Map;

/**
 * Transitions changes in ImageView {@link ImageView#setScaleType(ImageView.ScaleType)} as
 * well as image scaling due to ImageView size changes. When combined with
 * {@link android.transition.ChangeBounds}, an ImageView that changes size will
 * scale smoothly.
 */
public class ChangeImageTransform extends Transition {

    private static final String TAG = "ChangeScaleType";

    private static final String PROPNAME_MATRIX = "android:changeScaleType:matrix";
    private static final String PROPNAME_BOUNDS = "android:changeScaleType:bounds";

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

    private static TypeEvaluator<Matrix> NULL_MATRIX_EVALUATOR = new TypeEvaluator<Matrix>() {
        @Override
        public Matrix evaluate(float fraction, Matrix startValue, Matrix endValue) {
            return null;
        }
    };

    private static Property<ImageView, Matrix> ANIMATED_TRANSFORM_PROPERTY
            = new Property<ImageView, Matrix>(Matrix.class, "animatedTransform") {
        @Override
        public void set(ImageView object, Matrix value) {
            object.animateTransform(value);
        }

        @Override
        public Matrix get(ImageView object) {
            return null;
        }
    };

    private void captureValues(TransitionValues transitionValues) {
        View view = transitionValues.view;
        if (!(view instanceof ImageView) || view.getVisibility() != View.VISIBLE) {
            return;
        }
        ImageView imageView = (ImageView) view;
        Drawable drawable = imageView.getDrawable();
        if (drawable == null) {
            return;
        }
        Map<String, Object> values = transitionValues.values;

        int left = view.getLeft();
        int top = view.getTop();
        int right = view.getRight();
        int bottom = view.getBottom();

        Rect bounds = new Rect(left, top, right, bottom);
        values.put(PROPNAME_BOUNDS, bounds);
        Matrix matrix;
        ImageView.ScaleType scaleType = imageView.getScaleType();
        if (scaleType == ImageView.ScaleType.FIT_XY) {
            matrix = imageView.getImageMatrix();
            if (!matrix.isIdentity()) {
                matrix = new Matrix(matrix);
            } else {
                int drawableWidth = drawable.getIntrinsicWidth();
                int drawableHeight = drawable.getIntrinsicHeight();
                if (drawableWidth > 0 && drawableHeight > 0) {
                    float scaleX = ((float) bounds.width()) / drawableWidth;
                    float scaleY = ((float) bounds.height()) / drawableHeight;
                    matrix = new Matrix();
                    matrix.setScale(scaleX, scaleY);
                } else {
                    matrix = null;
                }
            }
        } else {
            matrix = new Matrix(imageView.getImageMatrix());
        }
        values.put(PROPNAME_MATRIX, matrix);
    }

    @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) {
            return null;
        }
        Rect startBounds = (Rect) startValues.values.get(PROPNAME_BOUNDS);
        Rect endBounds = (Rect) endValues.values.get(PROPNAME_BOUNDS);
        if (startBounds == null || endBounds == null) {
            return null;
        }

        Matrix startMatrix = (Matrix) startValues.values.get(PROPNAME_MATRIX);
        Matrix endMatrix = (Matrix) endValues.values.get(PROPNAME_MATRIX);

        boolean matricesEqual = (startMatrix == null && endMatrix == null) ||
                (startMatrix != null && startMatrix.equals(endMatrix));

        if (startBounds.equals(endBounds) && matricesEqual) {
            return null;
        }

        ImageView imageView = (ImageView) endValues.view;
        Drawable drawable = imageView.getDrawable();
        int drawableWidth = drawable.getIntrinsicWidth();
        int drawableHeight = drawable.getIntrinsicHeight();

        ObjectAnimator animator;
        if (drawableWidth == 0 || drawableHeight == 0) {
            animator = createNullAnimator(imageView);
        } else {
            if (startMatrix == null) {
                startMatrix = Matrix.IDENTITY_MATRIX;
            }
            if (endMatrix == null) {
                endMatrix = Matrix.IDENTITY_MATRIX;
            }
            animator = createMatrixAnimator(imageView, startMatrix, endMatrix);
        }
        return animator;
    }

    private ObjectAnimator createNullAnimator(ImageView imageView) {
        return ObjectAnimator.ofObject(imageView, ANIMATED_TRANSFORM_PROPERTY,
                NULL_MATRIX_EVALUATOR, null, null);
    }

    private ObjectAnimator createMatrixAnimator(final ImageView imageView, Matrix startMatrix,
            final Matrix endMatrix) {
        ObjectAnimator animator = ObjectAnimator.ofObject(imageView, ANIMATED_TRANSFORM_PROPERTY,
                new MatrixEvaluator(), startMatrix, endMatrix);
        /*
        AnimatorListenerAdapter listener = new AnimatorListenerAdapter() {
            private Matrix mPausedMatrix;

            @Override
            public void onAnimationPause(Animator animation) {
                if (mPausedMatrix == null) {
                    mPausedMatrix = new Matrix();
                }
                Matrix imageMatrix = imageView.getImageMatrix();
                mPausedMatrix.set(imageMatrix);
                imageView.animateTransform(endMatrix);
            }

            @Override
            public void onAnimationResume(Animator animation) {
                imageView.animateTransform(mPausedMatrix);
            }
        };
        animator.addPauseListener(listener);
        */
        return animator;
    }

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

}
+0 −300
Original line number Diff line number Diff line
/*
 * 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.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.Property;

/**
 * Used in MoveImage to mock an ImageView as a Drawable to be scaled in the scene root Overlay.
 * @hide
 */
class MatrixClippedDrawable extends Drawable implements Drawable.Callback {
    private static final String TAG = "MatrixClippedDrawable";

    private ClippedMatrixState mClippedMatrixState;

    public static final Property<MatrixClippedDrawable, Rect> CLIP_PROPERTY
            = new Property<MatrixClippedDrawable, Rect>(Rect.class, "clipRect") {

        @Override
        public Rect get(MatrixClippedDrawable object) {
            return object.getClipRect();
        }

        @Override
        public void set(MatrixClippedDrawable object, Rect value) {
            object.setClipRect(value);
        }
    };

    public static final Property<MatrixClippedDrawable, Matrix> MATRIX_PROPERTY
            = new Property<MatrixClippedDrawable, Matrix>(Matrix.class, "matrix") {
        @Override
        public void set(MatrixClippedDrawable object, Matrix value) {
            object.setMatrix(value);
        }

        @Override
        public Matrix get(MatrixClippedDrawable object) {
            return object.getMatrix();
        }
    };

    public MatrixClippedDrawable(Drawable drawable) {
        this(null, null);

        mClippedMatrixState.mDrawable = drawable;

        if (drawable != null) {
            drawable.setCallback(this);
        }
    }

    public void setMatrix(Matrix matrix) {
        if (matrix == null) {
            mClippedMatrixState.mMatrix = null;
        } else {
            if (mClippedMatrixState.mMatrix == null) {
                mClippedMatrixState.mMatrix = new Matrix();
            }
            mClippedMatrixState.mMatrix.set(matrix);
        }
        invalidateSelf();
    }

    public Matrix getMatrix() {
        return mClippedMatrixState.mMatrix;
    }

    public Rect getClipRect() {
        return mClippedMatrixState.mClipRect;
    }

    public void setClipRect(Rect clipRect) {
        if (clipRect == null) {
            if (mClippedMatrixState.mClipRect != null) {
                mClippedMatrixState.mClipRect = null;
                invalidateSelf();
            }
        } else {
            if (mClippedMatrixState.mClipRect == null) {
                mClippedMatrixState.mClipRect = new Rect(clipRect);
            } else {
                mClippedMatrixState.mClipRect.set(clipRect);
            }
            invalidateSelf();
        }
    }

    // overrides from Drawable.Callback

    public void invalidateDrawable(Drawable who) {
        final Drawable.Callback callback = getCallback();
        if (callback != null) {
            callback.invalidateDrawable(this);
        }
    }

    public void scheduleDrawable(Drawable who, Runnable what, long when) {
        final Drawable.Callback callback = getCallback();
        if (callback != null) {
            callback.scheduleDrawable(this, what, when);
        }
    }

    public void unscheduleDrawable(Drawable who, Runnable what) {
        final Drawable.Callback callback = getCallback();
        if (callback != null) {
            callback.unscheduleDrawable(this, what);
        }
    }

    // overrides from Drawable

    @Override
    public int getChangingConfigurations() {
        return super.getChangingConfigurations()
                | mClippedMatrixState.mChangingConfigurations
                | mClippedMatrixState.mDrawable.getChangingConfigurations();
    }

    @Override
    public boolean getPadding(Rect padding) {
        // XXX need to adjust padding!
        return mClippedMatrixState.mDrawable.getPadding(padding);
    }

    @Override
    public boolean setVisible(boolean visible, boolean restart) {
        mClippedMatrixState.mDrawable.setVisible(visible, restart);
        return super.setVisible(visible, restart);
    }

    @Override
    public void setAlpha(int alpha) {
        mClippedMatrixState.mDrawable.setAlpha(alpha);
    }

    @Override
    public int getAlpha() {
        return mClippedMatrixState.mDrawable.getAlpha();
    }

    @Override
    public void setColorFilter(ColorFilter cf) {
        mClippedMatrixState.mDrawable.setColorFilter(cf);
    }

    @Override
    public int getOpacity() {
        return mClippedMatrixState.mDrawable.getOpacity();
    }

    @Override
    public boolean isStateful() {
        return mClippedMatrixState.mDrawable.isStateful();
    }

    @Override
    protected boolean onStateChange(int[] state) {
        return mClippedMatrixState.mDrawable.setState(state);
    }

    @Override
    protected boolean onLevelChange(int level) {
        mClippedMatrixState.mDrawable.setLevel(level);
        invalidateSelf();
        return true;
    }

    @Override
    protected void onBoundsChange(Rect bounds) {
        super.setBounds(bounds);
        if (mClippedMatrixState.mMatrix == null) {
            mClippedMatrixState.mDrawable.setBounds(bounds);
        } else {
            int drawableWidth = mClippedMatrixState.mDrawable.getIntrinsicWidth();
            int drawableHeight = mClippedMatrixState.mDrawable.getIntrinsicHeight();
            mClippedMatrixState.mDrawable.setBounds(bounds.left, bounds.top,
                    drawableWidth + bounds.left, drawableHeight + bounds.top);
        }
        invalidateSelf();
    }

    @Override
    public void draw(Canvas canvas) {
        Rect bounds = getBounds();
        int left = bounds.left;
        int top = bounds.top;
        int saveCount = canvas.getSaveCount();
        canvas.save();
        if (mClippedMatrixState.mClipRect != null) {
            canvas.clipRect(mClippedMatrixState.mClipRect);
        } else {
            canvas.clipRect(bounds);
        }

        if (mClippedMatrixState != null && !mClippedMatrixState.mMatrix.isIdentity()) {
            canvas.translate(left, top);
            canvas.concat(mClippedMatrixState.mMatrix);
            canvas.translate(-left, -top);
        }
        mClippedMatrixState.mDrawable.draw(canvas);
        canvas.restoreToCount(saveCount);
    }

    @Override
    public int getIntrinsicWidth() {
        return mClippedMatrixState.mDrawable.getIntrinsicWidth();
    }

    @Override
    public int getIntrinsicHeight() {
        return mClippedMatrixState.mDrawable.getIntrinsicHeight();
    }

    @Override
    public Drawable.ConstantState getConstantState() {
        if (mClippedMatrixState.canConstantState()) {
            mClippedMatrixState.mChangingConfigurations = getChangingConfigurations();
            return mClippedMatrixState;
        }
        return null;
    }

    final static class ClippedMatrixState extends Drawable.ConstantState {
        Drawable mDrawable;
        Matrix mMatrix;
        Rect mClipRect;

        private boolean mCheckedConstantState;
        private boolean mCanConstantState;
        int mChangingConfigurations;

        ClippedMatrixState(ClippedMatrixState orig, MatrixClippedDrawable owner, Resources res) {
            if (orig != null) {
                if (res != null) {
                    mDrawable = orig.mDrawable.getConstantState().newDrawable(res);
                } else {
                    mDrawable = orig.mDrawable.getConstantState().newDrawable();
                }
                mDrawable.setCallback(owner);
                mCheckedConstantState = mCanConstantState = true;
                if (orig.mMatrix != null) {
                    mMatrix = new Matrix(orig.mMatrix);
                }
                if (orig.mClipRect != null) {
                    mClipRect = new Rect(orig.mClipRect);
                }
            }
        }

        @Override
        public Drawable newDrawable() {
            return new MatrixClippedDrawable(this, null);
        }

        @Override
        public Drawable newDrawable(Resources res) {
            return new MatrixClippedDrawable(this, res);
        }

        @Override
        public int getChangingConfigurations() {
            return mChangingConfigurations;
        }

        boolean canConstantState() {
            if (!mCheckedConstantState) {
                mCanConstantState = mDrawable.getConstantState() != null;
                mCheckedConstantState = true;
            }

            return mCanConstantState;
        }
    }

    private MatrixClippedDrawable(ClippedMatrixState state, Resources res) {
        mClippedMatrixState = new ClippedMatrixState(state, this, res);
    }

}
+9 −332

File changed.

Preview size limit exceeded, changes collapsed.

+2 −2
Original line number Diff line number Diff line
@@ -68,8 +68,8 @@ import java.util.List;
 *
 * <p>This TransitionSet contains {@link android.transition.Explode} for visibility,
 * {@link android.transition.ChangeBounds}, {@link android.transition.ChangeTransform},
 * and {@link android.transition.ChangeClipBounds} for non-<code>ImageView</code>s and
 * {@link android.transition.MoveImage} for <code>ImageView</code>s:</p>
 * and {@link android.transition.ChangeClipBounds} and
 * {@link android.transition.ChangeImageTransform}:</p>
 *
 * {@sample development/samples/ApiDemos/res/transition/explode_move_together.xml MultipleTransform}
 *
Loading