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