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  */
16 package android.transition;
17 
18 import android.animation.Animator;
19 import android.animation.ObjectAnimator;
20 import android.animation.TypeEvaluator;
21 import android.content.Context;
22 import android.graphics.Matrix;
23 import android.graphics.Rect;
24 import android.graphics.drawable.Drawable;
25 import android.util.AttributeSet;
26 import android.util.Property;
27 import android.view.View;
28 import android.view.ViewGroup;
29 import android.widget.ImageView;
30 
31 import 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  */
41 public 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 
ChangeImageTransform()73     public ChangeImageTransform() {}
74 
ChangeImageTransform(Context context, AttributeSet attrs)75     public ChangeImageTransform(Context context, AttributeSet attrs) {
76         super(context, attrs);
77     }
78 
captureValues(TransitionValues transitionValues)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
captureStartValues(TransitionValues transitionValues)123     public void captureStartValues(TransitionValues transitionValues) {
124         captureValues(transitionValues);
125     }
126 
127     @Override
captureEndValues(TransitionValues transitionValues)128     public void captureEndValues(TransitionValues transitionValues) {
129         captureValues(transitionValues);
130     }
131 
132     @Override
getTransitionProperties()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
createAnimator(ViewGroup sceneRoot, TransitionValues startValues, TransitionValues endValues)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 
createNullAnimator(ImageView imageView)190     private ObjectAnimator createNullAnimator(ImageView imageView) {
191         return ObjectAnimator.ofObject(imageView, ANIMATED_TRANSFORM_PROPERTY,
192                 NULL_MATRIX_EVALUATOR, null, null);
193     }
194 
createMatrixAnimator(final ImageView imageView, Matrix startMatrix, final Matrix endMatrix)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