1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5  * except in compliance with the License. You may obtain a copy of the License at
6  *
7  *      http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software distributed under the
10  * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
11  * KIND, either express or implied. See the License for the specific language governing
12  * permissions and limitations under the License.
13  */
14 
15 package com.android.systemui.qs;
16 
17 import android.util.FloatProperty;
18 import android.util.MathUtils;
19 import android.util.Property;
20 import android.view.View;
21 import android.view.animation.Interpolator;
22 
23 import androidx.annotation.Nullable;
24 
25 import java.util.ArrayList;
26 import java.util.List;
27 
28 /**
29  * Helper class, that handles similar properties as animators (delay, interpolators)
30  * but can have a float input as to the amount they should be in effect.  This allows
31  * easier animation that tracks input.
32  *
33  * All "delays" and "times" are as fractions from 0-1.
34  */
35 public class TouchAnimator {
36 
37     private final Object[] mTargets;
38     private final KeyframeSet[] mKeyframeSets;
39     private final float mStartDelay;
40     private final float mEndDelay;
41     private final float mSpan;
42     @Nullable
43     private final Interpolator mInterpolator;
44     @Nullable
45     private final Listener mListener;
46     private float mLastT = -1;
47 
TouchAnimator( Object[] targets, KeyframeSet[] keyframeSets, float startDelay, float endDelay, @Nullable Interpolator interpolator, @Nullable Listener listener)48     private TouchAnimator(
49             Object[] targets,
50             KeyframeSet[] keyframeSets,
51             float startDelay,
52             float endDelay,
53             @Nullable Interpolator interpolator,
54             @Nullable Listener listener) {
55         mTargets = targets;
56         mKeyframeSets = keyframeSets;
57         mStartDelay = startDelay;
58         mEndDelay = endDelay;
59         mSpan = (1 - mEndDelay - mStartDelay);
60         mInterpolator = interpolator;
61         mListener = listener;
62     }
63 
setPosition(float fraction)64     public void setPosition(float fraction) {
65         if (Float.isNaN(fraction)) return;
66         float t = MathUtils.constrain((fraction - mStartDelay) / mSpan, 0, 1);
67         if (mInterpolator != null) {
68             t = mInterpolator.getInterpolation(t);
69         }
70         if (t == mLastT) {
71             return;
72         }
73         if (mListener != null) {
74             if (t == 1) {
75                 mListener.onAnimationAtEnd();
76             } else if (t == 0) {
77                 mListener.onAnimationAtStart();
78             } else if (mLastT <= 0 || mLastT == 1) {
79                 mListener.onAnimationStarted();
80             }
81             mLastT = t;
82         }
83         for (int i = 0; i < mTargets.length; i++) {
84             mKeyframeSets[i].setValue(t, mTargets[i]);
85         }
86     }
87 
88     private static final FloatProperty<TouchAnimator> POSITION =
89             new FloatProperty<TouchAnimator>("position") {
90         @Override
91         public void setValue(TouchAnimator touchAnimator, float value) {
92             touchAnimator.setPosition(value);
93         }
94 
95         @Override
96         public Float get(TouchAnimator touchAnimator) {
97             return touchAnimator.mLastT;
98         }
99     };
100 
101     public static class ListenerAdapter implements Listener {
102         @Override
onAnimationAtStart()103         public void onAnimationAtStart() { }
104 
105         @Override
onAnimationAtEnd()106         public void onAnimationAtEnd() { }
107 
108         @Override
onAnimationStarted()109         public void onAnimationStarted() { }
110     }
111 
112     public interface Listener {
113         /**
114          * Called when the animator moves into a position of "0". Start and end delays are
115          * taken into account, so this position may cover a range of fractional inputs.
116          */
onAnimationAtStart()117         void onAnimationAtStart();
118 
119         /**
120          * Called when the animator moves into a position of "1". Start and end delays are
121          * taken into account, so this position may cover a range of fractional inputs.
122          */
onAnimationAtEnd()123         void onAnimationAtEnd();
124 
125         /**
126          * Called when the animator moves out of the start or end position and is in a transient
127          * state.
128          */
onAnimationStarted()129         void onAnimationStarted();
130     }
131 
132     public static class Builder {
133         private List<Object> mTargets = new ArrayList<>();
134         private List<KeyframeSet> mValues = new ArrayList<>();
135 
136         private float mStartDelay;
137         private float mEndDelay;
138         @Nullable
139         private Interpolator mInterpolator;
140         @Nullable
141         private Listener mListener;
142 
addFloat(Object target, String property, float... values)143         public Builder addFloat(Object target, String property, float... values) {
144             add(target, KeyframeSet.ofFloat(getProperty(target, property, float.class), values));
145             return this;
146         }
147 
addInt(Object target, String property, int... values)148         public Builder addInt(Object target, String property, int... values) {
149             add(target, KeyframeSet.ofInt(getProperty(target, property, int.class), values));
150             return this;
151         }
152 
add(Object target, KeyframeSet keyframeSet)153         private void add(Object target, KeyframeSet keyframeSet) {
154             mTargets.add(target);
155             mValues.add(keyframeSet);
156         }
157 
getProperty(Object target, String property, Class<?> cls)158         private static Property getProperty(Object target, String property, Class<?> cls) {
159             if (target instanceof View) {
160                 switch (property) {
161                     case "translationX":
162                         return View.TRANSLATION_X;
163                     case "translationY":
164                         return View.TRANSLATION_Y;
165                     case "translationZ":
166                         return View.TRANSLATION_Z;
167                     case "alpha":
168                         return View.ALPHA;
169                     case "rotation":
170                         return View.ROTATION;
171                     case "x":
172                         return View.X;
173                     case "y":
174                         return View.Y;
175                     case "scaleX":
176                         return View.SCALE_X;
177                     case "scaleY":
178                         return View.SCALE_Y;
179                 }
180             }
181             if (target instanceof TouchAnimator && "position".equals(property)) {
182                 return POSITION;
183             }
184             return Property.of(target.getClass(), cls, property);
185         }
186 
setStartDelay(float startDelay)187         public Builder setStartDelay(float startDelay) {
188             mStartDelay = startDelay;
189             return this;
190         }
191 
setEndDelay(float endDelay)192         public Builder setEndDelay(float endDelay) {
193             mEndDelay = endDelay;
194             return this;
195         }
196 
197         /** Sets interpolator. */
setInterpolator(@ullable Interpolator intepolator)198         public Builder setInterpolator(@Nullable Interpolator intepolator) {
199             mInterpolator = intepolator;
200             return this;
201         }
202 
setListener(Listener listener)203         public Builder setListener(Listener listener) {
204             mListener = listener;
205             return this;
206         }
207 
build()208         public TouchAnimator build() {
209             return new TouchAnimator(mTargets.toArray(new Object[mTargets.size()]),
210                     mValues.toArray(new KeyframeSet[mValues.size()]),
211                     mStartDelay, mEndDelay, mInterpolator, mListener);
212         }
213     }
214 
215     private static abstract class KeyframeSet {
216         private final float mFrameWidth;
217         private final int mSize;
218 
KeyframeSet(int size)219         public KeyframeSet(int size) {
220             mSize = size;
221             mFrameWidth = 1 / (float) (size - 1);
222         }
223 
setValue(float fraction, Object target)224         void setValue(float fraction, Object target) {
225             int i = MathUtils.constrain((int) Math.ceil(fraction / mFrameWidth), 1, mSize - 1);
226             float amount = (fraction - mFrameWidth * (i - 1)) / mFrameWidth;
227             interpolate(i, amount, target);
228         }
229 
interpolate(int index, float amount, Object target)230         protected abstract void interpolate(int index, float amount, Object target);
231 
ofInt(Property property, int... values)232         public static KeyframeSet ofInt(Property property, int... values) {
233             return new IntKeyframeSet((Property<?, Integer>) property, values);
234         }
235 
ofFloat(Property property, float... values)236         public static KeyframeSet ofFloat(Property property, float... values) {
237             return new FloatKeyframeSet((Property<?, Float>) property, values);
238         }
239     }
240 
241     private static class FloatKeyframeSet<T> extends KeyframeSet {
242         private final float[] mValues;
243         private final Property<T, Float> mProperty;
244 
FloatKeyframeSet(Property<T, Float> property, float[] values)245         public FloatKeyframeSet(Property<T, Float> property, float[] values) {
246             super(values.length);
247             mProperty = property;
248             mValues = values;
249         }
250 
251         @Override
interpolate(int index, float amount, Object target)252         protected void interpolate(int index, float amount, Object target) {
253             float firstFloat = mValues[index - 1];
254             float secondFloat = mValues[index];
255             mProperty.set((T) target, firstFloat + (secondFloat - firstFloat) * amount);
256         }
257     }
258 
259     private static class IntKeyframeSet<T> extends KeyframeSet {
260         private final int[] mValues;
261         private final Property<T, Integer> mProperty;
262 
IntKeyframeSet(Property<T, Integer> property, int[] values)263         public IntKeyframeSet(Property<T, Integer> property, int[] values) {
264             super(values.length);
265             mProperty = property;
266             mValues = values;
267         }
268 
269         @Override
interpolate(int index, float amount, Object target)270         protected void interpolate(int index, float amount, Object target) {
271             int firstFloat = mValues[index - 1];
272             int secondFloat = mValues[index];
273             mProperty.set((T) target, (int) (firstFloat + (secondFloat - firstFloat) * amount));
274         }
275     }
276 }
277