1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package android.transition;
17
18import android.animation.Animator;
19import android.animation.ObjectAnimator;
20import android.animation.TypeEvaluator;
21import android.content.Context;
22import android.graphics.Matrix;
23import android.graphics.Rect;
24import android.graphics.drawable.Drawable;
25import android.util.AttributeSet;
26import android.util.Property;
27import android.view.View;
28import android.view.ViewGroup;
29import android.widget.ImageView;
30
31import java.util.Map;
32
33/**
34 * This Transition captures an ImageView's matrix before and after the
35 * scene change and animates it during the transition.
36 *
37 * <p>In combination with ChangeBounds, ChangeImageTransform allows ImageViews
38 * that change size, shape, or {@link android.widget.ImageView.ScaleType} to animate contents
39 * smoothly.</p>
40 */
41public class ChangeImageTransform extends Transition {
42
43    private static final String TAG = "ChangeImageTransform";
44
45    private static final String PROPNAME_MATRIX = "android:changeImageTransform:matrix";
46    private static final String PROPNAME_BOUNDS = "android:changeImageTransform:bounds";
47
48    private static final String[] sTransitionProperties = {
49            PROPNAME_MATRIX,
50            PROPNAME_BOUNDS,
51    };
52
53    private static TypeEvaluator<Matrix> NULL_MATRIX_EVALUATOR = new TypeEvaluator<Matrix>() {
54        @Override
55        public Matrix evaluate(float fraction, Matrix startValue, Matrix endValue) {
56            return null;
57        }
58    };
59
60    private static Property<ImageView, Matrix> ANIMATED_TRANSFORM_PROPERTY
61            = new Property<ImageView, Matrix>(Matrix.class, "animatedTransform") {
62        @Override
63        public void set(ImageView object, Matrix value) {
64            object.animateTransform(value);
65        }
66
67        @Override
68        public Matrix get(ImageView object) {
69            return null;
70        }
71    };
72
73    public ChangeImageTransform() {}
74
75    public ChangeImageTransform(Context context, AttributeSet attrs) {
76        super(context, attrs);
77    }
78
79    private void captureValues(TransitionValues transitionValues) {
80        View view = transitionValues.view;
81        if (!(view instanceof ImageView) || view.getVisibility() != View.VISIBLE) {
82            return;
83        }
84        ImageView imageView = (ImageView) view;
85        Drawable drawable = imageView.getDrawable();
86        if (drawable == null) {
87            return;
88        }
89        Map<String, Object> values = transitionValues.values;
90
91        int left = view.getLeft();
92        int top = view.getTop();
93        int right = view.getRight();
94        int bottom = view.getBottom();
95
96        Rect bounds = new Rect(left, top, right, bottom);
97        values.put(PROPNAME_BOUNDS, bounds);
98        Matrix matrix;
99        ImageView.ScaleType scaleType = imageView.getScaleType();
100        if (scaleType == ImageView.ScaleType.FIT_XY) {
101            matrix = imageView.getImageMatrix();
102            if (!matrix.isIdentity()) {
103                matrix = new Matrix(matrix);
104            } else {
105                int drawableWidth = drawable.getIntrinsicWidth();
106                int drawableHeight = drawable.getIntrinsicHeight();
107                if (drawableWidth > 0 && drawableHeight > 0) {
108                    float scaleX = ((float) bounds.width()) / drawableWidth;
109                    float scaleY = ((float) bounds.height()) / drawableHeight;
110                    matrix = new Matrix();
111                    matrix.setScale(scaleX, scaleY);
112                } else {
113                    matrix = null;
114                }
115            }
116        } else {
117            matrix = new Matrix(imageView.getImageMatrix());
118        }
119        values.put(PROPNAME_MATRIX, matrix);
120    }
121
122    @Override
123    public void captureStartValues(TransitionValues transitionValues) {
124        captureValues(transitionValues);
125    }
126
127    @Override
128    public void captureEndValues(TransitionValues transitionValues) {
129        captureValues(transitionValues);
130    }
131
132    @Override
133    public String[] getTransitionProperties() {
134        return sTransitionProperties;
135    }
136
137    /**
138     * Creates an Animator for ImageViews moving, changing dimensions, and/or changing
139     * {@link android.widget.ImageView.ScaleType}.
140     *
141     * @param sceneRoot   The root of the transition hierarchy.
142     * @param startValues The values for a specific target in the start scene.
143     * @param endValues   The values for the target in the end scene.
144     * @return An Animator to move an ImageView or null if the View is not an ImageView,
145     * the Drawable changed, the View is not VISIBLE, or there was no change.
146     */
147    @Override
148    public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues,
149            TransitionValues endValues) {
150        if (startValues == null || endValues == null) {
151            return null;
152        }
153        Rect startBounds = (Rect) startValues.values.get(PROPNAME_BOUNDS);
154        Rect endBounds = (Rect) endValues.values.get(PROPNAME_BOUNDS);
155        if (startBounds == null || endBounds == null) {
156            return null;
157        }
158
159        Matrix startMatrix = (Matrix) startValues.values.get(PROPNAME_MATRIX);
160        Matrix endMatrix = (Matrix) endValues.values.get(PROPNAME_MATRIX);
161
162        boolean matricesEqual = (startMatrix == null && endMatrix == null) ||
163                (startMatrix != null && startMatrix.equals(endMatrix));
164
165        if (startBounds.equals(endBounds) && matricesEqual) {
166            return null;
167        }
168
169        ImageView imageView = (ImageView) endValues.view;
170        Drawable drawable = imageView.getDrawable();
171        int drawableWidth = drawable.getIntrinsicWidth();
172        int drawableHeight = drawable.getIntrinsicHeight();
173
174        ObjectAnimator animator;
175        if (drawableWidth == 0 || drawableHeight == 0) {
176            animator = createNullAnimator(imageView);
177        } else {
178            if (startMatrix == null) {
179                startMatrix = Matrix.IDENTITY_MATRIX;
180            }
181            if (endMatrix == null) {
182                endMatrix = Matrix.IDENTITY_MATRIX;
183            }
184            ANIMATED_TRANSFORM_PROPERTY.set(imageView, startMatrix);
185            animator = createMatrixAnimator(imageView, startMatrix, endMatrix);
186        }
187        return animator;
188    }
189
190    private ObjectAnimator createNullAnimator(ImageView imageView) {
191        return ObjectAnimator.ofObject(imageView, ANIMATED_TRANSFORM_PROPERTY,
192                NULL_MATRIX_EVALUATOR, null, null);
193    }
194
195    private ObjectAnimator createMatrixAnimator(final ImageView imageView, Matrix startMatrix,
196            final Matrix endMatrix) {
197        return ObjectAnimator.ofObject(imageView, ANIMATED_TRANSFORM_PROPERTY,
198                new TransitionUtils.MatrixEvaluator(), startMatrix, endMatrix);
199    }
200}
201