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