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