成功的事業(yè)離不開堅如磐石的意志。成功路上的坎坷默垄,意志脆弱者是根本無法超越的渐裸∥紫妫——富蘭克林·羅斯福
老式電影膠片就是逐幀動畫的工作原理,很簡單昏鹃,將一個完整的電影拆分成一張張單獨的圖片,然后再將它們連貫起來進(jìn)行播放诀诊。所以我們總是看到一個輪子在不停的轉(zhuǎn)圈洞渤,就是在拖動一張張圖片。動畫分為兩類属瓣,視圖動畫 和 屬性動畫载迄。視圖動圖包括補間動畫和幀動畫,屬性動畫包括ValueAnimator 和 ObjectAnimator抡蛙。為什么在Android 3.0 引入屬性動畫护昧?
補間動畫有一個致命的缺陷,比如現(xiàn)在屏幕的左上角有一個按鈕粗截,我們通過補間動畫將它移動到了屏幕的右下角惋耙,你可以去嘗試點擊一下這個按鈕,點擊事件是絕對不會觸發(fā)熊昌,因為這個按鈕還是停留在屏幕的左上角绽榛,只不過補間動畫將這個按鈕繪制到了屏幕的右下角而已。
下面簡單看一下這個動畫的代碼實現(xiàn)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp"
android:text="Start Anim" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#03A9F4"
android:padding="10dp"
android:text="Hello World"
android:textColor="@android:color/white" />
</LinearLayout>
接下來給Buttom 和 TextView 添加單擊事件婿屹,當(dāng)點擊TextView時灭美,彈出Toast提示,當(dāng)點擊Buttom昂利,TextView 移動
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button btn = findViewById(R.id.btn);
final TextView tv = findViewById(R.id.tv);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
TranslateAnimation translateAnimation = new TranslateAnimation(Animation.ABSOLUTE, 0, Animation.ABSOLUTE, 400,
Animation.ABSOLUTE, 0, Animation.ABSOLUTE, 200);
translateAnimation.setFillAfter(true);
translateAnimation.setDuration(2000);
tv.startAnimation(translateAnimation);
}
});
tv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "go", Toast.LENGTH_SHORT).show();
}
});
}
}
補間動畫還有一個缺陷届腐,它只能夠?qū)崿F(xiàn)移動铁坎、縮放、旋轉(zhuǎn)和淡入淡出這四種動畫操作犁苏,那如果我們希望可以對View的背景色進(jìn)行動態(tài)地改變呢硬萍?它實現(xiàn)不了,屬性動畫因為對控件的屬性進(jìn)行修改傀顾,它可以輕易實現(xiàn)背景顏色的動態(tài)改變襟铭。
ValueAnimator
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 400);
valueAnimator.setDuration(1000);
valueAnimator.start();
ValueAnimator的ofFloat()方法就可以構(gòu)建出一個0到400的動畫,從這段代碼中可以出看短曾,ValueAnimator沒有跟任何控件想關(guān)聯(lián)寒砖,動畫時長是1秒,然后開始動畫嫉拐,這也正好說明ValueAnimator只對值進(jìn)行動畫運算哩都,而不是針對控件的。我們需要監(jiān)聽動畫過程來自己對控件進(jìn)行操作婉徘。
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 400);
valueAnimator.setDuration(1000);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float curValue = (float) animation.getAnimatedValue();
Log.d("tag", "----curValue:" + curValue);
}
});
valueAnimator.start();
我們通過addUpdateListener()方法來添加一個動畫的監(jiān)聽器漠嵌,在監(jiān)聽傳回的結(jié)果中,animator表達(dá)當(dāng)前ValueAnimation實例盖呼,通過animation.getAnimatedValue()函數(shù)得到當(dāng)前值儒鹿,運行結(jié)果:
06-10 20:20:34.855 23008-23008/com.as.propertyanimator D/tag: ----curValue:0.0
06-10 20:20:35.063 23008-23008/com.as.propertyanimator D/tag: ----curValue:0.0
06-10 20:20:35.198 23008-23008/com.as.propertyanimator D/tag: ----curValue:1.822114
06-10 20:20:35.254 23008-23008/com.as.propertyanimator D/tag: ----curValue:7.4239135
06-10 20:20:35.267 23008-23008/com.as.propertyanimator D/tag: ----curValue:11.401903
06-10 20:20:35.280 23008-23008/com.as.propertyanimator D/tag: ----curValue:15.465462
06-10 20:20:35.299 23008-23008/com.as.propertyanimator D/tag: ----curValue:20.118963
06-10 20:20:35.317 23008-23008/com.as.propertyanimator D/tag: ----curValue:25.347496
06-10 20:20:35.335 23008-23008/com.as.propertyanimator D/tag: ----curValue:31.134438
06-10 20:20:36.110 23008-23008/com.as.propertyanimator D/tag: ----curValue:398.00473
06-10 20:20:36.128 23008-23008/com.as.propertyanimator D/tag: ----curValue:399.3332
06-10 20:20:36.146 23008-23008/com.as.propertyanimator D/tag: ----curValue:399.93683
06-10 20:20:36.165 23008-23008/com.as.propertyanimator D/tag: ----curValue:400.0
對指定值區(qū)間進(jìn)行動畫運算,我們對運算過程進(jìn)行監(jiān)聽來自己操作控件几晤,總而言之
- ValueAnimator 只負(fù)責(zé)對指定值區(qū)間進(jìn)行動畫運算
- 我們需要對運算過程進(jìn)行監(jiān)聽约炎,然后自己對控件執(zhí)行動畫操作
ValueAnimator 使用實例
下面使用ValueAnimator 來實現(xiàn)補間動畫的單機(jī)區(qū)域問題中的例子,看看是否仍存在單機(jī)區(qū)域問題
布局代碼與上一個例子相同
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp"
android:text="Start Anim" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#03A9F4"
android:padding="10dp"
android:id="@+id/tv"
android:text="Hello World"
android:textColor="@android:color/white" />
</LinearLayout>
分別給Buttom 和 TextView 添加單擊事件蟹瘾,當(dāng)點擊TextView時圾浅,彈出Toast提示,當(dāng)點擊Buttom憾朴,TextView 開始移動狸捕。
public class MainActivity extends AppCompatActivity {
private TextView tv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button btn = findViewById(R.id.btn);
tv = findViewById(R.id.tv);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
translate();
}
});
tv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "go", Toast.LENGTH_SHORT).show();
}
});
}
//當(dāng)點擊Button 的時候調(diào)用translate 函數(shù)執(zhí)行動畫操作,在單擊TextView 彈出Toast提示
private void translate() {
ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 400);
valueAnimator.setDuration(2000);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int curValue = (int) animation.getAnimatedValue();
//通過layout 函數(shù)改變TextView 的位置众雷,而layout函數(shù)在改變控件位置時是永久的灸拍,即通過更改left ,top报腔,right株搔,bottom這四個點的坐標(biāo)來更改坐標(biāo)位置,而不僅僅是視覺上畫在哪個位置上
tv.layout(curValue, curValue, tv.getWidth() + curValue, tv.getHeight() + curValue);
}
});
valueAnimator.start();
}
...
TextView 的運動軌跡從屏幕左上角(0纯蛾,0)點運行到(400纤房,400)點
在上面的列子中,我們使用了ofFloat 和 ofInt 函數(shù)翻诉,下面看下它的具體聲明
public static ValueAnimator ofInt(int... values)
public static ValueAnimator ofFloat(float... values)
它們的參數(shù)類型都是可變參數(shù)炮姨,所以我們可以傳入任何數(shù)量的值捌刮,傳進(jìn)入的列表就表示動畫的變化方位,比如ofInt(100,400,200)就表示從數(shù)字100 變化到400在變化到數(shù)字200舒岸,所以我們傳進(jìn)去的數(shù)字越多绅作,動畫變化就越復(fù)雜。從參數(shù)類型中可以看出ofInt() 和 ofFloat() 唯一的區(qū)別就是傳入的數(shù)字類型不同蛾派。
在上面例子的基礎(chǔ)上俄认,我們使用ofFloat() 函數(shù)來舉一個列子
private void translateFloat() {
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0f, 400f,100f,300f);
valueAnimator.setDuration(4000);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
Float curValueFloat = (Float) animation.getAnimatedValue();
int curValue = curValueFloat.intValue();
tv.layout(curValue, curValue, tv.getWidth() + curValue, tv.getHeight() + curValue);
}
});
valueAnimator.start();
}
在這個列子中,我們使用 ValueAnimator.ofFloat(0f, 400f,100f,300f) 構(gòu)造了一個比較復(fù)雜的動畫洪乍,值從0變到400眯杏,再回到100,最后變成300壳澳。
所以岂贩,在單機(jī)按鈕之后,TextView 會從(0巷波,0)點移動到(400萎津,400)點,再運動到(100抹镊,100)點锉屈,最后運動到(300,300)點垮耳。
大家可能會疑問部念,為什么要轉(zhuǎn)換成Float 類型,我們先來看看getAnimatorValue()函數(shù)聲明
public Object getAnimatedValue()
它返回的是Object類型氨菇,那我們怎么知道要轉(zhuǎn)換的類型呢?哎妓湘,我們在設(shè)置動畫初始值使用的是ofFloat()函數(shù)查蓉,所以每個值的類型必定是Float類型,我們獲取到的類型也必然是Float類型榜贴。在得到當(dāng)前運動點后豌研,通過layout() 函數(shù)將TextView 移動到指定位置即可。
常用函數(shù)
// 設(shè)置動畫時長
public ValueAnimator setDuration(long duration)
// 獲取當(dāng)前運動點的值
public Object getAnimatedValue()
// 開始動畫
void start()
// 設(shè)置循環(huán)次數(shù)唬党,INFINITE 表示無限循環(huán)鹃共,0表示不循環(huán)
public void setRepeatCount(int value)
/**設(shè)置循環(huán)模式
*RESTART 表示正序重新開始
* REVERSE 表示倒序重新開始
*/
public void setRepeatMode(@RepeatMode int value)
//取消動畫
void cancel()
下面舉個例子
首先是布局代碼,我們將兩個按鈕放到一列驶拱,將TextView 放到中間
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/btnStart"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp"
android:text="Start Anim" />
<TextView
android:id="@+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:background="#03A9F4"
android:padding="10dp"
android:text="Hello World"
android:textColor="@android:color/white" />
<Button
android:id="@+id/btnCancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/btnStart"
android:layout_marginTop="30dp"
android:padding="10dp"
android:text="Cancel Anim" />
</RelativeLayout>
下面看看兩個按鈕的操作
public class MainActivity extends AppCompatActivity {
private TextView tv;
private ValueAnimator valueAnimatorRepeat;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button btnStart = findViewById(R.id.btnStart);
Button btnCancel = findViewById(R.id.btnCancel);
tv = findViewById(R.id.tv);
btnStart.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
doRepeatAnimator();
}
});
btnCancel.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
valueAnimatorRepeat.cancel();
}
});
tv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "go", Toast.LENGTH_SHORT).show();
}
});
}
private void doRepeatAnimator() {
valueAnimatorRepeat = ValueAnimator.ofInt(0, 400);
valueAnimatorRepeat.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int curValue = (int) animation.getAnimatedValue();
tv.layout(tv.getLeft(), curValue, tv.getRight(), tv.getHeight() + curValue);
}
});
valueAnimatorRepeat.setRepeatMode(ValueAnimator.REVERSE); //倒敘重新開始
valueAnimatorRepeat.setRepeatCount(ValueAnimator.INFINITE); // 無限循環(huán)
valueAnimatorRepeat.setDuration(2500);
valueAnimatorRepeat.start();
}
}
當(dāng)點擊btnStart時霜浴,調(diào)用doRepeatAnimator()函數(shù),返回ValueAnimator對象蓝纲,并將其賦給valueAnimatorRepeat阴孟,下面來看看這段代碼
private void doRepeatAnimator() {
valueAnimatorRepeat = ValueAnimator.ofInt(0, 400);
valueAnimatorRepeat.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int curValue = (int) animation.getAnimatedValue();
tv.layout(tv.getLeft(), curValue, tv.getRight(), tv.getHeight() + curValue);
}
});
valueAnimatorRepeat.setRepeatMode(ValueAnimator.REVERSE); //倒敘重新開始
valueAnimatorRepeat.setRepeatCount(ValueAnimator.INFINITE); // 無限循環(huán)
valueAnimatorRepeat.setDuration(2500);
valueAnimatorRepeat.start();
}
在這里晌纫,我們構(gòu)造一個ValueAnimator,動畫方位時0到400永丝,設(shè)置重復(fù)次數(shù)無限循環(huán)锹漱,重復(fù)模式為倒序。當(dāng)活動結(jié)束的時候慕嚷,必須調(diào)用cancel()函數(shù)取消動畫哥牍,否則動畫將會無限循環(huán),導(dǎo)致View無法釋放喝检,進(jìn)一步導(dǎo)致Activity無法釋放嗅辣,最終引起內(nèi)存泄漏。
自定義插值器
我們通過ofInt(0蛇耀,400)定義了動畫的區(qū)間值 0 到400辩诞,然后通過添加AnimatorUpdateListener來監(jiān)聽動畫的實時變化。那么問題來了纺涤,0到400之間的值是怎么變化的呢译暂?像我們跑步,還有得快撩炊,有的慢外永,這個值是勻速變化的嗎?如果是拧咳,想讓他一直加入該怎么辦呢伯顶?這就是插值器的作用。
插值器就是控制動畫區(qū)間值如何被計算出來骆膝,比如LinearInterpolator插值器表示勻速返回區(qū)間內(nèi)的值祭衩,等等其它插值器。
在自定義插值器之前阅签,先看看系統(tǒng)自帶的插值器是如何實現(xiàn)的掐暮,比如LinearInterpolator
public class LinearInterpolator implements Interpolator {
public LinearInterpolator() {
}
public LinearInterpolator(Context context, AttributeSet attrs) {
}
public float getInterpolation(float input) {
return input;
}
}
LinearInterpolator 實現(xiàn)了Interpolator 接口,而Interpolator 接口直接繼承TimeInterpolator政钟,并且沒有添加任何其它的方法路克。
package android.animation;
public interface TimeInterpolator {
float getInterpolation(float input);
}
參數(shù)input :它的取值方位0到1,表示當(dāng)前動畫的進(jìn)度养交,0表示動畫開始精算,1表示動畫結(jié)束,0.5表示動畫中間的位置碎连。表示當(dāng)前的動畫參數(shù)是勻速增加的灰羽,動畫進(jìn)度就是動畫在時間上的進(jìn)度,隨著時間推移,動畫自然從0到1逐漸增加谦趣。input參數(shù)相當(dāng)于時間的概念疲吸,我們通過setDuration()指定了動畫時長。
返回值:表示當(dāng)前實際想要顯示的進(jìn)度前鹅,取值可以超過1摘悴,表示超過目標(biāo)值,小于0 表示小于開始值舰绘。
ValueAnimator valueAnimator = ValueAnimator.ofInt(100, 500);
valueAnimator.setDuration(1000);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int curValue = (int) animation.getAnimatedValue();
tv.layout(curValue, curValue, tv.getWidth() + curValue, tv.getHeight() + curValue);
}
});
valueAnimator.start();
animation.getAnimatedValue() 得到當(dāng)前值是怎么來的蹂喻?看下面的計算公式,目前可以這么理解
當(dāng)前的值 = 100 + (400 - 100 ) * 顯示進(jìn)度
其中捂寿,100和400就是我們設(shè)置ofFloat (100,400)中的值
input 參數(shù)就表示當(dāng)前動畫進(jìn)度口四,而返回值則表示當(dāng)前動畫的數(shù)值進(jìn)度。
我們自定義插值器
package com.as.propertyanimator;
import android.animation.TimeInterpolator;
public class MyInterpolator implements TimeInterpolator {
@Override
public float getInterpolation(float input) {
return 1 - input;
}
}
在這個自定義插值器中我們將進(jìn)度反轉(zhuǎn)過來秦陋,當(dāng)傳入0的時候蔓彩,讓它的數(shù)值進(jìn)度在完成的位置,當(dāng)完成的時候驳概,讓它的數(shù)值進(jìn)度在開始位置赤嚼。
private void myInterpolator() {
ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 300);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int curValue = (int) animation.getAnimatedValue();
tv.layout(tv.getLeft(), curValue, tv.getRight(), tv.getHeight() + curValue);
}
});
valueAnimator.setDuration(1000);
valueAnimator.setInterpolator(new MyInterpolator());
valueAnimator.start();
}
Evaluator(求值器)
上圖講述了從定義動畫的數(shù)值區(qū)間到在AnimatorUpdateListener中得到當(dāng)前動畫所對應(yīng)數(shù)值的整個過程。
這4個步驟的具體含義如下
(1)ofInt(0,400) 表示指定動畫的數(shù)值區(qū)間顺又,從0運動到400
(2)插值器:在動畫開始后更卒,返回當(dāng)前動畫進(jìn)度所對應(yīng)的數(shù)值進(jìn)度,但這個數(shù)值進(jìn)度是以小數(shù)表示的稚照,如0.2蹂空。
(3)Evaluator:我們通過監(jiān)聽器拿到的是當(dāng)前動畫所對應(yīng)的具體數(shù)值,而不是用小數(shù)表示的數(shù)值果录。那么必須有一個地方根據(jù)檔期那數(shù)值進(jìn)度轉(zhuǎn)化為對應(yīng)數(shù)值上枕,這個地方就是Evaluator。Evaluator用于將插值器返回的數(shù)值進(jìn)度轉(zhuǎn)化為對應(yīng)數(shù)值弱恒。
(4)監(jiān)聽器返回:我們通過AnimatorUpdateListener 監(jiān)聽器中使用animation.getAnimatedValue()函數(shù)拿到Evaluator中返回的數(shù)值姿骏。
講了這么多,Evaluator其實就是一個轉(zhuǎn)換器斤彼,能把小數(shù)進(jìn)度轉(zhuǎn)換成對應(yīng)的數(shù)值。
在設(shè)置Evaluator時蘸泻,是通過animator.setEvaluator()函數(shù)來實現(xiàn)的琉苇,比如:
ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 300);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int curValue = (int) animation.getAnimatedValue();
tv.layout(tv.getLeft(), curValue, tv.getRight(), tv.getHeight() + curValue);
}
});
valueAnimator.setDuration(1000);
valueAnimator.setEvaluator(new IntEvaluator());
valueAnimator.setInterpolator(new BounceInterpolator());
valueAnimator.start();
我們設(shè)置了IntEvaluator,用來計算數(shù)值進(jìn)度所對應(yīng)的數(shù)值悦施。但在此之前并扇,我們在使用ofInt()函數(shù)時,從來沒有定義過使用IntEvaluator來轉(zhuǎn)換值抡诞,能正常運行是因為ofInt()和ofFloat()都是系統(tǒng)直接提供的函數(shù)穷蛹,所以會有默認(rèn)的插值器和Evaluator可供使用土陪。下面看下IntEvaluator內(nèi)部是怎么實現(xiàn)的。
package android.animation;
/**
* This evaluator can be used to perform type interpolation between <code>int</code> values.
*/
public class IntEvaluator implements TypeEvaluator<Integer> {
public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
int startInt = startValue;
return (int)(startInt + fraction * (endValue - startInt));
}
}
- fraction 參數(shù)就是插值器中返回的值肴熏,表示當(dāng)前動畫的百分比進(jìn)度鬼雀。
- startValue 和 endValue 分別對應(yīng)ofInt(int start,int end)函數(shù)中start 和 end值
- 返回值就是當(dāng)前數(shù)值進(jìn)度所對應(yīng)的具體數(shù)值
假設(shè)當(dāng)我們定義的動畫ofInt(200,500)進(jìn)行到數(shù)值進(jìn)度10%的時候,我們來算下具體值
return (int)(startInt + fraction * (endValue - startInt));
當(dāng)前值 = 200 + 0.1 * (500 - 200)
簡單實現(xiàn)Evaluator
下面仿照一個IntEvaluator 的實現(xiàn)方法蛙吏,自定義一個MyEvalutor
package com.as.propertyanimator;
import android.animation.TypeEvaluator;
public class MyEvaluator implements TypeEvaluator<Integer> {
@Override
public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
return (int) (200 + startValue + fraction * (endValue - startValue));
}
}
我們在IntEvaluator的基礎(chǔ)上修改了一下源哩,讓它返回時增加200。當(dāng)我們定義一個ofInt(0,500)時鸦做,它的實際返回值區(qū)間時(200励烦,700)。
很明顯泼诱,TextView 的動畫位置都向下移動了200px坛掠。
自定義插值器實現(xiàn)倒序輸出
package com.as.propertyanimator;
import android.animation.TypeEvaluator;
/**
* 倒敘輸出
*/
public class ReverseEvaluator implements TypeEvaluator<Integer> {
@Override
public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
return (int) (endValue - fraction * (endValue - startValue));
}
}
fraction * (endValue - startValue) 表示動畫的實際運動距離,我們用endValue 減去實際運動距離表示距離終點越來越遠(yuǎn)治筒,也就實現(xiàn)了從終點出發(fā)屉栓,到起點的效果
private void myReverseEvaluator() {
ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 300);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int curValue = (int) animation.getAnimatedValue();
tv.layout(tv.getLeft(), curValue, tv.getRight(), tv.getHeight() + curValue);
}
});
valueAnimator.setDuration(1000);
valueAnimator.setEvaluator(new ReverseEvaluator());
valueAnimator.start();
}
我們來實現(xiàn)顏色過渡轉(zhuǎn)換
private void colorEvaluator() {
ValueAnimator valueAnimator = ValueAnimator.ofInt(0xffffff00, 0xff0000ff);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int curValue = (int) animation.getAnimatedValue();
tv.setBackgroundColor(curValue);
}
});
valueAnimator.setDuration(5000);
valueAnimator.setEvaluator(new ArgbEvaluator());
valueAnimator.start();
}
我們將動畫的定義為(0xffffff00, 0xff0000ff),即從黃色變?yōu)樗{(lán)色矢炼。在監(jiān)聽事件中系瓢,我們根據(jù)當(dāng)前傳回的顏色值,將其設(shè)置為TextView 的背景色句灌。
這里需要注意的是夷陋,必須使用ofInt()函數(shù)來定義顏色的取值范圍,并且顏色必須包含A胰锌,R骗绕,G,B 4個值资昧,我們來簡單看一下ArgbEvaluator的源碼
/*
* Copyright (C) 2010 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 android.animation;
import android.annotation.UnsupportedAppUsage;
/**
* This evaluator can be used to perform type interpolation between integer
* values that represent ARGB colors.
*/
public class ArgbEvaluator implements TypeEvaluator {
public Object evaluate(float fraction, Object startValue, Object endValue) {
int startInt = (Integer) startValue;
float startA = ((startInt >> 24) & 0xff) / 255.0f;
float startR = ((startInt >> 16) & 0xff) / 255.0f;
float startG = ((startInt >> 8) & 0xff) / 255.0f;
float startB = ( startInt & 0xff) / 255.0f;
int endInt = (Integer) endValue;
float endA = ((endInt >> 24) & 0xff) / 255.0f;
float endR = ((endInt >> 16) & 0xff) / 255.0f;
float endG = ((endInt >> 8) & 0xff) / 255.0f;
float endB = ( endInt & 0xff) / 255.0f;
// compute the interpolated color in linear space
float a = startA + fraction * (endA - startA); //當(dāng)前進(jìn)度的透明度
float r = startR + fraction * (endR - startR); //當(dāng)前進(jìn)度下的紅色值
float g = startG + fraction * (endG - startG); //當(dāng)前進(jìn)度下的綠色值
float b = startB + fraction * (endB - startB); //當(dāng)前進(jìn)度下的藍(lán)色值
return Math.round(a) << 24 | Math.round(r) << 16 | Math.round(g) << 8 | Math.round(b);
}
}
這段代碼分為三部分酬土,第一部分根據(jù)startValue 起初 A,R格带,G撤缴,B中各個色彩的初始值,第二部分根據(jù)endValue 求出 A叽唱,R屈呕,G,B 各個色彩的結(jié)束值棺亭,第三部分根據(jù)當(dāng)前動畫的百分比進(jìn)度求出對應(yīng)的數(shù)值虎眨,最后通過或運算把結(jié)果拼接到一個整型 4個字節(jié)中。
這段代碼根據(jù)位移和與運算求出顏色中A,R嗽桩,G岳守,B 各個部分對應(yīng)的值,
如果大家對位移和與運算及如何得到指定為不太了解的畫碌冶,可以看我這篇博客算法之美湿痢,位運算。
ValueAnimator 進(jìn)階 ofObject
ofInt()函數(shù)只能傳入Integer類型种樱,ofFloat只能傳入Float 類型蒙袍,如果我們需要操作其它類型的變量該怎么辦呢?其實嫩挤,ValueAnimator還有一個函數(shù)ofObject()害幅,可以傳入任何類型的變量,該函數(shù)的定義如下:
public static ValueAnimator ofObject(TypeEvaluator evaluator, Object... values)
它有兩個參數(shù)岂昭,第一個參數(shù)是自定義Evaluator以现;第二個參數(shù)是可變參數(shù),屬于Object類型约啊。既然Object對象是我們自定義的邑遏,那么進(jìn)度值的轉(zhuǎn)換過程也由我們來做,否則系統(tǒng)不可能知道轉(zhuǎn)換出來的具體值是什么恰矩。
下面我們嘗試使用ofObject函數(shù)實現(xiàn)下面的效果记盒,將TextView 中的字母從A變化到Z。
package com.as.propertyanimator;
import android.animation.TypeEvaluator;
/**
* 動畫求值器從字母A到字母Z
*/
public class CharEvaluator implements TypeEvaluator<Character> {
@Override
public Character evaluate(float fraction, Character startValue, Character endValue) {
int startInt = startValue; //A = 65
int endInt = endValue; //Z = 90
return (char) (startInt + fraction * (endInt - startInt)); //當(dāng)前字符
}
}
private void objectEvaluator() {
//我們要實現(xiàn)的動畫效果是從字母A到字母Z
ValueAnimator valueAnimator = ValueAnimator.ofObject(new CharEvaluator(), 'A', 'Z');
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//獲取當(dāng)前動畫的值==>獲取到當(dāng)前字母
char curValue = (char) animation.getAnimatedValue();
tv.setText(String.valueOf(curValue));
}
});
//動畫勻速==>線性變化
valueAnimator.setInterpolator(new LinearInterpolator());
valueAnimator.setDuration(5000);
valueAnimator.start();
}
我們自定義了一個CharEvaluator外傅;在初始動畫時纪吮,傳入的是char對象,一個是字母A萎胰,另一個是字母Z碾盟。我們先來了解下ASCIi 碼表中數(shù)字與字符的轉(zhuǎn)換方法,每個字符都有一個對應(yīng)的數(shù)字技竟,字母A到Z對應(yīng)的數(shù)字區(qū)間65到90冰肴,在程序中,我們可以將數(shù)字轉(zhuǎn)換為字符榔组,也可以將字符轉(zhuǎn)化為數(shù)字熙尉。
數(shù)字轉(zhuǎn)字符
char c = (char) 65; //得到的c 就是大寫字母A
char temp = 'A';
int num = (int) temp; // 65
我們來實現(xiàn)一個小球落地的動畫
先用shape 標(biāo)簽實現(xiàn)一個圓形drawable (circle.xml)
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="#03A9F4" />
</shape>
然后實現(xiàn)布局文件
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/btnStart"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp"
android:text="Start Anim" />
<Button
android:id="@+id/btnCancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/btnStart"
android:layout_marginTop="30dp"
android:padding="10dp"
android:text="Cancel Anim" />
<TextView
android:id="@+id/tv"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_below="@+id/btnCancel"
android:layout_marginLeft="10dp"
android:layout_marginTop="10dp"
android:background="@drawable/circle"
android:padding="10dp"
android:textColor="@android:color/white" />
</RelativeLayout>
將上面的圓形shape 作為ImageView 的源文件顯示出來,接著實現(xiàn)動畫
package com.as.propertyanimator;
import android.animation.TypeEvaluator;
import android.graphics.Point;
/**
* 蹦蹦求 從空中落到地面上
*/
public class FallingBallEvaluator implements TypeEvaluator<Point> {
//蹦蹦求返回值
private Point mPoint = new Point();
@Override
public Point evaluate(float fraction, Point startValue, Point endValue) {
int x = (int) (startValue.x + (fraction * (endValue.x - startValue.x)));
mPoint.x = x;
int y = (int) (startValue.y + (fraction * (endValue.y - startValue.y)));
mPoint.y = y;
return mPoint;
}
}
private void ballEvaluator() {
//定義了球的位置
ValueAnimator valueAnimator = ValueAnimator.ofObject(new FallingBallEvaluator(), new Point(0, 0), new Point(500, 500));
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//獲取當(dāng)前動畫的值==>獲取到當(dāng)前x 和 y 位置
Point curValue = (Point) animation.getAnimatedValue();
//將球移動到指定位置
tv.layout(curValue.x, curValue.y, curValue.x + tv.getWidth(), curValue.y + tv.getHeight());
}
});
// 彈跳插值器搓扯,模擬了控件自由落地后回彈的效果
valueAnimator.setInterpolator(new BounceInterpolator());
valueAnimator.setDuration(3000);
valueAnimator.start();
}