1990205eada00ad3e575761d19607bb03e12f9aa3George Mount/*
2990205eada00ad3e575761d19607bb03e12f9aa3George Mount * Copyright (C) 2014 The Android Open Source Project
3990205eada00ad3e575761d19607bb03e12f9aa3George Mount *
4990205eada00ad3e575761d19607bb03e12f9aa3George Mount * Licensed under the Apache License, Version 2.0 (the "License");
5990205eada00ad3e575761d19607bb03e12f9aa3George Mount * you may not use this file except in compliance with the License.
6990205eada00ad3e575761d19607bb03e12f9aa3George Mount * You may obtain a copy of the License at
7990205eada00ad3e575761d19607bb03e12f9aa3George Mount *
8990205eada00ad3e575761d19607bb03e12f9aa3George Mount *      http://www.apache.org/licenses/LICENSE-2.0
9990205eada00ad3e575761d19607bb03e12f9aa3George Mount *
10990205eada00ad3e575761d19607bb03e12f9aa3George Mount * Unless required by applicable law or agreed to in writing, software
11990205eada00ad3e575761d19607bb03e12f9aa3George Mount * distributed under the License is distributed on an "AS IS" BASIS,
12990205eada00ad3e575761d19607bb03e12f9aa3George Mount * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13990205eada00ad3e575761d19607bb03e12f9aa3George Mount * See the License for the specific language governing permissions and
14990205eada00ad3e575761d19607bb03e12f9aa3George Mount * limitations under the License.
15990205eada00ad3e575761d19607bb03e12f9aa3George Mount */
16990205eada00ad3e575761d19607bb03e12f9aa3George Mountpackage android.transition;
17990205eada00ad3e575761d19607bb03e12f9aa3George Mount
18990205eada00ad3e575761d19607bb03e12f9aa3George Mountimport android.animation.Animator;
19990205eada00ad3e575761d19607bb03e12f9aa3George Mountimport android.animation.ObjectAnimator;
20990205eada00ad3e575761d19607bb03e12f9aa3George Mountimport android.animation.TypeEvaluator;
21ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mountimport android.content.Context;
22990205eada00ad3e575761d19607bb03e12f9aa3George Mountimport android.graphics.Matrix;
23990205eada00ad3e575761d19607bb03e12f9aa3George Mountimport android.graphics.Rect;
24990205eada00ad3e575761d19607bb03e12f9aa3George Mountimport android.graphics.drawable.Drawable;
25ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mountimport android.util.AttributeSet;
26990205eada00ad3e575761d19607bb03e12f9aa3George Mountimport android.util.Property;
27990205eada00ad3e575761d19607bb03e12f9aa3George Mountimport android.view.View;
28990205eada00ad3e575761d19607bb03e12f9aa3George Mountimport android.view.ViewGroup;
29990205eada00ad3e575761d19607bb03e12f9aa3George Mountimport android.widget.ImageView;
30990205eada00ad3e575761d19607bb03e12f9aa3George Mount
31990205eada00ad3e575761d19607bb03e12f9aa3George Mountimport java.util.Map;
32990205eada00ad3e575761d19607bb03e12f9aa3George Mount
33990205eada00ad3e575761d19607bb03e12f9aa3George Mount/**
34ad88e1b1b2c57fa56bde68764b22240d145fa0efGeorge Mount * This Transition captures an ImageView's matrix before and after the
35ad88e1b1b2c57fa56bde68764b22240d145fa0efGeorge Mount * scene change and animates it during the transition.
36ad88e1b1b2c57fa56bde68764b22240d145fa0efGeorge Mount *
37ad88e1b1b2c57fa56bde68764b22240d145fa0efGeorge Mount * <p>In combination with ChangeBounds, ChangeImageTransform allows ImageViews
38ad88e1b1b2c57fa56bde68764b22240d145fa0efGeorge Mount * that change size, shape, or {@link android.widget.ImageView.ScaleType} to animate contents
39ad88e1b1b2c57fa56bde68764b22240d145fa0efGeorge Mount * smoothly.</p>
40990205eada00ad3e575761d19607bb03e12f9aa3George Mount */
41990205eada00ad3e575761d19607bb03e12f9aa3George Mountpublic class ChangeImageTransform extends Transition {
42990205eada00ad3e575761d19607bb03e12f9aa3George Mount
43ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount    private static final String TAG = "ChangeImageTransform";
44990205eada00ad3e575761d19607bb03e12f9aa3George Mount
45ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount    private static final String PROPNAME_MATRIX = "android:changeImageTransform:matrix";
46ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount    private static final String PROPNAME_BOUNDS = "android:changeImageTransform:bounds";
47990205eada00ad3e575761d19607bb03e12f9aa3George Mount
48990205eada00ad3e575761d19607bb03e12f9aa3George Mount    private static final String[] sTransitionProperties = {
49990205eada00ad3e575761d19607bb03e12f9aa3George Mount            PROPNAME_MATRIX,
50990205eada00ad3e575761d19607bb03e12f9aa3George Mount            PROPNAME_BOUNDS,
51990205eada00ad3e575761d19607bb03e12f9aa3George Mount    };
52990205eada00ad3e575761d19607bb03e12f9aa3George Mount
53990205eada00ad3e575761d19607bb03e12f9aa3George Mount    private static TypeEvaluator<Matrix> NULL_MATRIX_EVALUATOR = new TypeEvaluator<Matrix>() {
54990205eada00ad3e575761d19607bb03e12f9aa3George Mount        @Override
55990205eada00ad3e575761d19607bb03e12f9aa3George Mount        public Matrix evaluate(float fraction, Matrix startValue, Matrix endValue) {
56990205eada00ad3e575761d19607bb03e12f9aa3George Mount            return null;
57990205eada00ad3e575761d19607bb03e12f9aa3George Mount        }
58990205eada00ad3e575761d19607bb03e12f9aa3George Mount    };
59990205eada00ad3e575761d19607bb03e12f9aa3George Mount
60990205eada00ad3e575761d19607bb03e12f9aa3George Mount    private static Property<ImageView, Matrix> ANIMATED_TRANSFORM_PROPERTY
61990205eada00ad3e575761d19607bb03e12f9aa3George Mount            = new Property<ImageView, Matrix>(Matrix.class, "animatedTransform") {
62990205eada00ad3e575761d19607bb03e12f9aa3George Mount        @Override
63990205eada00ad3e575761d19607bb03e12f9aa3George Mount        public void set(ImageView object, Matrix value) {
64990205eada00ad3e575761d19607bb03e12f9aa3George Mount            object.animateTransform(value);
65990205eada00ad3e575761d19607bb03e12f9aa3George Mount        }
66990205eada00ad3e575761d19607bb03e12f9aa3George Mount
67990205eada00ad3e575761d19607bb03e12f9aa3George Mount        @Override
68990205eada00ad3e575761d19607bb03e12f9aa3George Mount        public Matrix get(ImageView object) {
69990205eada00ad3e575761d19607bb03e12f9aa3George Mount            return null;
70990205eada00ad3e575761d19607bb03e12f9aa3George Mount        }
71990205eada00ad3e575761d19607bb03e12f9aa3George Mount    };
72990205eada00ad3e575761d19607bb03e12f9aa3George Mount
73ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount    public ChangeImageTransform() {}
74ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount
75ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount    public ChangeImageTransform(Context context, AttributeSet attrs) {
76ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount        super(context, attrs);
77ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount    }
78ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount
79990205eada00ad3e575761d19607bb03e12f9aa3George Mount    private void captureValues(TransitionValues transitionValues) {
80990205eada00ad3e575761d19607bb03e12f9aa3George Mount        View view = transitionValues.view;
81990205eada00ad3e575761d19607bb03e12f9aa3George Mount        if (!(view instanceof ImageView) || view.getVisibility() != View.VISIBLE) {
82990205eada00ad3e575761d19607bb03e12f9aa3George Mount            return;
83990205eada00ad3e575761d19607bb03e12f9aa3George Mount        }
84990205eada00ad3e575761d19607bb03e12f9aa3George Mount        ImageView imageView = (ImageView) view;
85990205eada00ad3e575761d19607bb03e12f9aa3George Mount        Drawable drawable = imageView.getDrawable();
86990205eada00ad3e575761d19607bb03e12f9aa3George Mount        if (drawable == null) {
87990205eada00ad3e575761d19607bb03e12f9aa3George Mount            return;
88990205eada00ad3e575761d19607bb03e12f9aa3George Mount        }
89990205eada00ad3e575761d19607bb03e12f9aa3George Mount        Map<String, Object> values = transitionValues.values;
90990205eada00ad3e575761d19607bb03e12f9aa3George Mount
91990205eada00ad3e575761d19607bb03e12f9aa3George Mount        int left = view.getLeft();
92990205eada00ad3e575761d19607bb03e12f9aa3George Mount        int top = view.getTop();
93990205eada00ad3e575761d19607bb03e12f9aa3George Mount        int right = view.getRight();
94990205eada00ad3e575761d19607bb03e12f9aa3George Mount        int bottom = view.getBottom();
95990205eada00ad3e575761d19607bb03e12f9aa3George Mount
96990205eada00ad3e575761d19607bb03e12f9aa3George Mount        Rect bounds = new Rect(left, top, right, bottom);
97990205eada00ad3e575761d19607bb03e12f9aa3George Mount        values.put(PROPNAME_BOUNDS, bounds);
98990205eada00ad3e575761d19607bb03e12f9aa3George Mount        Matrix matrix;
99990205eada00ad3e575761d19607bb03e12f9aa3George Mount        ImageView.ScaleType scaleType = imageView.getScaleType();
100990205eada00ad3e575761d19607bb03e12f9aa3George Mount        if (scaleType == ImageView.ScaleType.FIT_XY) {
101990205eada00ad3e575761d19607bb03e12f9aa3George Mount            matrix = imageView.getImageMatrix();
102990205eada00ad3e575761d19607bb03e12f9aa3George Mount            if (!matrix.isIdentity()) {
103990205eada00ad3e575761d19607bb03e12f9aa3George Mount                matrix = new Matrix(matrix);
104990205eada00ad3e575761d19607bb03e12f9aa3George Mount            } else {
105990205eada00ad3e575761d19607bb03e12f9aa3George Mount                int drawableWidth = drawable.getIntrinsicWidth();
106990205eada00ad3e575761d19607bb03e12f9aa3George Mount                int drawableHeight = drawable.getIntrinsicHeight();
107990205eada00ad3e575761d19607bb03e12f9aa3George Mount                if (drawableWidth > 0 && drawableHeight > 0) {
108990205eada00ad3e575761d19607bb03e12f9aa3George Mount                    float scaleX = ((float) bounds.width()) / drawableWidth;
109990205eada00ad3e575761d19607bb03e12f9aa3George Mount                    float scaleY = ((float) bounds.height()) / drawableHeight;
110990205eada00ad3e575761d19607bb03e12f9aa3George Mount                    matrix = new Matrix();
111990205eada00ad3e575761d19607bb03e12f9aa3George Mount                    matrix.setScale(scaleX, scaleY);
112990205eada00ad3e575761d19607bb03e12f9aa3George Mount                } else {
113990205eada00ad3e575761d19607bb03e12f9aa3George Mount                    matrix = null;
114990205eada00ad3e575761d19607bb03e12f9aa3George Mount                }
115990205eada00ad3e575761d19607bb03e12f9aa3George Mount            }
116990205eada00ad3e575761d19607bb03e12f9aa3George Mount        } else {
117990205eada00ad3e575761d19607bb03e12f9aa3George Mount            matrix = new Matrix(imageView.getImageMatrix());
118990205eada00ad3e575761d19607bb03e12f9aa3George Mount        }
119990205eada00ad3e575761d19607bb03e12f9aa3George Mount        values.put(PROPNAME_MATRIX, matrix);
120990205eada00ad3e575761d19607bb03e12f9aa3George Mount    }
121990205eada00ad3e575761d19607bb03e12f9aa3George Mount
122990205eada00ad3e575761d19607bb03e12f9aa3George Mount    @Override
123990205eada00ad3e575761d19607bb03e12f9aa3George Mount    public void captureStartValues(TransitionValues transitionValues) {
124990205eada00ad3e575761d19607bb03e12f9aa3George Mount        captureValues(transitionValues);
125990205eada00ad3e575761d19607bb03e12f9aa3George Mount    }
126990205eada00ad3e575761d19607bb03e12f9aa3George Mount
127990205eada00ad3e575761d19607bb03e12f9aa3George Mount    @Override
128990205eada00ad3e575761d19607bb03e12f9aa3George Mount    public void captureEndValues(TransitionValues transitionValues) {
129990205eada00ad3e575761d19607bb03e12f9aa3George Mount        captureValues(transitionValues);
130990205eada00ad3e575761d19607bb03e12f9aa3George Mount    }
131990205eada00ad3e575761d19607bb03e12f9aa3George Mount
132990205eada00ad3e575761d19607bb03e12f9aa3George Mount    @Override
133990205eada00ad3e575761d19607bb03e12f9aa3George Mount    public String[] getTransitionProperties() {
134990205eada00ad3e575761d19607bb03e12f9aa3George Mount        return sTransitionProperties;
135990205eada00ad3e575761d19607bb03e12f9aa3George Mount    }
136990205eada00ad3e575761d19607bb03e12f9aa3George Mount
137990205eada00ad3e575761d19607bb03e12f9aa3George Mount    /**
138990205eada00ad3e575761d19607bb03e12f9aa3George Mount     * Creates an Animator for ImageViews moving, changing dimensions, and/or changing
139990205eada00ad3e575761d19607bb03e12f9aa3George Mount     * {@link android.widget.ImageView.ScaleType}.
140990205eada00ad3e575761d19607bb03e12f9aa3George Mount     *
141990205eada00ad3e575761d19607bb03e12f9aa3George Mount     * @param sceneRoot   The root of the transition hierarchy.
142990205eada00ad3e575761d19607bb03e12f9aa3George Mount     * @param startValues The values for a specific target in the start scene.
143990205eada00ad3e575761d19607bb03e12f9aa3George Mount     * @param endValues   The values for the target in the end scene.
144990205eada00ad3e575761d19607bb03e12f9aa3George Mount     * @return An Animator to move an ImageView or null if the View is not an ImageView,
145990205eada00ad3e575761d19607bb03e12f9aa3George Mount     * the Drawable changed, the View is not VISIBLE, or there was no change.
146990205eada00ad3e575761d19607bb03e12f9aa3George Mount     */
147990205eada00ad3e575761d19607bb03e12f9aa3George Mount    @Override
148990205eada00ad3e575761d19607bb03e12f9aa3George Mount    public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues,
149990205eada00ad3e575761d19607bb03e12f9aa3George Mount            TransitionValues endValues) {
150990205eada00ad3e575761d19607bb03e12f9aa3George Mount        if (startValues == null || endValues == null) {
151990205eada00ad3e575761d19607bb03e12f9aa3George Mount            return null;
152990205eada00ad3e575761d19607bb03e12f9aa3George Mount        }
153990205eada00ad3e575761d19607bb03e12f9aa3George Mount        Rect startBounds = (Rect) startValues.values.get(PROPNAME_BOUNDS);
154990205eada00ad3e575761d19607bb03e12f9aa3George Mount        Rect endBounds = (Rect) endValues.values.get(PROPNAME_BOUNDS);
155990205eada00ad3e575761d19607bb03e12f9aa3George Mount        if (startBounds == null || endBounds == null) {
156990205eada00ad3e575761d19607bb03e12f9aa3George Mount            return null;
157990205eada00ad3e575761d19607bb03e12f9aa3George Mount        }
158990205eada00ad3e575761d19607bb03e12f9aa3George Mount
159990205eada00ad3e575761d19607bb03e12f9aa3George Mount        Matrix startMatrix = (Matrix) startValues.values.get(PROPNAME_MATRIX);
160990205eada00ad3e575761d19607bb03e12f9aa3George Mount        Matrix endMatrix = (Matrix) endValues.values.get(PROPNAME_MATRIX);
161990205eada00ad3e575761d19607bb03e12f9aa3George Mount
162990205eada00ad3e575761d19607bb03e12f9aa3George Mount        boolean matricesEqual = (startMatrix == null && endMatrix == null) ||
163990205eada00ad3e575761d19607bb03e12f9aa3George Mount                (startMatrix != null && startMatrix.equals(endMatrix));
164990205eada00ad3e575761d19607bb03e12f9aa3George Mount
165990205eada00ad3e575761d19607bb03e12f9aa3George Mount        if (startBounds.equals(endBounds) && matricesEqual) {
166990205eada00ad3e575761d19607bb03e12f9aa3George Mount            return null;
167990205eada00ad3e575761d19607bb03e12f9aa3George Mount        }
168990205eada00ad3e575761d19607bb03e12f9aa3George Mount
169990205eada00ad3e575761d19607bb03e12f9aa3George Mount        ImageView imageView = (ImageView) endValues.view;
170990205eada00ad3e575761d19607bb03e12f9aa3George Mount        Drawable drawable = imageView.getDrawable();
171990205eada00ad3e575761d19607bb03e12f9aa3George Mount        int drawableWidth = drawable.getIntrinsicWidth();
172990205eada00ad3e575761d19607bb03e12f9aa3George Mount        int drawableHeight = drawable.getIntrinsicHeight();
173990205eada00ad3e575761d19607bb03e12f9aa3George Mount
174990205eada00ad3e575761d19607bb03e12f9aa3George Mount        ObjectAnimator animator;
175990205eada00ad3e575761d19607bb03e12f9aa3George Mount        if (drawableWidth == 0 || drawableHeight == 0) {
176990205eada00ad3e575761d19607bb03e12f9aa3George Mount            animator = createNullAnimator(imageView);
177990205eada00ad3e575761d19607bb03e12f9aa3George Mount        } else {
178990205eada00ad3e575761d19607bb03e12f9aa3George Mount            if (startMatrix == null) {
179990205eada00ad3e575761d19607bb03e12f9aa3George Mount                startMatrix = Matrix.IDENTITY_MATRIX;
180990205eada00ad3e575761d19607bb03e12f9aa3George Mount            }
181990205eada00ad3e575761d19607bb03e12f9aa3George Mount            if (endMatrix == null) {
182990205eada00ad3e575761d19607bb03e12f9aa3George Mount                endMatrix = Matrix.IDENTITY_MATRIX;
183990205eada00ad3e575761d19607bb03e12f9aa3George Mount            }
184dbe3655c0b93b4bf80c751b4148fcbefbfe96d5bGeorge Mount            ANIMATED_TRANSFORM_PROPERTY.set(imageView, startMatrix);
185990205eada00ad3e575761d19607bb03e12f9aa3George Mount            animator = createMatrixAnimator(imageView, startMatrix, endMatrix);
186990205eada00ad3e575761d19607bb03e12f9aa3George Mount        }
187990205eada00ad3e575761d19607bb03e12f9aa3George Mount        return animator;
188990205eada00ad3e575761d19607bb03e12f9aa3George Mount    }
189990205eada00ad3e575761d19607bb03e12f9aa3George Mount
190990205eada00ad3e575761d19607bb03e12f9aa3George Mount    private ObjectAnimator createNullAnimator(ImageView imageView) {
191990205eada00ad3e575761d19607bb03e12f9aa3George Mount        return ObjectAnimator.ofObject(imageView, ANIMATED_TRANSFORM_PROPERTY,
192990205eada00ad3e575761d19607bb03e12f9aa3George Mount                NULL_MATRIX_EVALUATOR, null, null);
193990205eada00ad3e575761d19607bb03e12f9aa3George Mount    }
194990205eada00ad3e575761d19607bb03e12f9aa3George Mount
195990205eada00ad3e575761d19607bb03e12f9aa3George Mount    private ObjectAnimator createMatrixAnimator(final ImageView imageView, Matrix startMatrix,
196990205eada00ad3e575761d19607bb03e12f9aa3George Mount            final Matrix endMatrix) {
197ad88e1b1b2c57fa56bde68764b22240d145fa0efGeorge Mount        return ObjectAnimator.ofObject(imageView, ANIMATED_TRANSFORM_PROPERTY,
198c94e2b393f6eba684ee2c84eaa50746fc1459d0fDake Gu                new TransitionUtils.MatrixEvaluator(), startMatrix, endMatrix);
199990205eada00ad3e575761d19607bb03e12f9aa3George Mount    }
200990205eada00ad3e575761d19607bb03e12f9aa3George Mount}
201