/*
|
* Copyright (C) 2016 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.systemui.qs;
|
|
import android.util.FloatProperty;
|
import android.util.MathUtils;
|
import android.util.Property;
|
import android.view.View;
|
import android.view.animation.Interpolator;
|
|
import java.util.ArrayList;
|
import java.util.List;
|
|
/**
|
* Helper class, that handles similar properties as animators (delay, interpolators)
|
* but can have a float input as to the amount they should be in effect. This allows
|
* easier animation that tracks input.
|
*
|
* All "delays" and "times" are as fractions from 0-1.
|
*/
|
public class TouchAnimator {
|
|
private final Object[] mTargets;
|
private final KeyframeSet[] mKeyframeSets;
|
private final float mStartDelay;
|
private final float mEndDelay;
|
private final float mSpan;
|
private final Interpolator mInterpolator;
|
private final Listener mListener;
|
private float mLastT = -1;
|
|
private TouchAnimator(Object[] targets, KeyframeSet[] keyframeSets,
|
float startDelay, float endDelay, Interpolator interpolator, Listener listener) {
|
mTargets = targets;
|
mKeyframeSets = keyframeSets;
|
mStartDelay = startDelay;
|
mEndDelay = endDelay;
|
mSpan = (1 - mEndDelay - mStartDelay);
|
mInterpolator = interpolator;
|
mListener = listener;
|
}
|
|
public void setPosition(float fraction) {
|
float t = MathUtils.constrain((fraction - mStartDelay) / mSpan, 0, 1);
|
if (mInterpolator != null) {
|
t = mInterpolator.getInterpolation(t);
|
}
|
if (t == mLastT) {
|
return;
|
}
|
if (mListener != null) {
|
if (t == 1) {
|
mListener.onAnimationAtEnd();
|
} else if (t == 0) {
|
mListener.onAnimationAtStart();
|
} else if (mLastT <= 0 || mLastT == 1) {
|
mListener.onAnimationStarted();
|
}
|
mLastT = t;
|
}
|
for (int i = 0; i < mTargets.length; i++) {
|
mKeyframeSets[i].setValue(t, mTargets[i]);
|
}
|
}
|
|
private static final FloatProperty<TouchAnimator> POSITION =
|
new FloatProperty<TouchAnimator>("position") {
|
@Override
|
public void setValue(TouchAnimator touchAnimator, float value) {
|
touchAnimator.setPosition(value);
|
}
|
|
@Override
|
public Float get(TouchAnimator touchAnimator) {
|
return touchAnimator.mLastT;
|
}
|
};
|
|
public static class ListenerAdapter implements Listener {
|
@Override
|
public void onAnimationAtStart() { }
|
|
@Override
|
public void onAnimationAtEnd() { }
|
|
@Override
|
public void onAnimationStarted() { }
|
}
|
|
public interface Listener {
|
/**
|
* Called when the animator moves into a position of "0". Start and end delays are
|
* taken into account, so this position may cover a range of fractional inputs.
|
*/
|
void onAnimationAtStart();
|
|
/**
|
* Called when the animator moves into a position of "1". Start and end delays are
|
* taken into account, so this position may cover a range of fractional inputs.
|
*/
|
void onAnimationAtEnd();
|
|
/**
|
* Called when the animator moves out of the start or end position and is in a transient
|
* state.
|
*/
|
void onAnimationStarted();
|
}
|
|
public static class Builder {
|
private List<Object> mTargets = new ArrayList<>();
|
private List<KeyframeSet> mValues = new ArrayList<>();
|
|
private float mStartDelay;
|
private float mEndDelay;
|
private Interpolator mInterpolator;
|
private Listener mListener;
|
|
public Builder addFloat(Object target, String property, float... values) {
|
add(target, KeyframeSet.ofFloat(getProperty(target, property, float.class), values));
|
return this;
|
}
|
|
public Builder addInt(Object target, String property, int... values) {
|
add(target, KeyframeSet.ofInt(getProperty(target, property, int.class), values));
|
return this;
|
}
|
|
private void add(Object target, KeyframeSet keyframeSet) {
|
mTargets.add(target);
|
mValues.add(keyframeSet);
|
}
|
|
private static Property getProperty(Object target, String property, Class<?> cls) {
|
if (target instanceof View) {
|
switch (property) {
|
case "translationX":
|
return View.TRANSLATION_X;
|
case "translationY":
|
return View.TRANSLATION_Y;
|
case "translationZ":
|
return View.TRANSLATION_Z;
|
case "alpha":
|
return View.ALPHA;
|
case "rotation":
|
return View.ROTATION;
|
case "x":
|
return View.X;
|
case "y":
|
return View.Y;
|
case "scaleX":
|
return View.SCALE_X;
|
case "scaleY":
|
return View.SCALE_Y;
|
}
|
}
|
if (target instanceof TouchAnimator && "position".equals(property)) {
|
return POSITION;
|
}
|
return Property.of(target.getClass(), cls, property);
|
}
|
|
public Builder setStartDelay(float startDelay) {
|
mStartDelay = startDelay;
|
return this;
|
}
|
|
public Builder setEndDelay(float endDelay) {
|
mEndDelay = endDelay;
|
return this;
|
}
|
|
public Builder setInterpolator(Interpolator intepolator) {
|
mInterpolator = intepolator;
|
return this;
|
}
|
|
public Builder setListener(Listener listener) {
|
mListener = listener;
|
return this;
|
}
|
|
public TouchAnimator build() {
|
return new TouchAnimator(mTargets.toArray(new Object[mTargets.size()]),
|
mValues.toArray(new KeyframeSet[mValues.size()]),
|
mStartDelay, mEndDelay, mInterpolator, mListener);
|
}
|
}
|
|
private static abstract class KeyframeSet {
|
private final float mFrameWidth;
|
private final int mSize;
|
|
public KeyframeSet(int size) {
|
mSize = size;
|
mFrameWidth = 1 / (float) (size - 1);
|
}
|
|
void setValue(float fraction, Object target) {
|
int i = MathUtils.constrain((int) Math.ceil(fraction / mFrameWidth), 1, mSize - 1);
|
float amount = (fraction - mFrameWidth * (i - 1)) / mFrameWidth;
|
interpolate(i, amount, target);
|
}
|
|
protected abstract void interpolate(int index, float amount, Object target);
|
|
public static KeyframeSet ofInt(Property property, int... values) {
|
return new IntKeyframeSet((Property<?, Integer>) property, values);
|
}
|
|
public static KeyframeSet ofFloat(Property property, float... values) {
|
return new FloatKeyframeSet((Property<?, Float>) property, values);
|
}
|
}
|
|
private static class FloatKeyframeSet<T> extends KeyframeSet {
|
private final float[] mValues;
|
private final Property<T, Float> mProperty;
|
|
public FloatKeyframeSet(Property<T, Float> property, float[] values) {
|
super(values.length);
|
mProperty = property;
|
mValues = values;
|
}
|
|
@Override
|
protected void interpolate(int index, float amount, Object target) {
|
float firstFloat = mValues[index - 1];
|
float secondFloat = mValues[index];
|
mProperty.set((T) target, firstFloat + (secondFloat - firstFloat) * amount);
|
}
|
}
|
|
private static class IntKeyframeSet<T> extends KeyframeSet {
|
private final int[] mValues;
|
private final Property<T, Integer> mProperty;
|
|
public IntKeyframeSet(Property<T, Integer> property, int[] values) {
|
super(values.length);
|
mProperty = property;
|
mValues = values;
|
}
|
|
@Override
|
protected void interpolate(int index, float amount, Object target) {
|
int firstFloat = mValues[index - 1];
|
int secondFloat = mValues[index];
|
mProperty.set((T) target, (int) (firstFloat + (secondFloat - firstFloat) * amount));
|
}
|
}
|
}
|