Interpolator
被用來修飾動畫效果,定義動畫的變化率。在Android原始碼中對應的介面類為TimeInterpolator
,通過輸入均勻變化的0~1之間的值
,可以得到勻速、正加速、負加速、無規則變加速等0~1之間的變化曲線
。
曲線舉例:
如下圖所示,為Android原始碼中OvershootInterpolator
插值器變化率曲線。
輸入
為均勻變化0~1.0f之間
浮點值,輸出
為先加速超過臨界值1.0f 再慢慢又回落到1.0f
連續變化的浮點值。
效果舉例:
使用OvershootInterpolator
動畫插值器後,動畫的執行效果如下所示:
上圖中,旋轉放大效果中,旋轉動畫就是使用了OvershootInterpolator
動畫插值器。
可以看到3D勳章 360度旋轉時,旋轉角度先超過了360度
,然後慢慢又回到了360度位置
,從而呈現一個回彈的視覺效果
。
注:
瞭解 3D勳章具體實現,參考文章《3D勳章實現方案》:
https://xiaxl.blog.csdn.net/article/details/77048507
- Android 原始碼中的動畫插值器
- Easing 經典動畫插值器
一、Android中的插值器
Android原始碼中使用 TimeInterpolator
介面修飾動畫效果,定義動畫的變化率。
程式碼位於android.animation
包下,只包含一個抽象方法為getInterpolation(float input)
。
// 位於android.animation包下
package android.animation;
// Android原始碼中的 動畫插值器
public interface TimeInterpolator {
// 差值計算(輸入為0~1.0f之間的浮點值,輸出為連續的變化率曲線)
float getInterpolation(float input);
}
TimeInterpolator介面類中,只有一個方法float getInterpolation(float input)
,根據輸入的浮點值input
(0~1.0f之間),輸出為連續的變化率曲線。
Android中動畫插值器的使用方式如下:
// view 位移動畫
AnimatorSet localAnimatorSet = new AnimatorSet();
float[] arrayOfFloat = new float[2];
arrayOfFloat[0] = y0;
arrayOfFloat[1] = y1;
// 位移動畫使用了 DecelerateInterpolator() 動畫插值器
// 動畫效果:先位移超過臨界值,再回到臨界值
ObjectAnimator localObjectAnimator = ObjectAnimator.ofFloat(view,
"translationY", arrayOfFloat);
localObjectAnimator.setDuration(240L);
localObjectAnimator.setInterpolator(new DecelerateInterpolator());
localAnimatorSet.play(localObjectAnimator);
localAnimatorSet.start();
TimeInterpolator
為介面類,其有如下介面實現類。
1.1 AccelerateDecelerateInterpolator
AccelerateDecelerateInterpolator
該插值器運動曲線 兩邊慢 中間快
,其運動曲線如下圖所示:
/**
* An interpolator where the rate of change starts and ends slowly but
* accelerates through the middle.
* 兩邊慢 中間快
*/
public class AccelerateDecelerateInterpolator extends BaseInterpolator
implements NativeInterpolatorFactory {
public AccelerateDecelerateInterpolator() {
}
public float getInterpolation(float input) {
return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;
}
}
1.2 AccelerateInterpolator
AccelerateInterpolator
該插值器運動曲線 先慢 後快
,其運動曲線如下圖所示(factor值為1):
/**
* An interpolator where the rate of change starts out slowly and
* and then accelerates.
* 先慢 後快
*/
public class AccelerateInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
private final float mFactor;
private final double mDoubleFactor;
public AccelerateInterpolator() {
mFactor = 1.0f;
mDoubleFactor = 2.0;
}
/**
* Constructor
*
* @param factor Degree to which the animation should be eased. Seting
* factor to 1.0f produces a y=x^2 parabola. Increasing factor above
* 1.0f exaggerates the ease-in effect (i.e., it starts even
* slower and ends evens faster)
*/
public AccelerateInterpolator(float factor) {
mFactor = factor;
mDoubleFactor = 2 * mFactor;
}
public float getInterpolation(float input) {
if (mFactor == 1.0f) {
return input * input;
} else {
return (float)Math.pow(input, mDoubleFactor);
}
}
}
1.3 AnticipateInterpolator
AnticipateInterpolator
該插值器運動曲線 先向後超過臨界值,再快速向前
,像一個迴盪的鞦韆,因此被稱為迴盪鞦韆插值器
曲線圖如下:
/**
* An interpolator where the change starts backward then flings forward.
* 先向後 再向前
*/
public class AnticipateInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
private final float mTension;
public AnticipateInterpolator() {
mTension = 2.0f;
}
/**
* @param tension Amount of anticipation. When tension equals 0.0f, there is
* no anticipation and the interpolator becomes a simple
* acceleration interpolator.
*/
public AnticipateInterpolator(float tension) {
mTension = tension;
}
public float getInterpolation(float t) {
// a(t) = t * t * ((tension + 1) * t - tension)
return t * t * ((mTension + 1) * t - mTension);
}
}
1.4 AnticipateOvershootInterpolator
AnticipateOvershootInterpolator
該插值器運動曲線 先向後運動 超過臨界值,再快速向前運動到達臨界值
,其運動曲線如下圖所示:
/**
* An interpolator where the change starts backward then flings forward and overshoots
* the target value and finally goes back to the final value.
* 先向後運動 超過臨界值,再快速向前運動超過臨界值,最後慢慢回到臨界值
*/
public class AnticipateOvershootInterpolator extends BaseInterpolator
implements NativeInterpolatorFactory {
private final float mTension;
public AnticipateOvershootInterpolator() {
mTension = 2.0f * 1.5f;
}
/**
* @param tension Amount of anticipation/overshoot. When tension equals 0.0f,
* there is no anticipation/overshoot and the interpolator becomes
* a simple acceleration/deceleration interpolator.
*/
public AnticipateOvershootInterpolator(float tension) {
mTension = tension * 1.5f;
}
/**
* @param tension Amount of anticipation/overshoot. When tension equals 0.0f,
* there is no anticipation/overshoot and the interpolator becomes
* a simple acceleration/deceleration interpolator.
* @param extraTension Amount by which to multiply the tension. For instance,
* to get the same overshoot as an OvershootInterpolator with
* a tension of 2.0f, you would use an extraTension of 1.5f.
*/
public AnticipateOvershootInterpolator(float tension, float extraTension) {
mTension = tension * extraTension;
}
private static float a(float t, float s) {
return t * t * ((s + 1) * t - s);
}
private static float o(float t, float s) {
return t * t * ((s + 1) * t + s);
}
public float getInterpolation(float t) {
// a(t, s) = t * t * ((s + 1) * t - s)
// o(t, s) = t * t * ((s + 1) * t + s)
// f(t) = 0.5 * a(t * 2, tension * extraTension), when t < 0.5
// f(t) = 0.5 * (o(t * 2 - 2, tension * extraTension) + 2), when t <= 1.0
if (t < 0.5f) return 0.5f * a(t * 2.0f, mTension);
else return 0.5f * (o(t * 2.0f - 2.0f, mTension) + 2.0f);
}
}
1.5 BounceInterpolator
BounceInterpolator
該插值器運動曲線 快速運動到臨界值後,進行幾次回跳,類似一個從高空墜落籃球的運動曲線
,其運動曲線如下圖所示:
/**
* An interpolator where the change bounces at the end.
* 快速運動到臨界值後,進行幾次回跳,類似一個從高空墜落籃球的運動曲線。
*/
public class BounceInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
public BounceInterpolator() {
}
private static float bounce(float t) {
return t * t * 8.0f;
}
public float getInterpolation(float t) {
// _b(t) = t * t * 8
// bs(t) = _b(t) for t < 0.3535
// bs(t) = _b(t - 0.54719) + 0.7 for t < 0.7408
// bs(t) = _b(t - 0.8526) + 0.9 for t < 0.9644
// bs(t) = _b(t - 1.0435) + 0.95 for t <= 1.0
// b(t) = bs(t * 1.1226)
t *= 1.1226f;
if (t < 0.3535f) return bounce(t);
else if (t < 0.7408f) return bounce(t - 0.54719f) + 0.7f;
else if (t < 0.9644f) return bounce(t - 0.8526f) + 0.9f;
else return bounce(t - 1.0435f) + 0.95f;
}
}
1.6 CycleInterpolator
CycleInterpolator
該插值器運動曲線 正弦變化曲線
,其運動曲線如下圖所示:
/**
* Repeats the animation for a specified number of cycles. The
* rate of change follows a sinusoidal pattern.
* sin正弦變化曲線
*/
@HasNativeInterpolator
public class CycleInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
private float mCycles;
public CycleInterpolator(float cycles) {
mCycles = cycles;
}
public float getInterpolation(float input) {
return (float)(Math.sin(2 * mCycles * Math.PI * input));
}
}
1.7 DecelerateInterpolator
DecelerateInterpolator
該插值器運動曲線 減速插值器變化曲線
,其演算法為AccelerateInterpolator的完全倒置,同樣有DecelerateInterpolator(float factor)建構函式來指定mFactor運動曲線如下圖所示(factor值為1):
/**
* An interpolator where the rate of change starts out quickly and
* and then decelerates.
* 減速插值器變化曲線,其演算法為AccelerateInterpolator的完全倒置。
*/
public class DecelerateInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
private float mFactor = 1.0f;
public DecelerateInterpolator() {
}
/**
* Constructor
*
* @param factor Degree to which the animation should be eased. Setting factor to 1.0f produces
* an upside-down y=x^2 parabola. Increasing factor above 1.0f exaggerates the
* ease-out effect (i.e., it starts even faster and ends evens slower).
*/
public DecelerateInterpolator(float factor) {
mFactor = factor;
}
public float getInterpolation(float input) {
float result;
if (mFactor == 1.0f) {
result = (float)(1.0f - (1.0f - input) * (1.0f - input));
} else {
result = (float)(1.0f - Math.pow((1.0f - input), 2 * mFactor));
}
return result;
}
}
1.8 LinearInterpolator
LinearInterpolator
該插值器運動曲線 為0~1之間勻速變化的一條直線
,其運動曲線如下圖所示:
/**
* An interpolator where the rate of change starts out quickly and
* and then decelerates.
* 為0~1之間勻速變化的一條直線。
*/
/**
* An interpolator where the rate of change is constant
*/
public class LinearInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
public LinearInterpolator() {
}
public float getInterpolation(float input) {
return input;
}
}
1.9 OvershootInterpolator
OvershootInterpolator
該插值器運動曲線 先加速超過臨界值1.0f 再慢慢又回落到1.0f,有一個回彈的效果
。
可使用OvershootInterpolator(float tension)建構函式設定mTension彈力值,mTension值越大,超出目標值的時間點越靠前,超出目標值的回彈距離越大,回彈越明顯。
其運動曲線如下圖所示:
/**
* An interpolator where the change flings forward and overshoots the last value
* then comes back.
* 先超過臨界值 再慢慢回到臨界值
*/
public class OvershootInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
private final float mTension;
public OvershootInterpolator() {
mTension = 2.0f;
}
/**
* @param tension Amount of overshoot. When tension equals 0.0f, there is
* no overshoot and the interpolator becomes a simple
* deceleration interpolator.
*/
public OvershootInterpolator(float tension) {
mTension = tension;
}
public float getInterpolation(float t) {
// _o(t) = t * t * ((tension + 1) * t + tension)
// o(t) = _o(t - 1) + 1
t -= 1.0f;
return t * t * ((mTension + 1) * t + mTension) + 1.0f;
}
}
1.10 PathInterpolator
PathInterpolator
可以稱之為萬能插值器
,可以通過PathInterpolator構造一個Path路徑 或 通過傳入點來構造一個貝塞爾曲線(通過這個貝塞爾曲線,我們可以構造任意的變化曲線)。
//建立一個任意Path的插值器
PathInterpolator(Path path)
//建立一個二階貝塞爾曲線的插值器
PathInterpolator(float controlX, float controlY)
//建立一個三階貝塞爾曲線的插值器
PathInterpolator(float controlX1, float controlY1, float controlX2, float controlY2)
貝塞爾曲線的構建,可以使用如下輔助工具 cubic-bezier:
https://cubic-bezier.com/
/**
* An interpolator that can traverse a Path that extends from <code>Point</code>
* <code>(0, 0)</code> to <code>(1, 1)</code>. The x coordinate along the <code>Path</code>
* is the input value and the output is the y coordinate of the line at that point.
* This means that the Path must conform to a function <code>y = f(x)</code>.
*
* <p>The <code>Path</code> must not have gaps in the x direction and must not
* loop back on itself such that there can be two points sharing the same x coordinate.
* It is alright to have a disjoint line in the vertical direction:</p>
* <p><blockquote><pre>
* Path path = new Path();
* path.lineTo(0.25f, 0.25f);
* path.moveTo(0.25f, 0.5f);
* path.lineTo(1f, 1f);
* </pre></blockquote></p>
* 構造一個普通Path路徑或者貝塞爾曲線的插值器
*/
public class PathInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
// This governs how accurate the approximation of the Path is.
private static final float PRECISION = 0.002f;
private float[] mX; // x coordinates in the line
private float[] mY; // y coordinates in the line
/**
* Create an interpolator for an arbitrary <code>Path</code>. The <code>Path</code>
* must begin at <code>(0, 0)</code> and end at <code>(1, 1)</code>.
*
* @param path The <code>Path</code> to use to make the line representing the interpolator.
*/
public PathInterpolator(Path path) {
initPath(path);
}
public PathInterpolator(float controlX, float controlY) {
initQuad(controlX, controlY);
}
/**
* Create an interpolator for a cubic Bezier curve. The end points
* <code>(0, 0)</code> and <code>(1, 1)</code> are assumed.
*
* @param controlX1 The x coordinate of the first control point of the cubic Bezier.
* @param controlY1 The y coordinate of the first control point of the cubic Bezier.
* @param controlX2 The x coordinate of the second control point of the cubic Bezier.
* @param controlY2 The y coordinate of the second control point of the cubic Bezier.
*/
public PathInterpolator(float controlX1, float controlY1, float controlX2, float controlY2) {
initCubic(controlX1, controlY1, controlX2, controlY2);
}
private void initQuad(float controlX, float controlY) {
Path path = new Path();
path.moveTo(0, 0);
path.quadTo(controlX, controlY, 1f, 1f);
initPath(path);
}
private void initCubic(float x1, float y1, float x2, float y2) {
Path path = new Path();
path.moveTo(0, 0);
path.cubicTo(x1, y1, x2, y2, 1f, 1f);
initPath(path);
}
private void initPath(Path path) {
float[] pointComponents = path.approximate(PRECISION);
int numPoints = pointComponents.length / 3;
if (pointComponents[1] != 0 || pointComponents[2] != 0
|| pointComponents[pointComponents.length - 2] != 1
|| pointComponents[pointComponents.length - 1] != 1) {
throw new IllegalArgumentException("The Path must start at (0,0) and end at (1,1)");
}
mX = new float[numPoints];
mY = new float[numPoints];
float prevX = 0;
float prevFraction = 0;
int componentIndex = 0;
for (int i = 0; i < numPoints; i++) {
float fraction = pointComponents[componentIndex++];
float x = pointComponents[componentIndex++];
float y = pointComponents[componentIndex++];
if (fraction == prevFraction && x != prevX) {
throw new IllegalArgumentException(
"The Path cannot have discontinuity in the X axis.");
}
if (x < prevX) {
throw new IllegalArgumentException("The Path cannot loop back on itself.");
}
mX[i] = x;
mY[i] = y;
prevX = x;
prevFraction = fraction;
}
}
/**
* Using the line in the Path in this interpolator that can be described as
* <code>y = f(x)</code>, finds the y coordinate of the line given <code>t</code>
* as the x coordinate. Values less than 0 will always return 0 and values greater
* than 1 will always return 1.
*
* @param t Treated as the x coordinate along the line.
* @return The y coordinate of the Path along the line where x = <code>t</code>.
* @see Interpolator#getInterpolation(float)
*/
@Override
public float getInterpolation(float t) {
if (t <= 0) {
return 0;
} else if (t >= 1) {
return 1;
}
// Do a binary search for the correct x to interpolate between.
int startIndex = 0;
int endIndex = mX.length - 1;
while (endIndex - startIndex > 1) {
int midIndex = (startIndex + endIndex) / 2;
if (t < mX[midIndex]) {
endIndex = midIndex;
} else {
startIndex = midIndex;
}
}
float xRange = mX[endIndex] - mX[startIndex];
if (xRange == 0) {
return mY[startIndex];
}
float tInRange = t - mX[startIndex];
float fraction = tInRange / xRange;
float startY = mY[startIndex];
float endY = mY[endIndex];
return startY + (fraction * (endY - startY));
}
}
1.11 OvershootInterpolator
OvershootInterpolator
該插值器運動曲線 先加速超過臨界值1.0f 再慢慢又回落到1.0f,有一個回彈的效果
。
可使用OvershootInterpolator(float tension)建構函式設定mTension彈力值,mTension值越大,超出目標值的時間點越靠前,超出目標值的回彈距離越大,回彈越明顯。
其運動曲線如下圖所示:
/**
* An interpolator where the change flings forward and overshoots the last value
* then comes back.
* 先超過臨界值 再慢慢回到臨界值
*/
public class OvershootInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
private final float mTension;
public OvershootInterpolator() {
mTension = 2.0f;
}
/**
* @param tension Amount of overshoot. When tension equals 0.0f, there is
* no overshoot and the interpolator becomes a simple
* deceleration interpolator.
*/
public OvershootInterpolator(float tension) {
mTension = tension;
}
public float getInterpolation(float t) {
// _o(t) = t * t * ((tension + 1) * t + tension)
// o(t) = _o(t - 1) + 1
t -= 1.0f;
return t * t * ((mTension + 1) * t + mTension) + 1.0f;
}
}
注:
使用PathInterpolator插值器會消耗更多的記憶體,不同於其他簡單插值器,一般的插值器都是在演算法上來生成插值,而PathInterpolator是在初始化時依賴Path演算法生成一系列插值點儲存,原始碼顯示是以0.02為step在0到1範圍內取點,生成500個x樣本和500個y樣本共計1000個float資料,相比其他插值器消耗了相當1000倍的記憶體,雖然對目前手機效能來說微不足道,但在動畫這種要求高效能的操作時建議謹慎使用,不要頻繁初始化,儘量複用同參數的插值器,以提高效能。
二、Easing 插值器
Easing
演算法是業界著名的一組插值器演算法,涵蓋了各種速率的曲線演算法。
其涵蓋的曲線演算法如下圖所示:
注:
easings 官方網址:
https://easings.net/
easeInOutBounce
舉例一個動畫插值器 easeInOutBounce
。Easing官方對於每一個動畫插值器,均給出了完整的演算法實現
和動畫運動曲線
,開發者可以根據自己的需要自行選擇對應的插值器演算法,構造自己的動畫插值器。
function easeInOutBounce(x: number): number {
return x < 0.5
? (1 - easeOutBounce(1 - 2 * x)) / 2
: (1 + easeOutBounce(2 * x - 1)) / 2;
}
三、除錯插值器
除錯動畫插值器,可以使用如下小工具:
wolframalpha 除錯動畫插值器:
https://www.wolframalpha.com/input/?i=x%5E%282*3%29%280%3Cx%3C%3D1%29
參考
wolframalpha除錯工具:
https://www.wolframalpha.com/input/?i=x%5E%282*3%29%280%3Cx%3C%3D1%29
cubic-bezier輔助工具:
https://cubic-bezier.com/
easings 插值器:
https://easings.net/
3D勳章實現方案:
https://xiaxl.blog.csdn.net/article/details/77048507