作者:不洗碗工作室 - catango
文章出處: Android動畫
版權歸作者所有,轉載請註明出處
前言
動畫效果一直是人機互動的一個非常重要的部分,動畫效果的引入,會讓互動變得更加友好,讓使用者獲得更加愉悅的體驗。作為一個前端開發者,動畫也是一個必會的技能。
View Animation
View Animation包含了Tween Animation、Frame Animation(Drawable Animation)。這些都是在安卓3.0之前的兩種動畫。
幀動畫
幀動畫有時也叫Drawable動畫,它允許你實現像播放幻燈片一樣的效果,這種動畫的實質其實是Drawable,所以這種動畫的XML定義方式檔案 一般放在res/drawable/目錄下。幀動畫的動畫本質呢就是我們的視覺殘留。
一般我們會先在Drawable下面將動畫資源引用好,然後在程式碼中呼叫start()/stop()來開始或者停止播放動畫。當然我們也可以在Java程式碼中建立逐幀動畫,建立AnimationDrawable物件,然後呼叫 addFrame(Drawable frame,int duration)向動畫中新增幀,接著呼叫start()和stop()而已~推薦是使用XML來定義動畫。
具體的標籤有:
<animation-list>
:必須是根節點,包含多個< item>
元素。屬性有android:oneshot true代表只執行一次,false迴圈執行。< item>
:animation-list的子項,包含的屬性有:- android:drawable 一個frame的Drawable資源。
- android:duration 一個frame顯示多長時間。
舉個例子,我們在drawable中
< ?xml version="1.0" encoding="utf-8"?>
< animation-list xmlns:android="http://schemas.android.com/apk/res/android" android:oneshot="false">
< item android:drawable="@color/black" android:duration="300"/>
< item android:drawable="@color/white" android:duration="300"/>
< /animation-list>複製程式碼
然後在kotlin中
img_test.apply {
setBackgroundResource(R.drawable.test_frame)
setOnClickListener {
if ((background as AnimationDrawable).isRunning)
(background as AnimationDrawable).stop()
else (background as AnimationDrawable).start()
}
}複製程式碼
這樣當我們點選圖片的時候就會自動播放在drawable中設定的資源了。
注意,Animation的start方法不能在Activity的onCreate方法中呼叫,因為AnimationDrawable還未完全附著到Window上。
補間動畫
Tween Animation(補間動畫)只能應用於View物件,而且只支援一部分屬性,如支援縮放旋轉而不支援背景顏色的改變。而且對於Tween Animation,並不改變屬性的值,它只是改變了View物件繪製的位置,而沒有改變View物件本身,比如,你有一個Button,座標(100,100),Width:100,Height:100,而你有一個動畫使其移動(200,200),你會發現動畫過程中觸發按鈕點選的區域仍是(100,100)-(200,200)。
補間動畫通過XML或Android程式碼定義,建議還是使用XML檔案定義,因為它更具可讀性、可重用性。這裡說一下,補間動畫的所有父類都是它:
public abstract class Animation implements Cloneable複製程式碼
這個注意和屬性動畫的父類區分。
java類名 | xml關鍵字 | 描述資訊 |
---|---|---|
AlphaAnimation | 放置在res/anim/目錄下 | 漸變透明度動畫效果 |
RotateAnimation | 放置在res/anim/目錄下 | 畫面轉移旋轉動畫效果 |
ScaleAnimation | 放置在res/anim/目錄下 | 漸變尺寸伸縮動畫效果 |
TranslateAnimation | 放置在res/anim/目錄下 | 畫面轉換位置移動動畫效果 |
AnimationSet | 放置在res/anim/目錄下 | 一個持有其它動畫元素alpha、scale、translate、rotate或者其它set元素的容器 |
Animation屬性
xml屬性 | java方法 | 解釋 |
---|---|---|
android:detachWallpaper | setDetachWallpaper(boolean) | 是否在桌布上執行 |
android:duration | setDuration(long) | 動畫持續時間,毫秒為單位 |
android:fillAfter | setFillAfter(boolean) | 控制元件動畫結束時是否保持動畫最後的狀態 |
android:fillBefore | setFillBefore(boolean) | 控制元件動畫結束時是否還原到開始動畫前的狀態 |
android:fillEnabled | setFillEnabled(boolean) | 與android:fillBefore效果相同 |
android:interpolator | setInterpolator(Interpolator) | 設定插值器(指定的動畫效果,譬如回彈等) |
android:repeatCount | setRepeatCount(int) | 重複次數 |
android:repeatMode | setRepeatMode(int) | 重複型別有兩個值,reverse表示倒序回放,restart表示從頭播放 |
android:startOffset | setStartOffset(long) | 呼叫start函式之後等待開始執行的時間,單位為毫秒 |
android:zAdjustment | setZAdjustment(int) | 表示被設定動畫的內容執行時在Z軸上的位置(top/bottom/normal),預設為normal |
Alpha屬性
xml屬性 | java方法 | 解釋 |
---|---|---|
android:fromAlpha | AlphaAnimation(float fromAlpha, …) | 動畫開始的透明度(0.0到1.0,0.0是全透明,1.0是不透明) |
android:toAlpha | AlphaAnimation(…, float toAlpha) | 動畫結束的透明度,同上 |
Rotate屬性
xml屬性 | java方法 | 解釋 |
---|---|---|
android:fromDegrees | RotateAnimation(float fromDegrees, …) | 旋轉開始角度,正代表順時針度數,負代表逆時針度數 |
android:toDegrees | RotateAnimation(…, float toDegrees, …) | 旋轉結束角度,正代表順時針度數,負代表逆時針度數 |
android:pivotX | RotateAnimation(…, float pivotX, …) | 縮放起點X座標(數值、百分數、百分數p,譬如50表示以當前View左上角座標加50px為初始點、50%表示以當前View的左上角加上當前View寬高的50%做為初始點、50%p表示以當前View的左上角加上父控制元件寬高的50%做為初始點) |
android:pivotY | RotateAnimation(…, float pivotY) | 縮放起點Y座標,同上規律 |
Scale屬性
xml屬性 | java方法 | 解釋 |
---|---|---|
android:fromXScale | ScaleAnimation(float fromX, …) | 初始X軸縮放比例,1.0表示無變化 |
android:toXScale | ScaleAnimation(…, float toX, …) | 結束X軸縮放比例 |
android:fromYScale | ScaleAnimation(…, float fromY, …) | 初始Y軸縮放比例 |
android:toYScale | ScaleAnimation(…, float toY, …) | 結束Y軸縮放比例 |
android:pivotX | ScaleAnimation(…, float pivotX, …) | 縮放起點X軸座標(數值、百分數、百分數p,譬如50表示以當前View左上角座標加50px為初始點、50%表示以當前View的左上角加上當前View寬高的50%做為初始點、50%p表示以當前View的左上角加上父控制元件寬高的50%做為初始點) |
android:pivotY | ScaleAnimation(…, float pivotY) | 縮放起點Y軸座標,同上規律 |
Translate屬性詳解
xml屬性 | java方法 | 解釋 |
---|---|---|
android:fromXDelta | TranslateAnimation(float fromXDelta, …) | 起始點X軸座標(數值、百分數、百分數p,譬如50表示以當前View左上角座標加50px為初始點、50%表示以當前View的左上角加上當前View寬高的50%做為初始點、50%p表示以當前View的左上角加上父控制元件寬高的50%做為初始點) |
android:fromYDelta | TranslateAnimation(…, float fromYDelta, …) | 起始點Y軸從標,同上規律 |
android:toXDelta | TranslateAnimation(…, float toXDelta, …) | 結束點X軸座標,同上規律 |
android:toYDelta | TranslateAnimation(…, float toYDelta) | 結束點Y軸座標,同上規律 |
插值器
插值器是用來控制動畫的變化速度,可以理解成動畫渲染器,當然我們也可以自己實現Interpolator 介面,自行來控制動畫的變化速度,而Android中已經為我們提供了五個可供選擇的實現類:
- LinearInterpolator:動畫以均勻的速度改變
- AccelerateInterpolator:在動畫開始的地方改變速度較慢,然後開始加速
- AccelerateDecelerateInterpolator:在動畫開始、結束的地方改變速度較慢,中間時加速
- CycleInterpolator:動畫迴圈播放特定次數,變化速度按正弦曲線改變: Math.sin(2 mCycles Math.PI * input)
- DecelerateInterpolator:在動畫開始的地方改變速度較快,然後開始減速
- AnticipateInterpolator:反向,先向相反方向改變一段再加速播放
- AnticipateOvershootInterpolator:開始的時候向後然後向前甩一定值後返回最後的值
- BounceInterpolator: 跳躍,快到目的值時值會跳躍,如目的值100,後面的值可能依次為85,77,70,80,90,100
- OvershottInterpolator:回彈,最後超出目的值然後緩慢改變到目的值
使用簡介:
上面這些都是補間動畫的一些xml和java方法的簡介,這些方法屬性記不住不要緊,我們們可以隨時檢視的,最重要的還是怎麼去用。其實用法也是特別簡單的,十分的套路,我們只需要記住套路就可以。我們就舉個簡單的旋轉動畫的例子吧。
首先是XML
< ?xml version="1.0" encoding="utf-8"?>
< rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
android:fromDegrees="0"
android:toDegrees="360"
android:duration="1000"
android:pivotX="50%"
android:pivotY="50%"
android:repeatCount="infinite"
/>複製程式碼
其中我們用了@android:animaccelerate_decelerate_interpolator
這個插值器,就是上面所說的DecelerateInterpolator在動畫開始的地方改變速度較快,然後開始減速。
寫完XML後,在java程式碼中
Animation animation = AnimationUtils.loadAnimation(this, R.anim.anim_rotate);
testImg.setAnimation(animation);
animation.start();
animation.cancel();複製程式碼
我們可以看到,先建立了一個Animation物件,然後在用AnimationUtils的loadAnimation方法將XML裡定義的動畫載入到animation物件中,然後通過View的setAnimation方法將animation傳入,這樣就將這個動畫繫結到view上面了。最後通過animation的start和cancel方法來開始或者取消動畫。 其中我們可以為animation設定動畫的監聽,這個特別簡單就不再這贅述了...
Property Animation
Android 3.0以後引入了屬性動畫,屬性動畫可以輕而易舉的實現許多View動畫做不到的事。其實說白了,記住一點就行,屬性動畫實現原理就是修改控制元件的屬性值實現的動畫。當然,功能強大的代價就是使用起來要比較複雜。
屬性動畫所有的父類是它:
public abstract class Animator implements Cloneable複製程式碼
這個注意要和補間動畫來區分。
接下來介紹一下相關的API:
- Animator 建立屬性動畫的基類,一般不直接用,而是用他的兩個子類
- ValueAnimator Animator的直接派生類。其內部採用一種時間迴圈的機制來計算值與值之間的動畫過度,我們只需將初始值以及結束值提供給該類,並告訴其動畫所需時間長度,該類就會自動幫我們從初始值平滑過度到結束。該類還能管理動畫的播放次數、模式和監聽器等。
- AnimatorSet Animator的直接派生類,可以組合多個Animator,並制定Animator的播放次序。
- ObjectAnimator ValueAnimator的子類,允許我們對指定物件的屬性執行動畫,用起來更簡單,實際中用得較多。
- Evaluator 計算器,告訴動畫系統如何從初始值過度到結束值。提供了一下的幾種Evaluator:
- IntEvaluator:用於計算int屬性
- FloatEvaluator:用於計算float屬性
- ArgbEvaluator:用於計算16進製表示顏色值的計算器
- TypeEvaluator:上述計算類的公共介面,可以自己實現介面完成自定義。
ValueAnimator
使用流程:
- 呼叫ValueAnimator的ofInt(),ofFloat()或ofObject()靜態方法建立ValueAnimator例項
- 呼叫例項的setXxx方法設定動畫持續時間,插值方式,重複次數等
- 呼叫例項的addUpdateListener新增AnimatorUpdateListener監聽器,在該監聽器中 可以獲得ValueAnimator計算出來的值,你可以值應用到指定物件上~
- 呼叫例項的start()方法開啟動畫! 另外我們可以看到ofInt和ofFloat都有個這樣的引數:float/int... values代表可以多個值!
舉個例子:
//按軌跡方程來運動
private void lineAnimator() {
width = ly_root.getWidth();
height = ly_root.getHeight();
ValueAnimator xValue = ValueAnimator.ofInt(height,0,height / 4,height / 2,height / 4 * 3 ,height);
xValue.setDuration(3000L);
xValue.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
// 軌跡方程 x = width / 2
int y = (Integer) animation.getAnimatedValue();
int x = width / 2;
moveView(img_babi, x, y);
}
});
xValue.setInterpolator(new LinearInterpolator());
xValue.start();
}
private void moveView(View view, int rawX, int rawY) {
int left = rawX - img_babi.getWidth() / 2;
int top = rawY - img_babi.getHeight();
int width = left + view.getWidth();
int height = top + view.getHeight();
view.layout(left, top, width, height);
}複製程式碼
其中moveView方法是將View重新佈局,xValue的值從引數列表就可以看出,然後設定每次更新的監聽,在監聽中每次呼叫moveView方法來改變View的佈局。如果是組合動畫的話,我們可以這樣:
//縮放效果
private void scaleAnimator(){
final float scale = 0.5f;
AnimatorSet scaleSet = new AnimatorSet();
ValueAnimator valueAnimatorSmall = ValueAnimator.ofFloat(1.0f, scale);
valueAnimatorSmall.setDuration(500);
ValueAnimator valueAnimatorLarge = ValueAnimator.ofFloat(scale, 1.0f);
valueAnimatorLarge.setDuration(500);
valueAnimatorSmall.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float scale = (Float) animation.getAnimatedValue();
img_babi.setScaleX(scale);
img_babi.setScaleY(scale);
}
});
valueAnimatorLarge.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float scale = (Float) animation.getAnimatedValue();
img_babi.setScaleX(scale);
img_babi.setScaleY(scale);
}
});
scaleSet.play(valueAnimatorLarge).after(valueAnimatorSmall);
scaleSet.start();
}複製程式碼
這個組合動畫我們可以用play after來設定動畫例項,然後動畫就先執行play的動畫再執行after的例項了。當然,類似的方法還有一個with,就是兩個動畫會一起播放。
- after(Animator anim) 將現有動畫插入到傳入的動畫之後執行
- after(long delay) 將現有動畫延遲指定毫秒後執行
- before(Animator anim) 將現有動畫插入到傳入的動畫之前執行
- with(Animator anim) 將現有動畫和傳入的動畫同時執行
ObjectAnimator
相比於ValueAnimator,ObjectAnimator可能才是我們最常接觸到的類,因為ValueAnimator只不過是對值進行了一個平滑的動畫過渡,但我們實際使用到這種功能的場景好像並不多。而ObjectAnimator則就不同了,它是可以直接對任意物件的任意屬性進行動畫操作的,比如說View的alpha屬性。還有ObjectAnimator在設計的時候就沒有針對於View來進行設計,而是針對於任意物件的。
既然ObjectAnimator是繼承自ValueAnimator的,那麼它的用法應該是和ValueAnimator相似的。
ObjectAnimator.ofFloat(textview, "alpha", 1f, 0f);複製程式碼
其實這段程式碼的意思就是ObjectAnimator會幫我們不斷地改變textview物件中alpha屬性的值,從1f變化到0f。然後textview物件需要根據alpha屬性值的改變來不斷重新整理介面的顯示,從而讓使用者可以看出淡入淡出的動畫效果。
那麼textview物件中是不是有alpha屬性這個值呢?沒有,不僅textview沒有這個屬性,連它所有的父類也是沒有這個屬性的!這就奇怪了,textview當中並沒有alpha這個屬性,ObjectAnimator是如何進行操作的呢?其實ObjectAnimator內部的工作機制並不是直接對我們傳入的屬性名進行操作的,而是會去尋找這個屬性名對應的get和set方法,因此alpha屬性所對應的get和set方法應該就是:
public void setAlpha(float value);
public float getAlpha();複製程式碼
ObjectAnimator的使用相比ValueAnimator簡單多了,我們只需要將它例項化後,其他的用法和ValueAnimator是一樣的。
動畫的監聽
接下來要說下動畫事件的監聽,上面我們ValueAnimator的監聽器是 AnimatorUpdateListener,當值狀態發生改變時候會回撥onAnimationUpdate方法!
除了這種事件外還有:動畫進行狀態的監聽, AnimatorListener,我們可以呼叫addListener方法 新增監聽器,然後重寫下面四個回撥方法:
- onAnimationStart():動畫開始
- onAnimationRepeat():動畫重複執行
- onAnimationEnd():動畫結束
- onAnimationCancel():動畫取消
加入AnimatorListener的話,四個方法你都要重寫,這樣寫起來很是麻煩,不過Android已經給我們提供好一個介面卡類:AnimatorListenerAdapter,該類中已經把每個介面 方法都實現好了,所以我們這裡只寫一個回撥方法也是可以的。
Evaluator
我們在呼叫屬性動畫的時候,用到了ofInt,ofFloat,ofObject這些靜態方法來建立ValueAnimator的例項,在例子中,我們用到了ofInt,ofFloat,但是細心的同學可能發現了,ValueAnimator還有個ofObject方法來構造ValueAnimator例項,那麼這個是幹什麼用的呢?
public static ValueAnimator ofObject(TypeEvaluator evaluator, Object... values) {
ValueAnimator anim = new ValueAnimator();
anim.setObjectValues(values);
anim.setEvaluator(evaluator);
return anim;
}複製程式碼
我們先戳進去看看這個原始碼的引數。第一個引數是名為TypeEvaluator的一個東西,這就是我們要說的計算器,他是來告訴動畫系統如何從初始值過渡到結束值的。前面提到過,TypeEvaluator是所有Evaluator的基類,這裡再重複一遍,系統提供了以下的幾種Evaluator:
- IntEvaluator:用於計算int屬性
- FloatEvaluator:用於計算float屬性
- ArgbEvaluator:用於計算16進製表示顏色值的計算器
- TypeEvaluator:上述計算類的公共介面,可以自己實現介面完成自定義。
當然,這些Evaluator都是TypeEvaluator實現。我們先來看看TypeEvaluator這個介面長什麼樣吧:
public interface TypeEvaluator<T> {
public T evaluate(float fraction, T startValue, T endValue);
}複製程式碼
就這個一個evaluate方法,簡單吧?這裡面的三個引數的含義依次是:
- fraction:動畫的完成度,我們根據他來計算動畫的值應該是多少
- startValue:動畫的起始值
- endValue:動畫的結束值
那麼我們來看看系統是如何實現TypeEvaluator這個介面的,我們們就從IntEvaluator說起:
public class IntEvaluator implements TypeEvaluator<Integer> {
public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
int startInt = startValue;
return (int)(startInt + fraction * (endValue - startInt));
}
}複製程式碼
實現了TypeEvaluator介面,重寫了evaluate方法,其中返回的是動畫的值。動畫的值是多少呢?我們從IntEvaluator的return語句中可以看出,動畫的值 = 初始值 + 完成度 * (結束值 - 初始值) 這樣當完成度為100%的時候,動畫的值就是結束值了,同樣的還有FloatEvaluator等。細心的同學也發現了,這個TypeEvaluator裡面傳入的是一個泛型,這個型別也就是我們接下來自定義的Object.
現在我們舉個自定義Evaluator的例子。
package com.example.yang.testkotlin.widget;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.TypeEvaluator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;
/**
* @author YangCihang
* @since 17/10/18.
* email yangcihang@hrsoft.net
*/
public class CircleAnimView extends View {
public static final int RADIUS = 80;
private Point currentPoint;
private Paint mPaint;
private int mColor;//必須寫這個屬性的get和set,否則動畫無法識別color屬性
public CircleAnimView(Context context) {
super(context);
}
public CircleAnimView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public CircleAnimView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
private void init() {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.BLUE);
}
@Override
protected void onDraw(Canvas canvas) {
if (currentPoint == null) {
currentPoint = new Point(RADIUS, RADIUS);
drawCircle(canvas);
startAnimation();
} else {
drawCircle(canvas);
}
}
private void drawCircle(Canvas canvas) {
float x = currentPoint.x;
float y = currentPoint.y;
//用canvas draw,因此此控制元件大小應該為全屏大小才可以從左上到右下
canvas.drawCircle(x, y, RADIUS, mPaint);
}
private void startAnimation() {
Point startPoint = new Point(RADIUS, RADIUS);
Point endPoint = new Point(getWidth() - RADIUS, getHeight() - RADIUS);
ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(), startPoint, endPoint);
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
currentPoint = (Point) animation.getAnimatedValue();
invalidate();
}
});
ObjectAnimator objectAnimator = ObjectAnimator.ofObject(this, "color", new ColorEvaluator(),
Color.BLUE, Color.RED);
//動畫集合將兩個動畫加到一起,with同時播放
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.play(anim).with(objectAnimator);
animatorSet.setStartDelay(1000L);
animatorSet.setDuration(3000L);
animatorSet.start();
}
/**
* 座標變化
*/
class PointEvaluator implements TypeEvaluator<Point> {
@Override
public Point evaluate(float fraction, Point startValue, Point endValue) {
//初始值 + 完成度 * (結束值 - 初始值)
int x = (int) (startValue.x + fraction * (endValue.x - startValue.x));
int y = (int) (startValue.y + fraction * (endValue.y - startValue.y));
return new Point(x, y);
}
}
/**
* 顏色變化
*/
public class ColorEvaluator implements TypeEvaluator<Integer> {
@Override
public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
int alpha = (int) (Color.alpha(startValue) + fraction *
(Color.alpha(endValue) - Color.alpha(startValue)));
int red = (int) (Color.red(startValue) + fraction *
(Color.red(endValue) - Color.red(startValue)));
int green = (int) (Color.green(startValue) + fraction *
(Color.green(endValue) - Color.green(startValue)));
int blue = (int) (Color.blue(startValue) + fraction *
(Color.blue(endValue) - Color.blue(startValue)));
return Color.argb(alpha, red, green, blue);
}
}
//color的get和set方法~
public int getColor() {
return mColor;
}
public void setColor(int color) {
mColor = color;
mPaint.setColor(color);
invalidate();
}
}複製程式碼
在這裡我們定義了兩個Evaluator類分別來表示座標變化和顏色變化。注意,我們要改變color屬性的時候,一定要寫setColor和getColor方法,原因上面已經說過了。
Interpolator
Interpolator中文譯名叫做插值器或者補間器。在補間動畫的時候我們介紹了幾個常用的插值器,我們可以回頭去看看。補間動畫和屬性動畫都可以用插值器的。而且補間動畫還新增加了一個TimeInterpolator介面,該介面是用於相容之前的Interpolator的(也就是Interpolator又繼承了TimeInterpolator介面),這使得所有過去的Interpolator實現類都可以直接拿過來 放到屬性動畫當中使用!我們可以呼叫動畫物件的setInterpolator()方法設定不同的Interpolator。比如:
animatorSet.setInterpolator(new AccelerateInterpolator(2f))複製程式碼
括號裡面的值用於控制加速度的。
自定義Interpolator
我們自定義Interpolator其實就只要實現TimeInterpolator就可以了,重寫也就只需要重寫getInterpolation方法比如:
private class DecelerateAccelerateInterpolator implements TimeInterpolator {
@Override
public float getInterpolation(float input) {
if (input < 0.5) {
return (float) (Math.sin(input * Math.PI) / 2);
} else {
return 1 - (float) (Math.sin(input * Math.PI) / 2);
}
}
}複製程式碼
不過我們如果需要做一些複雜的速率變化,就得需要數學功底了。