summaryrefslogtreecommitdiff
path: root/src/com/android/deskclock/AnimatorUtils.java
blob: 4e90fa81f361a764ca733ecc6aef7fcdf34a8afd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
/*
 * 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 com.android.deskclock;

import android.animation.Animator;
import android.animation.ArgbEvaluator;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.animation.TypeEvaluator;
import android.animation.ValueAnimator;
import android.graphics.Rect;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.util.Property;
import android.view.View;
import android.view.animation.Interpolator;
import android.widget.ImageView;

import androidx.core.graphics.drawable.DrawableCompat;
import androidx.interpolator.view.animation.FastOutSlowInInterpolator;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class AnimatorUtils {

    public static final Interpolator DECELERATE_ACCELERATE_INTERPOLATOR = new Interpolator() {
        @Override
        public float getInterpolation(float x) {
            return 0.5f + 4.0f * (x - 0.5f) * (x - 0.5f) * (x - 0.5f);
        }
    };

    public static final Interpolator INTERPOLATOR_FAST_OUT_SLOW_IN =
            new FastOutSlowInInterpolator();

    public static final Property<View, Integer> BACKGROUND_ALPHA =
            new Property<View, Integer>(Integer.class, "background.alpha") {
        @Override
        public Integer get(View view) {
            Drawable background = view.getBackground();
            if (background instanceof LayerDrawable
                    && ((LayerDrawable) background).getNumberOfLayers() > 0) {
                background = ((LayerDrawable) background).getDrawable(0);
            }
            return background.getAlpha();
        }

        @Override
        public void set(View view, Integer value) {
            setBackgroundAlpha(view, value);
        }
    };

    /**
     * Sets the alpha of the top layer's drawable (of the background) only, if the background is a
     * layer drawable, to ensure that the other layers (i.e., the selectable item background, and
     * therefore the touch feedback RippleDrawable) are not affected.
     *
     * @param view the affected view
     * @param value the alpha value (0-255)
     */
    public static void setBackgroundAlpha(View view, Integer value) {
        Drawable background = view.getBackground();
        if (background instanceof LayerDrawable
                && ((LayerDrawable) background).getNumberOfLayers() > 0) {
            background = ((LayerDrawable) background).getDrawable(0);
        }
        background.setAlpha(value);
    }

    public static final Property<ImageView, Integer> DRAWABLE_ALPHA =
            new Property<ImageView, Integer>(Integer.class, "drawable.alpha") {
        @Override
        public Integer get(ImageView view) {
            return view.getDrawable().getAlpha();
        }

        @Override
        public void set(ImageView view, Integer value) {
            view.getDrawable().setAlpha(value);
        }
    };

    public static final Property<ImageView, Integer> DRAWABLE_TINT =
            new Property<ImageView, Integer>(Integer.class, "drawable.tint") {
        @Override
        public Integer get(ImageView view) {
            return null;
        }

        @Override
        public void set(ImageView view, Integer value) {
            // Ensure the drawable is wrapped using DrawableCompat.
            final Drawable drawable = view.getDrawable();
            final Drawable wrappedDrawable = DrawableCompat.wrap(drawable);
            if (wrappedDrawable != drawable) {
                view.setImageDrawable(wrappedDrawable);
            }
            // Set the new tint value via DrawableCompat.
            DrawableCompat.setTint(wrappedDrawable, value);
        }
    };

    @SuppressWarnings("unchecked")
    public static final TypeEvaluator<Integer> ARGB_EVALUATOR = new ArgbEvaluator();

    public static void reverse(ValueAnimator... animators) {
        for (ValueAnimator animator : animators) {
            final float fraction = animator.getAnimatedFraction();
            if (fraction > 0.0f) {
                animator.reverse();
                animator.setCurrentFraction(1.0f - fraction);
            }
        }
    }

    public static void cancel(ValueAnimator... animators) {
        for (ValueAnimator animator : animators) {
            animator.cancel();
        }
    }

    public static ValueAnimator getScaleAnimator(View view, float... values) {
        return ObjectAnimator.ofPropertyValuesHolder(view,
                PropertyValuesHolder.ofFloat(View.SCALE_X, values),
                PropertyValuesHolder.ofFloat(View.SCALE_Y, values));
    }

    public static ValueAnimator getAlphaAnimator(View view, float... values) {
        return ObjectAnimator.ofFloat(view, View.ALPHA, values);
    }

    public static final Property<View, Integer> VIEW_LEFT =
            new Property<View, Integer>(Integer.class, "left") {
                @Override
                public Integer get(View view) {
                    return view.getLeft();
                }

                @Override
                public void set(View view, Integer left) {
                    view.setLeft(left);
                }
            };

    public static final Property<View, Integer> VIEW_TOP =
            new Property<View, Integer>(Integer.class, "top") {
                @Override
                public Integer get(View view) {
                    return view.getTop();
                }

                @Override
                public void set(View view, Integer top) {
                    view.setTop(top);
                }
            };

    public static final Property<View, Integer> VIEW_BOTTOM =
            new Property<View, Integer>(Integer.class, "bottom") {
                @Override
                public Integer get(View view) {
                    return view.getBottom();
                }

                @Override
                public void set(View view, Integer bottom) {
                    view.setBottom(bottom);
                }
            };

    public static final Property<View, Integer> VIEW_RIGHT =
            new Property<View, Integer>(Integer.class, "right") {
                @Override
                public Integer get(View view) {
                    return view.getRight();
                }

                @Override
                public void set(View view, Integer right) {
                    view.setRight(right);
                }
            };

    /**
     * @param target the view to be morphed
     * @param from the bounds of the {@code target} before animating
     * @param to the bounds of the {@code target} after animating
     * @return an animator that morphs the {@code target} between the {@code from} bounds and the
     *      {@code to} bounds. Note that it is the *content* bounds that matter here, so padding
     *      insets contributed by the background are subtracted from the views when computing the
     *      {@code target} bounds.
     */
    public static Animator getBoundsAnimator(View target, View from, View to) {
        // Fetch the content insets for the views. Content bounds are what matter, not total bounds.
        final Rect targetInsets = new Rect();
        target.getBackground().getPadding(targetInsets);
        final Rect fromInsets = new Rect();
        from.getBackground().getPadding(fromInsets);
        final Rect toInsets = new Rect();
        to.getBackground().getPadding(toInsets);

        // Before animating, the content bounds of target must match the content bounds of from.
        final int startLeft = from.getLeft() - fromInsets.left + targetInsets.left;
        final int startTop = from.getTop() - fromInsets.top + targetInsets.top;
        final int startRight = from.getRight() - fromInsets.right + targetInsets.right;
        final int startBottom = from.getBottom() - fromInsets.bottom + targetInsets.bottom;

        // After animating, the content bounds of target must match the content bounds of to.
        final int endLeft = to.getLeft() - toInsets.left + targetInsets.left;
        final int endTop = to.getTop() - toInsets.top + targetInsets.top;
        final int endRight = to.getRight() - toInsets.right + targetInsets.right;
        final int endBottom = to.getBottom() - toInsets.bottom + targetInsets.bottom;

        return getBoundsAnimator(target, startLeft, startTop, startRight, startBottom, endLeft,
                endTop, endRight, endBottom);
    }

    /**
     * Returns an animator that animates the bounds of a single view.
     */
    public static Animator getBoundsAnimator(View view, int fromLeft, int fromTop, int fromRight,
            int fromBottom, int toLeft, int toTop, int toRight, int toBottom) {
        view.setLeft(fromLeft);
        view.setTop(fromTop);
        view.setRight(fromRight);
        view.setBottom(fromBottom);

        return ObjectAnimator.ofPropertyValuesHolder(view,
                PropertyValuesHolder.ofInt(VIEW_LEFT, toLeft),
                PropertyValuesHolder.ofInt(VIEW_TOP, toTop),
                PropertyValuesHolder.ofInt(VIEW_RIGHT, toRight),
                PropertyValuesHolder.ofInt(VIEW_BOTTOM, toBottom));
    }

    public static void startDrawableAnimation(ImageView view) {
        final Drawable d = view.getDrawable();
        if (d instanceof Animatable) {
            ((Animatable) d).start();
        }
    }
}