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.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.content.Context;
24 import android.graphics.Matrix;
25 import android.graphics.Rect;
26 import android.graphics.drawable.Drawable;
27 import android.util.AttributeSet;
28 import android.util.Property;
29 import android.view.View;
30 import android.view.ViewGroup;
31 import android.widget.ImageView;
32 
33 import java.util.Map;
34 
35 /**
36  * This Transition captures an ImageView's matrix before and after the
37  * scene change and animates it during the transition.
38  *
39  * <p>In combination with ChangeBounds, ChangeImageTransform allows ImageViews
40  * that change size, shape, or {@link android.widget.ImageView.ScaleType} to animate contents
41  * smoothly.</p>
42  */
43 public class ChangeImageTransform extends Transition {
44 
45     private static final String TAG = "ChangeImageTransform";
46 
47     private static final String PROPNAME_MATRIX = "android:changeImageTransform:matrix";
48     private static final String PROPNAME_BOUNDS = "android:changeImageTransform:bounds";
49 
50     private static final String[] sTransitionProperties = {
51             PROPNAME_MATRIX,
52             PROPNAME_BOUNDS,
53     };
54 
55     private static TypeEvaluator<Matrix> NULL_MATRIX_EVALUATOR = new TypeEvaluator<Matrix>() {
56         @Override
57         public Matrix evaluate(float fraction, Matrix startValue, Matrix endValue) {
58             return null;
59         }
60     };
61 
62     private static Property<ImageView, Matrix> ANIMATED_TRANSFORM_PROPERTY
63             = new Property<ImageView, Matrix>(Matrix.class, "animatedTransform") {
64         @Override
65         public void set(ImageView object, Matrix value) {
66             object.animateTransform(value);
67         }
68 
69         @Override
70         public Matrix get(ImageView object) {
71             return null;
72         }
73     };
74 
ChangeImageTransform()75     public ChangeImageTransform() {}
76 
ChangeImageTransform(Context context, AttributeSet attrs)77     public ChangeImageTransform(Context context, AttributeSet attrs) {
78         super(context, attrs);
79     }
80 
captureValues(TransitionValues transitionValues)81     private void captureValues(TransitionValues transitionValues) {
82         View view = transitionValues.view;
83         if (!(view instanceof ImageView) || view.getVisibility() != View.VISIBLE) {
84             return;
85         }
86         ImageView imageView = (ImageView) view;
87         Drawable drawable = imageView.getDrawable();
88         if (drawable == null) {
89             return;
90         }
91         Map<String, Object> values = transitionValues.values;
92 
93         int left = view.getLeft();
94         int top = view.getTop();
95         int right = view.getRight();
96         int bottom = view.getBottom();
97 
98         Rect bounds = new Rect(left, top, right, bottom);
99         values.put(PROPNAME_BOUNDS, bounds);
100         Matrix matrix;
101         ImageView.ScaleType scaleType = imageView.getScaleType();
102         int drawableWidth = drawable.getIntrinsicWidth();
103         int drawableHeight = drawable.getIntrinsicHeight();
104         if (scaleType == ImageView.ScaleType.FIT_XY && drawableWidth > 0 && drawableHeight > 0) {
105             float scaleX = ((float) bounds.width()) / drawableWidth;
106             float scaleY = ((float) bounds.height()) / drawableHeight;
107             matrix = new Matrix();
108             matrix.setScale(scaleX, scaleY);
109         } else {
110             matrix = new Matrix(imageView.getImageMatrix());
111         }
112         values.put(PROPNAME_MATRIX, matrix);
113     }
114 
115     @Override
captureStartValues(TransitionValues transitionValues)116     public void captureStartValues(TransitionValues transitionValues) {
117         captureValues(transitionValues);
118     }
119 
120     @Override
captureEndValues(TransitionValues transitionValues)121     public void captureEndValues(TransitionValues transitionValues) {
122         captureValues(transitionValues);
123     }
124 
125     @Override
getTransitionProperties()126     public String[] getTransitionProperties() {
127         return sTransitionProperties;
128     }
129 
130     /**
131      * Creates an Animator for ImageViews moving, changing dimensions, and/or changing
132      * {@link android.widget.ImageView.ScaleType}.
133      *
134      * @param sceneRoot   The root of the transition hierarchy.
135      * @param startValues The values for a specific target in the start scene.
136      * @param endValues   The values for the target in the end scene.
137      * @return An Animator to move an ImageView or null if the View is not an ImageView,
138      * the Drawable changed, the View is not VISIBLE, or there was no change.
139      */
140     @Nullable
141     @Override
createAnimator(@onNull ViewGroup sceneRoot, @Nullable TransitionValues startValues, @Nullable TransitionValues endValues)142     public Animator createAnimator(@NonNull ViewGroup sceneRoot,
143             @Nullable TransitionValues startValues,
144             @Nullable TransitionValues endValues) {
145         if (startValues == null || endValues == null) {
146             return null;
147         }
148         Rect startBounds = (Rect) startValues.values.get(PROPNAME_BOUNDS);
149         Rect endBounds = (Rect) endValues.values.get(PROPNAME_BOUNDS);
150         Matrix startMatrix = (Matrix) startValues.values.get(PROPNAME_MATRIX);
151         Matrix endMatrix = (Matrix) endValues.values.get(PROPNAME_MATRIX);
152         if (startBounds == null || endBounds == null || startMatrix == null || endMatrix == null) {
153             return null;
154         }
155 
156         if (startBounds.equals(endBounds) && startMatrix.equals(endMatrix)) {
157             return null;
158         }
159 
160         ImageView imageView = (ImageView) endValues.view;
161         Drawable drawable = imageView.getDrawable();
162         int drawableWidth = drawable.getIntrinsicWidth();
163         int drawableHeight = drawable.getIntrinsicHeight();
164 
165         ObjectAnimator animator;
166         if (drawableWidth <= 0 || drawableHeight <= 0) {
167             animator = createNullAnimator(imageView);
168         } else {
169             ANIMATED_TRANSFORM_PROPERTY.set(imageView, startMatrix);
170             animator = createMatrixAnimator(imageView, startMatrix, endMatrix);
171         }
172         return animator;
173     }
174 
createNullAnimator(ImageView imageView)175     private ObjectAnimator createNullAnimator(ImageView imageView) {
176         return ObjectAnimator.ofObject(imageView, ANIMATED_TRANSFORM_PROPERTY,
177                 NULL_MATRIX_EVALUATOR, Matrix.IDENTITY_MATRIX, Matrix.IDENTITY_MATRIX);
178     }
179 
createMatrixAnimator(final ImageView imageView, Matrix startMatrix, final Matrix endMatrix)180     private ObjectAnimator createMatrixAnimator(final ImageView imageView, Matrix startMatrix,
181             final Matrix endMatrix) {
182         return ObjectAnimator.ofObject(imageView, ANIMATED_TRANSFORM_PROPERTY,
183                 new TransitionUtils.MatrixEvaluator(), startMatrix, endMatrix);
184     }
185 }
186