- 屬性動(dòng)畫(huà)簡(jiǎn)介
Android開(kāi)發(fā)過(guò)程中买猖,適當(dāng)?shù)氖褂靡恍﹦?dòng)畫(huà)可以讓自己的應(yīng)用看起來(lái)更棒更炫每界。最初的時(shí)候Google為了實(shí)現(xiàn)動(dòng)畫(huà)瞬测,主要提供了兩種基本動(dòng)畫(huà):
幀動(dòng)畫(huà):將一個(gè)完整的動(dòng)畫(huà)拆分成一張張單獨(dú)的圖片拓劝,然后再將它們連貫起來(lái)進(jìn)行播放
補(bǔ)間動(dòng)畫(huà):無(wú)需逐一定義每一幀底循,只要定義開(kāi)始仿滔、結(jié)束的幀惠毁,和指定動(dòng)畫(huà)持續(xù)時(shí)間。
到了Android3.0堤撵,Google推出了一種新的動(dòng)畫(huà)模式仁讨,屬性動(dòng)畫(huà)。屬性動(dòng)畫(huà)是對(duì)補(bǔ)間動(dòng)畫(huà)的進(jìn)一步完善和強(qiáng)化实昨。
- 為什么引入屬性動(dòng)畫(huà):
關(guān)于這個(gè)問(wèn)題洞豁,可以借鑒一下郭霖的一段話:
補(bǔ)間動(dòng)畫(huà)機(jī)制其實(shí)還算是比較健全的,可以讓我們實(shí)現(xiàn)View的移動(dòng)荒给、縮放丈挟、旋轉(zhuǎn)和淡入淡出效果,并且我們還可以借助AnimationSet來(lái)將這些動(dòng)畫(huà)效果組合起來(lái)使用志电,除此之外還可以通過(guò)配置Interpolator來(lái)控制動(dòng)畫(huà)的播放速度等等等等曙咽。那么這里大家可能要產(chǎn)生疑問(wèn)了,既然之前的動(dòng)畫(huà)機(jī)制已經(jīng)這么健全了挑辆,為什么還要引入屬性動(dòng)畫(huà)呢例朱?
其實(shí)上面所謂的健全都是相對(duì)的孝情,如果你的需求中只需要對(duì)View進(jìn)行移動(dòng)、縮放洒嗤、旋轉(zhuǎn)和淡入淡出操作箫荡,那么補(bǔ)間動(dòng)畫(huà)確實(shí)已經(jīng)足夠健全了。但是很顯然渔隶,這些功能是不足以覆蓋所有的場(chǎng)景的羔挡,一旦我們的需求超出了移動(dòng)、縮放间唉、旋轉(zhuǎn)和淡入淡出這四種對(duì)View的操作绞灼,那么補(bǔ)間動(dòng)畫(huà)就不能再幫我們忙了,也就是說(shuō)它在功能和可擴(kuò)展方面都有相當(dāng)大的局限性呈野,那么下面我們就來(lái)看看補(bǔ)間動(dòng)畫(huà)所不能勝任的場(chǎng)景低矮。
注意上面我在介紹補(bǔ)間動(dòng)畫(huà)的時(shí)候都有使用“對(duì)View進(jìn)行操作”這樣的描述,沒(méi)錯(cuò)际跪,補(bǔ)間動(dòng)畫(huà)是只能夠作用在View上的商佛。也就是說(shuō),我們可以對(duì)一個(gè)Button姆打、TextView良姆、甚至是LinearLayout、或者其它任何繼承自View的組件進(jìn)行動(dòng)畫(huà)操作幔戏,但是如果我們想要對(duì)一個(gè)非View的對(duì)象進(jìn)行動(dòng)畫(huà)操作玛追,抱歉,補(bǔ)間動(dòng)畫(huà)就幫不上忙了闲延∪剩可能有的朋友會(huì)感到不能理解,我怎么會(huì)需要對(duì)一個(gè)非View的對(duì)象進(jìn)行動(dòng)畫(huà)操作呢垒玲?這里我舉一個(gè)簡(jiǎn)單的例子陆馁,比如說(shuō)我們有一個(gè)自定義的View,在這個(gè)View當(dāng)中有一個(gè)Point對(duì)象用于管理坐標(biāo)合愈,然后在onDraw()方法當(dāng)中就是根據(jù)這個(gè)Point對(duì)象的坐標(biāo)值來(lái)進(jìn)行繪制的叮贩。也就是說(shuō),如果我們可以對(duì)Point對(duì)象進(jìn)行動(dòng)畫(huà)操作佛析,那么整個(gè)自定義View的動(dòng)畫(huà)效果就有了益老。顯然,補(bǔ)間動(dòng)畫(huà)是不具備這個(gè)功能的寸莫,這是它的第一個(gè)缺陷捺萌。
然后補(bǔ)間動(dòng)畫(huà)還有一個(gè)缺陷,就是它只能夠?qū)崿F(xiàn)移動(dòng)膘茎、縮放桃纯、旋轉(zhuǎn)和淡入淡出這四種動(dòng)畫(huà)操作酷誓,那如果我們希望可以對(duì)View的背景色進(jìn)行動(dòng)態(tài)地改變呢?很遺憾慈参,我們只能靠自己去實(shí)現(xiàn)了呛牲。說(shuō)白了,之前的補(bǔ)間動(dòng)畫(huà)機(jī)制就是使用硬編碼的方式來(lái)完成的驮配,功能限定死就是這些,基本上沒(méi)有任何擴(kuò)展性可言着茸。
最后壮锻,補(bǔ)間動(dòng)畫(huà)還有一個(gè)致命的缺陷,就是它只是改變了View的顯示效果而已涮阔,而不會(huì)真正去改變View的屬性猜绣。什么意思呢?比如說(shuō)敬特,現(xiàn)在屏幕的左上角有一個(gè)按鈕掰邢,然后我們通過(guò)補(bǔ)間動(dòng)畫(huà)將它移動(dòng)到了屏幕的右下角,現(xiàn)在你可以去嘗試點(diǎn)擊一下這個(gè)按鈕伟阔,點(diǎn)擊事件是絕對(duì)不會(huì)觸發(fā)的辣之,因?yàn)閷?shí)際上這個(gè)按鈕還是停留在屏幕的左上角,只不過(guò)補(bǔ)間動(dòng)畫(huà)將這個(gè)按鈕繪制到了屏幕的右下角而已皱炉。正是因?yàn)檫@些原因怀估,功能更為完善和強(qiáng)大的屬性動(dòng)畫(huà)就應(yīng)運(yùn)而生了。
- 繼承關(guān)系
從圖中可以看出合搅,補(bǔ)間動(dòng)畫(huà)主要是使用android.view.animation.Animation包下面的五個(gè)類來(lái)實(shí)現(xiàn)不同的動(dòng)畫(huà)效果多搀;而屬性動(dòng)畫(huà)主要是使用android.animation.Animator的ValueAnimator以及其子類ObjectAnimator來(lái)實(shí)現(xiàn)不同的動(dòng)畫(huà)效果。
- ValueAnimator
從屬性動(dòng)畫(huà)相關(guān)對(duì)象的繼承關(guān)系可以看出灾部,ValueAnimator是比較核心的一個(gè)類康铭。官方文檔的介紹是:this class provides a simple timing engine for running animations which calculate animated values and set them on target objects...也就是說(shuō)ValueAnimator對(duì)象內(nèi)部維持了一個(gè)簡(jiǎn)單的時(shí)間引擎,計(jì)算運(yùn)行動(dòng)畫(huà)的具體值同時(shí)把它們?cè)O(shè)置給目標(biāo)對(duì)象赌髓。通俗的講就是从藤,ValueAnimator的內(nèi)部使用一種時(shí)間循環(huán)的機(jī)制來(lái)計(jì)算值與值之間的動(dòng)畫(huà)過(guò)渡,我們只需要將初始值和結(jié)束值或者中間值提供給ValueAnimator春弥,并且設(shè)置動(dòng)畫(huà)運(yùn)行時(shí)長(zhǎng)呛哟,那么ValueAnimator就會(huì)自動(dòng)完成從初始值平滑過(guò)渡到結(jié)束值這樣的效果。而且匿沛,ValueAnimator還負(fù)責(zé)管理動(dòng)畫(huà)的播放次數(shù)扫责、播放模式、以及對(duì)動(dòng)畫(huà)設(shè)置監(jiān)聽(tīng)器等逃呼。說(shuō)的廢話比較多鳖孤,先舉個(gè)栗子者娱,切身體會(huì)一下!
1.效果圖
2.具體代碼
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0f, 1f);
valueAnimator.setDuration(3000);
//設(shè)置監(jiān)聽(tīng)苏揣,把當(dāng)前AnimatedValue的值時(shí)時(shí)顯示到EditText上面
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float currentValue = (float) animation.getAnimatedValue();
StringBuilder sb = new StringBuilder();
et_Animator.setText(sb.append(currentValue).toString());
}
});
valueAnimator.start();
ValueAnimator比較常用的方法有:設(shè)置AnimatorValue區(qū)間范圍的三個(gè)主要方法
ofFloat()黄鳍,ofInt(),ofObject()
還有設(shè)置延時(shí)播放時(shí)間的setStartDelay()方法平匈,設(shè)置重復(fù)次數(shù)的setRepeatCount()方法框沟,設(shè)置重復(fù)模式的setRepeatMode()
暫停動(dòng)畫(huà)的pause()方法,恢復(fù)動(dòng)畫(huà)的resume()等等增炭,還有很多方法忍燥,具體大家可以查閱文檔,把這個(gè)核心類的方法掌握了隙姿,使用ObjectAnimator就能更加得心應(yīng)手梅垄,因?yàn)閺睦^承關(guān)系中我們可以知道ObjectAnimator是ValueAnimator的子類。
- ObjectAnimator
ObjectAnimator對(duì)象應(yīng)該是我們產(chǎn)生屬性動(dòng)畫(huà)效果最常用的類了输玷,ValueAnimator僅僅是對(duì)數(shù)值進(jìn)行平滑的過(guò)渡變化队丝,而如果我們想對(duì)一個(gè)對(duì)象的任意屬性進(jìn)行動(dòng)畫(huà)效果,就需要使用ObjectAnimator對(duì)象了欲鹏,還是先舉個(gè)簡(jiǎn)單栗子吧机久。
1.效果圖
2.具體代碼
//ofFloat(需要實(shí)現(xiàn)動(dòng)畫(huà)的控件ID,需要實(shí)現(xiàn)的動(dòng)畫(huà)效果名稱,變化的區(qū)間范圍)
ObjectAnimator objAnim = bjectAnimator.ofFloat(et_Animator,"alpha",0f, 1f, 0f, 1f);
objAnim.setDuration(6000);
objAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float currentValue = (float) animation.getAnimatedValue();
StringBuilder sb = new StringBuilder();
et_Animator.setText(sb.append(currentValue).toString());
}
});
objAnim.start();
以上代碼實(shí)現(xiàn)的效果是:在6000毫秒內(nèi)貌虾,讓EditText控件平滑的實(shí)現(xiàn)了從完全透明>完全不透明>完全透明>完全不透明的過(guò)渡效果吞加;并且對(duì)整個(gè)動(dòng)畫(huà)執(zhí)行過(guò)程設(shè)置了監(jiān)聽(tīng),時(shí)時(shí)把當(dāng)前動(dòng)畫(huà)的AnimatorValue值顯示到EditText控件上面尽狠。
根據(jù)設(shè)置參數(shù)的不同衔憨,使用ObjectAnimator對(duì)象還可以實(shí)現(xiàn)translationX(豎直平移),translationY(水平平移)袄膏,rotation(旋轉(zhuǎn))践图,scaleX(水平縮放),scaleY(豎直縮放)等其他效果沉馆。當(dāng)然還可以使用AnimatorSet對(duì)象來(lái)產(chǎn)生組合動(dòng)畫(huà)效果码党。
- 效果圖
- 具體代碼
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private Button btn_OperationAnimator;
private EditText et_Animator;
private RadioGroup radioGroup;
private ObjectAnimator objAnim;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
et_Animator = (EditText) findViewById(R.id.et_Animator);
radioGroup = (RadioGroup) findViewById(R.id.radioGroup);
btn_OperationAnimator = (Button) findViewById(R.id.btn_OperationAnimator);
btn_OperationAnimator.setOnClickListener(this);
}
@Override
public void onClick(View v) {
int checkedId = radioGroup.getCheckedRadioButtonId();
switch (checkedId) {
case R.id.rb_value:
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0f, 1f);
valueAnimator.setDuration(3000);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float currentValue = (float) animation.getAnimatedValue();
StringBuilder sb = new StringBuilder();
et_Animator.setText(sb.append(currentValue).toString());
}
});
valueAnimator.start();
break;
case R.id.rb_alpha:
objAnim = ObjectAnimator.ofFloat(et_Animator,"alpha",0f, 1f, 0f, 1f);
objAnim.setDuration(3000);
objAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float currentValue = (float) animation.getAnimatedValue();
StringBuilder sb = new StringBuilder();
et_Animator.setText(sb.append(currentValue).toString());
}
});
objAnim.start();
break;
case R.id.rb_translationX:
objAnim = ObjectAnimator.ofFloat(et_Animator,"translationX",0f, 100f, 0f, 200f, 0f);
objAnim.setDuration(3000);
objAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float currentValue = (float) animation.getAnimatedValue();
StringBuilder sb = new StringBuilder();
et_Animator.setText(sb.append(currentValue).toString());
}
});
objAnim.start();
break;
case R.id.rb_translationY:
objAnim = ObjectAnimator.ofFloat(et_Animator,"translationY",0f, 100f, 0f, 200f, 0f);
objAnim.setDuration(3000);
objAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float currentValue = (float) animation.getAnimatedValue();
StringBuilder sb = new StringBuilder();
et_Animator.setText(sb.append(currentValue).toString());
}
});
objAnim.start();
break;
case R.id.rb_rotation:
objAnim = ObjectAnimator.ofFloat(et_Animator,"rotation",0f, 180f, 0f, 360f, 0f);
objAnim.setDuration(6000);
objAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float currentValue = (float) animation.getAnimatedValue();
StringBuilder sb = new StringBuilder();
et_Animator.setText(sb.append(currentValue).toString());
}
});
objAnim.start();
break;
case R.id.rb_scaleX:
objAnim = ObjectAnimator.ofFloat(et_Animator,"scaleX", 4f, 1f, 2f, 1f);
objAnim.setDuration(3000);
objAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float currentValue = (float) animation.getAnimatedValue();
StringBuilder sb = new StringBuilder();
et_Animator.setText(sb.append(currentValue).toString());
}
});
objAnim.start();
break;
case R.id.rb_scaleY:
objAnim = ObjectAnimator.ofFloat(et_Animator,"scaleY",1f, 5f, 2f, 1f);
objAnim.setDuration(3000);
objAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float currentValue = (float) animation.getAnimatedValue();
StringBuilder sb = new StringBuilder();
et_Animator.setText(sb.append(currentValue).toString());
}
});
objAnim.start();
break;
case R.id.rb_animatorSet:
ObjectAnimator objAnim_alpha = ObjectAnimator.ofFloat(et_Animator,"alpha",0f, 1f, 0f, 1f);
ObjectAnimator objAnim_translationX = ObjectAnimator.ofFloat(et_Animator,"translationX",0f, 100f, 0f, 200f, 0f);
ObjectAnimator objAnim_translationY = ObjectAnimator.ofFloat(et_Animator,"translationY",0f, 100f, 0f, 200f, 0f);
ObjectAnimator objAnim_rotation = ObjectAnimator.ofFloat(et_Animator,"rotation",0f, 180f, 0f, 360f, 0f);
ObjectAnimator objAnim_scaleX = ObjectAnimator.ofFloat(et_Animator,"scaleX", 4f, 1f, 2f, 1f);
ObjectAnimator objAnim_scaleY = ObjectAnimator.ofFloat(et_Animator,"scaleY",1f, 5f, 2f, 1f);
AnimatorSet animSet = new AnimatorSet();
animSet.play(objAnim_alpha)
.before(objAnim_rotation) //在play動(dòng)畫(huà)之后執(zhí)行
.after(objAnim_scaleX) //在play動(dòng)畫(huà)之前執(zhí)行
.after(objAnim_scaleY)
.with(objAnim_translationX) //和play動(dòng)畫(huà)同時(shí)執(zhí)行
.with(objAnim_translationY);
animSet.setDuration(5000);
animSet.start();
break;
}
}
}
栗子很簡(jiǎn)單,算是做個(gè)自我鞏固以及方便日后回顧斥黑,如果不小心幫到別人了那自然也是極好的揖盘。最后附上Demo下載。