long long ago,我寫過幾篇有關(guān) Animation 的文章,講解了傳統(tǒng)的 alpha、scale、translate、rotate 的用法及代碼生成方法。其實(shí)這三篇文章講的所有動(dòng)畫效果叫做 Tween Animation(補(bǔ)間動(dòng)畫) 在 Android 動(dòng)畫中,總共有兩種類型的動(dòng)畫 View Animation(視圖動(dòng)畫)和 Property Animator(屬性動(dòng)畫);
其中
首先,直觀上,他們有如下三點(diǎn)不同: 1、引入時(shí)間不同:View Animation 是 API Level 1 就引入的。Property Animation 是 API Level 11 引入的,即 Android 3.0 才開始有 Property Animation 相關(guān)的 API。 2、所在包名不同:View Animation 在包 android.view.animation 中。而 Property Animation API 在包 android.animation 中。 3、動(dòng)畫類的命名不同:View Animation 中動(dòng)畫類取名都叫 XXXXAnimation,而在 Property Animator 中動(dòng)畫類的取名則叫 XXXXAnimator
大家都知道逐幀動(dòng)畫主要是用來(lái)實(shí)現(xiàn)動(dòng)畫的,而補(bǔ)間動(dòng)畫才能實(shí)現(xiàn)控件的漸入漸出、移動(dòng)、旋轉(zhuǎn)和縮放的;而 Property Animator 是在 Android 3.0 版本才引入的,之前是沒有的。大家可能會(huì)覺得補(bǔ)間動(dòng)畫和逐幀動(dòng)畫已經(jīng)很全了,為什么還要引入 Property Animator 呢?
我提出一個(gè)假設(shè):請(qǐng)問大家,如何利用補(bǔ)間動(dòng)畫來(lái)將一個(gè)控件的背景色在一分鐘內(nèi)從綠色變?yōu)榧t色?這個(gè)效果想必沒辦法僅僅通過改變控件的漸入漸出、移動(dòng)、旋轉(zhuǎn)和縮放來(lái)實(shí)現(xiàn)吧,而這個(gè)效果是可以通過 Property Animator 完美實(shí)現(xiàn)的 這就是第一個(gè)原因:Property Animator 能實(shí)現(xiàn)補(bǔ)間動(dòng)畫無(wú)法實(shí)現(xiàn)的功能 大家都知道,補(bǔ)間動(dòng)畫和逐幀動(dòng)畫統(tǒng)稱為 View Animation,也就是說這兩個(gè)動(dòng)畫只能對(duì)派生自 View 的控件實(shí)例起作用;而 Property Animator 則不同,從名字中可以看出屬性動(dòng)畫,應(yīng)該是作用于控件屬性的!正因?yàn)閷傩詣?dòng)畫能夠只針對(duì)控件的某一個(gè)屬性來(lái)做動(dòng)畫,所以也就造就了他能單獨(dú)改變控件的某一個(gè)屬性的值!比如顏色!這就是 Property Animator 能實(shí)現(xiàn)補(bǔ)間動(dòng)畫無(wú)法實(shí)現(xiàn)的功能的最重要原因。 我們得到了第二點(diǎn)不同:View Animation 僅能對(duì)指定的控件做動(dòng)畫,而 Property Animator 是通過改變控件某一屬性值來(lái)做動(dòng)畫的。 假設(shè)我們將一個(gè)按鈕從左上角利用補(bǔ)間動(dòng)畫將其移動(dòng)到右下角,在移動(dòng)過程中和移動(dòng)后,這個(gè)按鈕都是不會(huì)響應(yīng)點(diǎn)擊事件的。這是為什么呢?因?yàn)檠a(bǔ)間動(dòng)畫僅僅轉(zhuǎn)變的是控件的顯示位置而已,并沒有改變控件本身的值。View Animation 的動(dòng)畫實(shí)現(xiàn)是通過其 Parent View 實(shí)現(xiàn)的,在 View 被 drawn 時(shí) Parents View 改變它的繪制參數(shù),這樣雖然 View 的大小或旋轉(zhuǎn)角度等改變了,但 View 的實(shí)際屬性沒變,所以有效區(qū)域還是應(yīng)用動(dòng)畫之前的區(qū)域;我們看到的效果僅僅是系統(tǒng)作用在按鈕上的顯示效果,利用動(dòng)畫把按鈕從原來(lái)的位置移到了右下角,但按鈕內(nèi)部的任何值是沒有變化的,所以按鈕所捕捉的點(diǎn)擊區(qū)域仍是原來(lái)的點(diǎn)擊區(qū)域。(下面會(huì)舉例來(lái)說明這個(gè)問題) 這就得到了第三點(diǎn)不同:補(bǔ)間動(dòng)畫雖能對(duì)控件做動(dòng)畫,但并沒有改變控件內(nèi)部的屬性值。而 Property Animator 則是恰恰相反,Property Animator 是通過改變控件內(nèi)部的屬性值來(lái)達(dá)到動(dòng)畫效果的
下面我們就利用 TranslateAnimation 來(lái)做一個(gè)移動(dòng)動(dòng)畫的例子,看它的點(diǎn)擊區(qū)域是否會(huì)變。 我們先來(lái)看看效果:
http://wiki.jikexueyuan.com/project/android-animation/images/53.gif" alt="" />
在效果圖中,首先,我給 textview 添加了點(diǎn)擊響應(yīng),當(dāng)點(diǎn)擊 textview 時(shí),會(huì)彈出 Toast。 然后,當(dāng)我點(diǎn)擊按鈕的時(shí)候,textview 開始向右下角移動(dòng)。 從結(jié)果中可以看出,在移動(dòng)前,點(diǎn)擊 textview 是可以彈出 toast 的的,在移動(dòng)后,點(diǎn)擊 textview 時(shí)則沒有響應(yīng),相反,點(diǎn)擊 textview 的原來(lái)所在區(qū)域則會(huì)彈出 toast. 這就論證了不同第三點(diǎn):補(bǔ)間動(dòng)畫雖能對(duì)控件做動(dòng)畫,但并沒有改變控件內(nèi)部的屬性值 下面簡(jiǎn)單看看這個(gè)動(dòng)畫的實(shí)現(xiàn)代碼吧:
(1)、看布局(main.xml) 從效果圖中也可以看出,布局很簡(jiǎn)單,一個(gè) button,一個(gè) textview,垂直排列,布局代碼如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<Button
android:id="@+id/btn"
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:padding="10dp"
android:background="#ffff00"
android:text="Hello qijian"/>
</LinearLayout>
(2)JAVA 代碼(MyActivity.java) 接下來(lái)是操作代碼,就是分別給 button 和 textview 添加上點(diǎn)擊響應(yīng),當(dāng)點(diǎn)擊 textview 時(shí)彈出 toast,點(diǎn)擊 button 時(shí),textview 使用移動(dòng)。 代碼如下:
public class MyActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
final TextView tv = (TextView) findViewById(R.id.tv);
Button btn = (Button)findViewById(R.id.btn);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
final TranslateAnimation animation = new TranslateAnimation(Animation.ABSOLUTE, 0, Animation.ABSOLUTE, 400,
Animation.ABSOLUTE, 0, Animation.ABSOLUTE, 400);
animation.setFillAfter(true);
animation.setDuration(1000);
tv.startAnimation(animation);
}
});
tv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MyActivity.this,"clicked me",Toast.LENGTH_SHORT).show();
}
});
}
}
這段代碼很容易理解,這里就不再細(xì)講了。 源碼在文章底部給出
我們前面講了 Property Animator 包括 ValueAnimator 和 ObjectAnimator;這篇文章就主要來(lái)看看 ValueAnimator 的使用方法吧。 我覺得谷歌那幫老頭是最會(huì)起名字的人,單從命名上,就能看出來(lái)這個(gè)東東的含義。ValueAnimator 從名字可以看出,這個(gè) Animation 是針對(duì)值的!ValueAnimator 不會(huì)對(duì)控件做任何操作,我們可以給它設(shè)定從哪個(gè)值運(yùn)動(dòng)到哪個(gè)值,通過監(jiān)聽這些值的漸變過程來(lái)自己操作控件。以前我們?cè)v過 Scroller 類,Scroller 類也是不會(huì)對(duì)控件操作的,也是通過給他設(shè)定滾動(dòng)值和時(shí)長(zhǎng),它會(huì)自己計(jì)算滾動(dòng)過程,然后我們需要監(jiān)聽它的動(dòng)畫過程來(lái)自己操作控件,ValueAnimator 的原理與 Scroller 類相似。有關(guān) Scroller 的知識(shí),大家可以參考:《 ListView 滑動(dòng)刪除實(shí)現(xiàn)之四——Scroller 類與 listview 緩慢滑動(dòng)》
要使用 ValueAnimaiton,總共有兩步: 第一步:創(chuàng)建 ValueAnimator 實(shí)例
ValueAnimator animator = ValueAnimator.ofInt(0,400);
animator.setDuration(1000);
animator.start();
在這里我們利用 ValueAnimator.ofInt 創(chuàng)建了一個(gè)值從 0 到 400 的動(dòng)畫,動(dòng)畫時(shí)長(zhǎng)是 1s,然后讓動(dòng)畫開始。從這段代碼中可以看出,ValueAnimator 沒有跟任何的控件相關(guān)聯(lián),那也正好說明 ValueAnimator 只是對(duì)值做動(dòng)畫運(yùn)算,而不是針對(duì)控件的,我們需要監(jiān)聽 ValueAnimator 的動(dòng)畫過程來(lái)自己對(duì)控件做操作。
第二步:添加監(jiān)聽 上面的三行代碼,我們已經(jīng)實(shí)現(xiàn)了動(dòng)畫,下面我們就添加監(jiān)聽:
ValueAnimator animator = ValueAnimator.ofInt(0,400);
animator.setDuration(1000);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int curValue = (int)animation.getAnimatedValue();
Log.d("qijian","curValue:"+curValue);
}
});
animator.start();
在上面的代碼中,我們通過 addUpdateListener 添加了一個(gè)監(jiān)聽,在監(jiān)聽傳回的結(jié)果中,是表示當(dāng)前狀態(tài)的 ValueAnimator 實(shí)例,我們通過 animation.getAnimatedValue()得到當(dāng)前值。然后通過 Log 打印出來(lái),結(jié)果如下:
http://wiki.jikexueyuan.com/project/android-animation/images/7.png" alt="" />
這就是 ValueAnimator 的功能:ValueAnimator 對(duì)指定值區(qū)間做動(dòng)畫運(yùn)算,我們通過對(duì)運(yùn)算過程做監(jiān)聽來(lái)自己操作控件。 總而言之就是兩點(diǎn):
這段,我們就使用上面我們講到的 ValueAnimator 做一個(gè)動(dòng)畫: 我們先看看效果圖:
http://wiki.jikexueyuan.com/project/android-animation/images/54.gif" alt="" />
首先這個(gè)動(dòng)畫的布局與上一個(gè)實(shí)例是一樣的。但實(shí)現(xiàn)的效果確不大相同:
下面我們就來(lái)看看這里如何利用 ValueAnimator 來(lái)實(shí)現(xiàn)這個(gè)效果的。 1、布局(main.xml) 布局代碼與上個(gè)例子相同:垂直布局按鈕控件和 textview
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<Button
android:id="@+id/btn"
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:padding="10dp"
android:background="#ffff00"
android:text="Hello qijian"/>
</LinearLayout>
2、JAVA 操作 首先,是對(duì) textview 和 btn 添加點(diǎn)擊響應(yīng),當(dāng)點(diǎn)擊 textview 時(shí),彈出 toast;點(diǎn)擊 btn 時(shí),textview 開始做動(dòng)畫
public class MyActivity extends Activity {
private TextView tv;
private Button btn;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
tv = (TextView) findViewById(R.id.tv);
btn = (Button) findViewById(R.id.btn);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
doAnimation();
}
});
tv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MyActivity.this, "clicked me", Toast.LENGTH_SHORT).show();
}
});
}
…………
}
這段代碼很簡(jiǎn)單,在點(diǎn)擊 btn 的時(shí)候執(zhí)行 doAnimation()來(lái)執(zhí)行動(dòng)畫操作,在點(diǎn)擊 tv 的時(shí)候,彈出 toast; 下面來(lái)看看 doAnimation()的具體實(shí)現(xiàn):
private void doAnimation(){
ValueAnimator animator = ValueAnimator.ofInt(0,400);
animator.setDuration(1000);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int curValue = (int)animation.getAnimatedValue();
tv.layout(curValue,curValue,curValue+tv.getWidth(),curValue+tv.getHeight());
}
});
animator.start();
}
首先,我們構(gòu)造一個(gè) ValueAnimator 實(shí)例,讓其計(jì)算的值是從 0 到 400; 然后添加對(duì)計(jì)算過程進(jìn)行監(jiān)聽:
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int curValue = (int)animation.getAnimatedValue();
tv.layout(curValue,curValue,curValue+tv.getWidth(),curValue+tv.getHeight());
}
});
在監(jiān)聽過程中,通過 layout 函數(shù)來(lái)改變 textview 的位置。這里注意了,我們是通過 layout 函數(shù)來(lái)改變位置的,我們知道 layout 函數(shù)在改變控件位置時(shí)是永久性的,即通過更改控件 left,top,right,bottom 這四個(gè)點(diǎn)的坐標(biāo)來(lái)改更改坐標(biāo)位置的,而不僅僅是從視覺上畫在哪個(gè)位置,所以通過 layout 函數(shù)更改位置后,控件在新位置是可以響應(yīng)點(diǎn)擊事件的。 大家可能注意到了,layout()函數(shù)中上下左右點(diǎn)的坐標(biāo)是以屏幕坐標(biāo)來(lái)標(biāo)準(zhǔn)的。所以在效果圖中可以看到,textview 的運(yùn)動(dòng)軌跡是從屏幕的左上角(0,0)點(diǎn)運(yùn)行到(400,400)點(diǎn)。 源碼在文章底部給出
經(jīng)過上面的例子,我們就大概知道 ValueAnimator 要怎么使用了,下面我們就來(lái)具體來(lái)看看它還有哪些常用的方法吧。
在上面的例子中,我們使用了 ofInt 函數(shù),與它同樣功能的還有一個(gè)函數(shù)叫 ofFloat,下面我們先看看他們的具體聲明:
public static ValueAnimator ofInt(int... values)
public static ValueAnimator ofFloat(float... values)
他們的參數(shù)類型都是可變參數(shù)長(zhǎng)參數(shù),所以我們可以傳入任何數(shù)量的值;傳進(jìn)去的值列表,就表示動(dòng)畫時(shí)的變化范圍;比如 ofInt(2,90,45)就表示從數(shù)值 2 變化到數(shù)字 90 再變化到數(shù)字 45;所以我們傳進(jìn)去的數(shù)字越多,動(dòng)畫變化就越復(fù)雜。從參數(shù)類型也可以看出 ofInt 與 ofFloat 的唯一區(qū)別就是傳入的數(shù)字類型不一樣,ofInt 需要傳入 Int 類型的參數(shù),而 ofFloat 則表示需要傳入 Float 類型的參數(shù)。 下面我們還在上面例子的基礎(chǔ)上,使用 ofFloat 函數(shù)來(lái)舉個(gè)例子:
ValueAnimator animator = ValueAnimator.ofFloat(0f,400f,50f,300f);
animator.setDuration(3000);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
Float curValueFloat = (Float)animation.getAnimatedValue();
int curValue = curValueFloat.intValue();
tv.layout(curValue,curValue,curValue+tv.getWidth(),curValue+tv.getHeight());
}
});
animator.start();
先看看效果:
http://wiki.jikexueyuan.com/project/android-animation/images/55.gif" alt="" />
在效果圖中,我們可以看到,在點(diǎn)擊按鈕之后,textview 先向右下運(yùn)動(dòng)然后再回來(lái),然后再向右下運(yùn)動(dòng)過去 在這個(gè)例子中,我們使用 ValueAnimator.ofFloat(0f,400f,50f,300f)構(gòu)造了一個(gè)比較復(fù)雜的動(dòng)畫漸變,值是 0 變到 400 再回到 50 最后變成 300; 所以我們?cè)诒O(jiān)聽時(shí),首先得到當(dāng)前動(dòng)畫的值:
Float curValueFloat = (Float)animation.getAnimatedValue();
通過 getAnimatedValue()來(lái)獲取當(dāng)前運(yùn)動(dòng)點(diǎn)的值,大家可能會(huì)疑問為什么要轉(zhuǎn)成 Float 類型,我們先來(lái)看看 getAnimatedValue()的聲明:
Object getAnimatedValue();
它返回的類型是一個(gè) Object 原始類型,那我們?cè)趺粗牢覀円獙⑺鼜?qiáng)轉(zhuǎn)成什么類型呢。注意,我們?cè)谠O(shè)定動(dòng)畫初始值時(shí)用的是 ofFloat()函數(shù),所以每個(gè)值的類型必定是 Float 類型,所以我們獲取出來(lái)的類型也必然是 Float 類型的。同樣,如果我們使用 ofInt 設(shè)定的初始值,那么通過 getAnimatedValue()獲取到的值就應(yīng)該強(qiáng)轉(zhuǎn)為 Int 類型。 在得到當(dāng)前運(yùn)動(dòng)的值以后,通過 layout 函數(shù)將 textview 移動(dòng)到指定位置即可。
源碼在文章底部給出
先做個(gè)匯總,這部分將講述的方法有:
/**
* 設(shè)置動(dòng)畫時(shí)長(zhǎng),單位是毫秒
*/
ValueAnimator setDuration(long duration)
/**
* 獲取 ValueAnimator 在運(yùn)動(dòng)時(shí),當(dāng)前運(yùn)動(dòng)點(diǎn)的值
*/
Object getAnimatedValue();
/**
* 開始動(dòng)畫
*/
void start()
/**
* 設(shè)置循環(huán)次數(shù),設(shè)置為 INFINITE 表示無(wú)限循環(huán)
*/
void setRepeatCount(int value)
/**
* 設(shè)置循環(huán)模式
* value 取值有 RESTART,REVERSE,
*/
void setRepeatMode(int value)
/**
* 取消動(dòng)畫
*/
void cancel()
1、setDuration()、getAnimatedValue()、start() 這三個(gè)函數(shù)在上面的實(shí)例中已經(jīng)使用過,setDuration(long duration)是設(shè)置一次動(dòng)畫的時(shí)長(zhǎng),單位是毫秒,start()是開始動(dòng)畫,唯一有點(diǎn)難度的是 Object getAnimatedValue(),它的聲明為:
Object getAnimatedValue();
它的意義就是獲取動(dòng)畫在當(dāng)前運(yùn)動(dòng)點(diǎn)的值,所以這個(gè)對(duì)象只能用于在動(dòng)畫運(yùn)動(dòng)中。返回的值是 Object,上面我們說過,通過 getAnimatedValue()得到的值的實(shí)際類型與初始設(shè)置的值相同,如果我們利用 ofInt()設(shè)置的動(dòng)畫,那通過 getAnimatedValue()得到的值為類型就是 Int 類型。如果我們利用 ofFloat()設(shè)置的動(dòng)畫,通過 getAnimatedValue()得到的值類型就是 Float 類型。 總而言之,通過 getAnimatedValue()值類型與初始設(shè)置動(dòng)畫時(shí)的值類型相同 上面我們已經(jīng)用過這些函數(shù)了,這里就不再舉例了。
2、setRepeatCount()、setRepeatMode()、cancel() setRepeatCount(int value)用于設(shè)置動(dòng)畫循環(huán)次數(shù),設(shè)置為 0 表示不循環(huán),設(shè)置為 ValueAnimation.INFINITE 表示無(wú)限循環(huán)。 cancel()用于取消動(dòng)畫 我們著重說一下 setRepeatMode:
/**
* 設(shè)置循環(huán)模式
* value 取值有 RESTART,REVERSE
*/
void setRepeatMode(int value)
setRepeatMode(int value)用于設(shè)置循環(huán)模式,取值為 ValueAnimation.RESTART 時(shí),表示正序重新開始,當(dāng)取值為 ValueAnimation.REVERSE 表示倒序重新開始。 下面我們使用這三個(gè)函數(shù)來(lái)舉個(gè)例子,先看下動(dòng)畫效果:
http://wiki.jikexueyuan.com/project/android-animation/images/56.gif" alt="" />
在這里,有兩個(gè)按鈕,當(dāng)點(diǎn)擊 start anim 時(shí),textview 垂直向下運(yùn)動(dòng),我定義的運(yùn)動(dòng)初始值為 ofInt(0,400);所以從效果圖中也可以看出我們定義它為無(wú)限循環(huán),而且每次循環(huán)時(shí)都是使用 ValueAnimation.REVERSE 讓其倒序重新開始循環(huán)。當(dāng)我們點(diǎn)擊 cancel anim 時(shí),取消動(dòng)畫。 下面我們來(lái)看看代碼 首先是布局代碼,布局代碼時(shí),采用 RelativeLayout 布局,將兩個(gè)按鈕放兩邊,textview 放中間,代碼如下:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<Button
android:id="@+id/btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:padding="10dp"
android:text="start anim"
/>
<Button
android:id="@+id/btn_cancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:padding="10dp"
android:text="cancel anim"
/>
<TextView
android:id="@+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:padding="10dp"
android:background="#ffff00"
android:text="Hello qijian"/>
</RelativeLayout>
這個(gè)布局代碼沒什么難度就不講了。 下面來(lái)看看兩個(gè)按鈕的操作代碼:
private Button btnStart,btnCancel;
private ValueAnimator repeatAnimator;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
tv = (TextView) findViewById(R.id.tv);
btnStart = (Button) findViewById(R.id.btn);
btnCancel = (Button)findViewById(R.id.btn_cancel);
btnStart.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
repeatAnimator = doRepeatAnim();
}
});
btnCancel.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
repeatAnimator.cancel();
}
});
}
這段代碼也沒什么難度,當(dāng)我們點(diǎn)擊 btnStart 的時(shí)候,執(zhí)行 doRepeatAnim()函數(shù),這個(gè)函數(shù)返回它構(gòu)造的 ValueAnimator 對(duì)象,將其賦值給 repeatAnimator 變量。當(dāng)點(diǎn)擊 btnCancel 時(shí),調(diào)用 repeatAnimator.cancel()取消當(dāng)前動(dòng)畫。 下面我們來(lái)看看 doRepeatAnim()函數(shù)都做了哪些工作:
private ValueAnimator doRepeatAnim(){
ValueAnimator animator = ValueAnimator.ofInt(0,400);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int curValue = (int)animation.getAnimatedValue();
tv.layout(tv.getLeft(),curValue,tv.getRight(),curValue+tv.getHeight());
}
});
animator.setRepeatMode(ValueAnimator.REVERSE);
animator.setRepeatCount(ValueAnimator.INFINITE);
animator.setDuration(1000);
animator.start();
return animator;
}
在這里我們構(gòu)造了一個(gè) ValueAnimator,動(dòng)畫范圍是 0-400,設(shè)置重復(fù)次數(shù)為無(wú)限循環(huán)。循環(huán)模式為倒序。在 animator.setDuration(1000)表示動(dòng)畫一次的時(shí)長(zhǎng)為 1000 毫秒。最后,由于我們?cè)谌∠麆?dòng)畫時(shí)還需要我們構(gòu)造的這個(gè) ValueAnimator 實(shí)例,所以將 animator 返回。 源碼在文章底部給出
(1)、添加監(jiān)聽器 前面,我們講過一個(gè)添加監(jiān)聽器 animator.addUpdateListener,以監(jiān)聽動(dòng)畫過程中值的實(shí)時(shí)變化,其實(shí)在 ValueAnimator 中共有兩個(gè)監(jiān)聽器:
/**
* 監(jiān)聽器一:監(jiān)聽動(dòng)畫變化時(shí)的實(shí)時(shí)值
*/
public static interface AnimatorUpdateListener {
void onAnimationUpdate(ValueAnimator animation);
}
//添加方法為:public void addUpdateListener(AnimatorUpdateListener listener)
/**
* 監(jiān)聽器二:監(jiān)聽動(dòng)畫變化時(shí)四個(gè)狀態(tài)
*/
public static interface AnimatorListener {
void onAnimationStart(Animator animation);
void onAnimationEnd(Animator animation);
void onAnimationCancel(Animator animation);
void onAnimationRepeat(Animator animation);
}
//添加方法為:public void addListener(AnimatorListener listener)
關(guān)于監(jiān)聽器一:AnimatorUpdateListener 就是監(jiān)聽動(dòng)畫的實(shí)時(shí)變化狀態(tài),在 onAnimationUpdate(ValueAnimator animation)中的 animation 表示當(dāng)前狀態(tài)動(dòng)畫的實(shí)例。這里就不再細(xì)講這個(gè)監(jiān)聽器了,這里我們主要講講監(jiān)聽器 AnimatorListener; 在 AnimatorListener 中,主要是監(jiān)聽 Animation 的四個(gè)狀態(tài),start、end、cancel、repeat;當(dāng)動(dòng)畫開始時(shí),會(huì)調(diào)用 onAnimationStart(Animator animation)方法,當(dāng)動(dòng)畫結(jié)束時(shí)調(diào)用 onAnimationEnd(Animator animation),當(dāng)動(dòng)畫取消時(shí),調(diào)用 onAnimationCancel(Animator animation)函數(shù),當(dāng)動(dòng)畫重復(fù)時(shí),會(huì)調(diào)用 onAnimationRepeat(Animator animation)函數(shù)。 添加 AnimatorListener 的方法是 addListener(AnimatorListener listener) ; 下面我們就舉個(gè)例子來(lái)看一下 AnimatorListener 的使用方法。 我們?cè)谏厦?doRepeatAnim()函數(shù)的基礎(chǔ)上,添加上 AnimatorListener,代碼如下: 代碼如下:
private ValueAnimator doAnimatorListener(){
ValueAnimator animator = ValueAnimator.ofInt(0,400);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int curValue = (int)animation.getAnimatedValue();
tv.layout(tv.getLeft(),curValue,tv.getRight(),curValue+tv.getHeight());
}
});
animator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
Log.d("qijian","animation start");
}
@Override
public void onAnimationEnd(Animator animation) {
Log.d("qijian","animation end");
}
@Override
public void onAnimationCancel(Animator animation) {
Log.d("qijian","animation cancel");
}
@Override
public void onAnimationRepeat(Animator animation) {
Log.d("qijian","animation repeat");
}
});
animator.setRepeatMode(ValueAnimator.REVERSE);
animator.setRepeatCount(ValueAnimator.INFINITE);
animator.setDuration(1000);
animator.start();
return animator;
}
在上面的代碼中,我們是在 doRepeatAnim()函數(shù)的基礎(chǔ)上,又添加了 AnimatorListener()以監(jiān)聽它的狀態(tài),并把這些狀態(tài)打印出來(lái)。 我們來(lái)看看動(dòng)畫效果:
http://wiki.jikexueyuan.com/project/android-animation/images/57.gif" alt="" />
打印出來(lái)結(jié)果如下:
http://wiki.jikexueyuan.com/project/android-animation/images/8.png" alt="" />
(2)、取消監(jiān)聽 上面我們講了如何添加監(jiān)聽函數(shù),下面我們來(lái)看看如何移除監(jiān)聽器:
/**
* 移除 AnimatorUpdateListener
*/
void removeUpdateListener(AnimatorUpdateListener listener);
void removeAllUpdateListeners();
/**
* 移除 AnimatorListener
*/
void removeListener(AnimatorListener listener);
void removeAllListeners();
針對(duì) AnimatorUpdateListener 和 AnimatorListener,每個(gè)監(jiān)聽器都有兩個(gè)方法來(lái)移除;我們就以移除 AnimatorListener 來(lái)簡(jiǎn)單講一下,removeListener(AnimatorListener listener)用于在 animator 中移除指定的監(jiān)聽器,而 removeAllListeners()用于移除 animator 中所有的 AnimatorListener 監(jiān)聽器; 下面上在添加監(jiān)聽器的例子基礎(chǔ)上,不改變 doAnimatorListener()的代碼,仍然是 textview 做動(dòng)畫時(shí)添加 AnimatorListener 的狀態(tài)監(jiān)聽。然后點(diǎn)擊 cancelAnim 時(shí),移除 AnimatorListener,代碼如下: AnimatorListener 的代碼:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
…………
btnStart.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
repeatAnimator = doAnimatorListener();
}
});
btnCancel.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
repeatAnimator.removeAllListeners();
}
});
}
doAnimatorListener 的代碼與上面的一樣,就不再重復(fù)貼了,當(dāng)點(diǎn)擊 btnCancel 時(shí)移除 animator 中所有的 AnimatorListener,但注意的是,我們?cè)谝瞥?AnimatorListener 后,并沒有 cancel 動(dòng)畫效果,所以動(dòng)畫會(huì)一直不停的運(yùn)動(dòng)下去。但移除 AnimatorListener 之后,Log 應(yīng)該就不會(huì)再打印了。 效果如下:
http://wiki.jikexueyuan.com/project/android-animation/images/58.gif" alt="" />
在效果圖中,在動(dòng)畫循環(huán)了三次之后,我們點(diǎn)擊 btnCancel 移除所有的 AnimatorListener;打印 tag 如下:
http://wiki.jikexueyuan.com/project/android-animation/images/9.png" alt="" />
可見只打印了循環(huán)三次以前的 log,在移除我們添加的 AnimatorListener 之后,我們打印 log 的代碼就不會(huì)再執(zhí)行了,所以也就不會(huì)再有 log 了。 好了,有關(guān)監(jiān)聽器的部分,我們就到這里了
源碼在文章底部給出
上面我們講了 ValueAnimator 中常用的一些函數(shù),但是還有一些函數(shù)雖然不常用,但我們還是簡(jiǎn)單講一下,他們分別是:
/**
* 延時(shí)多久時(shí)間開始,單位是毫秒
*/
public void setStartDelay(long startDelay)
/**
* 完全克隆一個(gè) ValueAnimator 實(shí)例,包括它所有的設(shè)置以及所有對(duì)監(jiān)聽器代碼的處理
*/
public ValueAnimator clone()
setStartDelay(long startDelay)非常容易理解,就是設(shè)置多久后動(dòng)畫才開始。 但 clone()這個(gè)函數(shù)就有點(diǎn)難度了;首先是什么叫克隆。就是完全一樣!注意是完全一樣!就是復(fù)制出來(lái)一個(gè)完全一樣的新的 ValueAnimator 實(shí)例出來(lái)。對(duì)原來(lái)的那個(gè) ValueAnimator 是怎么處理的,在這個(gè)新的實(shí)例中也是全部一樣的。 我們來(lái)看一個(gè)例子來(lái)看一下,什么叫全部一樣: 首先,我們定義一個(gè)函數(shù) doRepeatAnim():
private ValueAnimator doRepeatAnim(){
ValueAnimator animator = ValueAnimator.ofInt(0,400);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int curValue = (int)animation.getAnimatedValue();
tv.layout(tv.getLeft(),curValue,tv.getRight(),curValue+tv.getHeight());
}
});
animator.setDuration(1000);
animator.setRepeatMode(ValueAnimator.REVERSE);
animator.setRepeatCount(ValueAnimator.INFINITE);
return animator;
}
這個(gè)函數(shù)其實(shí)與上面在講循環(huán)函數(shù)時(shí)的 doRepeatAnim()函數(shù)是一樣的;在這個(gè)函數(shù)中,我們定義一個(gè) ValueAnimator,設(shè)置為無(wú)限循環(huán),然后添加 AnimatorUpdateListener 監(jiān)聽;在動(dòng)畫在運(yùn)動(dòng)時(shí),向下移動(dòng) textview.這里要非常注意的一點(diǎn)是我們只是定義了一個(gè) ValueAnimator 對(duì)象,并沒有調(diào)用 start()讓動(dòng)畫開始!?。?! 然后我們?cè)倏纯袋c(diǎn)擊 btnStart 和 btnCancel 時(shí)的代碼處理:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
…………
btnStart.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
repeatAnimator = doRepeatAnim();
//克隆一個(gè)新的 ValueAnimator,然后開始動(dòng)畫
ValueAnimator newAnimator = repeatAnimator.clone();
newAnimator.setStartDelay(1000);
newAnimator.start();
}
});
btnCancel.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
repeatAnimator.removeAllUpdateListeners();
repeatAnimator.cancel();
}
});
}
在上面的代碼中,我們?cè)邳c(diǎn)擊 btnStart 時(shí):
repeatAnimator = doRepeatAnim();
//克隆一個(gè)新的 ValueAnimator,然后開始動(dòng)畫
ValueAnimator newAnimator = repeatAnimator.clone();
newAnimator.setStartDelay(1000);
newAnimator.start();
我們利用 clone()克隆了一個(gè) doRepeatAnim()生成的對(duì)象。然后調(diào)用 setStartDelay(1000);將動(dòng)畫開始時(shí)間設(shè)為 1000 毫秒后開始動(dòng)畫。最后調(diào)用 start()函數(shù)開始動(dòng)畫。 這里有一點(diǎn)非常注意是:我們除了對(duì) newAnimator 設(shè)置了動(dòng)畫開始延時(shí) 1000 毫秒以后,沒有對(duì)它進(jìn)行任何設(shè)置,更沒有在在它的監(jiān)聽器中對(duì) textview 的處理?。。?!那 textview 會(huì)動(dòng)嗎?答案是會(huì)動(dòng)的,我們講了,克隆就是完全一樣,在原來(lái)的 ValueAnimator 中是如何處理的,克隆過來(lái)的 ValueAnimator 也是完全一樣的處理方式! 在點(diǎn)擊 btnCancel 時(shí):
repeatAnimator.removeAllUpdateListeners();
repeatAnimator.cancel();
我們既移除了 repeatAnimator 的監(jiān)聽器又取消了動(dòng)畫。但有用嗎?必須當(dāng)然是沒用的,因?yàn)槲覀?start 的動(dòng)畫對(duì)象是從 repeatAnimator 克隆來(lái)的 newAnimator。這好比是克隆羊,原來(lái)的羊和克隆羊什么都是一樣的,但你把原來(lái)的羊殺了,克隆的羊會(huì)死嗎?用大腳指頭想都知道不會(huì)!所以如果要取消當(dāng)前的動(dòng)畫必須通過 newAnimator.cancel()來(lái)取消 效果圖如下:
http://wiki.jikexueyuan.com/project/android-animation/images/59.gif" alt="" />
從效果圖中也可以看出,點(diǎn)擊 btnCancel 按鈕是沒有做用的,并沒能取消動(dòng)畫。
源碼在文章底部給出 好了,到這里有關(guān) ValueAnimator 的常用函數(shù)基本就講完了,下篇將更深入的講解 ValueAnimator 的高級(jí)用法。 源碼內(nèi)容:
1、《try_tween_anim_click》:對(duì)應(yīng)概述中:舉例說明補(bǔ)間動(dòng)畫的點(diǎn)擊區(qū)域問題 2、《BlogValueAnimator1》:對(duì)應(yīng):《實(shí)例使用 ValueAnimator》和《ofInt 與 ofFloat》兩部分源碼 3、《BlogValueAnimator2》:對(duì)應(yīng)以后所有的源碼
如果本文有幫到你,記得加關(guān)注哦 源碼地址:
csdn:http://download.csdn.net/detail/harvic880925/9405988 github:https://github.com/harvic/BlogResForGitHub
請(qǐng)大家尊重原創(chuàng)者版權(quán),轉(zhuǎn)載請(qǐng)標(biāo)明出處:http://blog.csdn.net/harvic880925/article/details/50525521