Android動(dòng)畫合集之屬性動(dòng)畫-初見(jiàn)
本節(jié)引言:
本節(jié)給帶來(lái)的是Android動(dòng)畫中的第三種動(dòng)畫——屬性動(dòng)畫(Property Animation), 記得在上一節(jié)8.4.2 Android動(dòng)畫合集之補(bǔ)間動(dòng)畫為Fragment 設(shè)置過(guò)渡動(dòng)畫的時(shí)候,說(shuō)過(guò),App包和V4包下的Fragment調(diào)用setCustomAnimations()對(duì)應(yīng)的 動(dòng)畫類型是不一樣的,v4包下的是Animation,而app包下的是Animator;
Animation一般動(dòng)畫就是我們前面學(xué)的幀動(dòng)畫和補(bǔ)間動(dòng)畫!Animator則是本節(jié)要講的屬性動(dòng)畫!
關(guān)于屬性動(dòng)畫,大牛郭大叔已經(jīng)寫了三篇非常好的總結(jié)文,寫得非常贊,就沒(méi)必要重復(fù)造輪子了, 不過(guò)這里還是過(guò)一遍,大部分內(nèi)容參考的下面三篇文章:
Android屬性動(dòng)畫完全解析(上),初識(shí)屬性動(dòng)畫的基本用法
Android屬性動(dòng)畫完全解析(中),ValueAnimator和ObjectAnimator的高級(jí)用法
Android屬性動(dòng)畫完全解析(下),Interpolator和ViewPropertyAnimator的用法
寫的非常好,或者說(shuō)你可以直接跳過(guò)本文去看上面的三篇文章~
當(dāng)然,你愿意看我叨叨逼的話,也很歡迎,好了,開(kāi)始本節(jié)內(nèi)容吧~
1.屬性動(dòng)畫概念叨叨逼
不BB,直接上圖,就是這么暴力~
2.ValueAnimator簡(jiǎn)單使用
使用流程:
- 1.調(diào)用ValueAnimator的ofInt(),ofFloat()或ofObject()靜態(tài)方法創(chuàng)建ValueAnimator實(shí)例
- 2.調(diào)用實(shí)例的setXxx方法設(shè)置動(dòng)畫持續(xù)時(shí)間,插值方式,重復(fù)次數(shù)等
- 3.調(diào)用實(shí)例的addUpdateListener添加AnimatorUpdateListener監(jiān)聽(tīng)器,在該監(jiān)聽(tīng)器中 可以獲得ValueAnimator計(jì)算出來(lái)的值,你可以值應(yīng)用到指定對(duì)象上~
- 4.調(diào)用實(shí)例的start()方法開(kāi)啟動(dòng)畫! 另外我們可以看到ofInt和ofFloat都有個(gè)這樣的參數(shù):float/int... values代表可以多個(gè)值!
使用示例:
代碼實(shí)現(xiàn):
布局文件:activity_main.xml,非常簡(jiǎn)單,四個(gè)按鈕,一個(gè)ImageView
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/ly_root" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <Button android:id="@+id/btn_one" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="動(dòng)畫1" /> <Button android:id="@+id/btn_two" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="動(dòng)畫2" /> <Button android:id="@+id/btn_three" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="動(dòng)畫3" /> <Button android:id="@+id/btn_four" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="動(dòng)畫4" /> <ImageView android:id="@+id/img_babi" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:background="@mipmap/img_babi" /></LinearLayout>
接著到MainActivity.java, 首先需要一個(gè)修改View位置的方法,這里調(diào)用moveView()設(shè)置左邊和上邊的起始坐標(biāo)以及寬高!
接著定義了四個(gè)動(dòng)畫,分別是:直線移動(dòng),縮放,旋轉(zhuǎn)加透明,以及圓形旋轉(zhuǎn)!
然后通過(guò)按鈕觸發(fā)對(duì)應(yīng)的動(dòng)畫~
public class MainActivity extends AppCompatActivity implements View.OnClickListener { private Button btn_one; private Button btn_two; private Button btn_three; private Button btn_four; private LinearLayout ly_root; private ImageView img_babi; private int width; private int height; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); bindViews(); } private void bindViews() { ly_root = (LinearLayout) findViewById(R.id.ly_root); btn_one = (Button) findViewById(R.id.btn_one); btn_two = (Button) findViewById(R.id.btn_two); btn_three = (Button) findViewById(R.id.btn_three); btn_four = (Button) findViewById(R.id.btn_four); img_babi = (ImageView) findViewById(R.id.img_babi); btn_one.setOnClickListener(this); btn_two.setOnClickListener(this); btn_three.setOnClickListener(this); btn_four.setOnClickListener(this); img_babi.setOnClickListener(this); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.btn_one: lineAnimator(); break; case R.id.btn_two: scaleAnimator(); break; case R.id.btn_three: raAnimator(); break; case R.id.btn_four: circleAnimator(); break; case R.id.img_babi: Toast.makeText(MainActivity.this, "不愧是coder-pig~", Toast.LENGTH_SHORT).show(); break; } } //定義一個(gè)修改ImageView位置的方法 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); } //定義屬性動(dòng)畫的方法: //按軌跡方程來(lái)運(yùn)動(dòng) 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 scaleAnimator(){ //這里故意用兩個(gè)是想讓大家體會(huì)下組合動(dòng)畫怎么用而已~ 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(); //其實(shí)可以一個(gè)就搞定的 // ValueAnimator vValue = ValueAnimator.ofFloat(1.0f, 0.6f, 1.2f, 1.0f, 0.6f, 1.2f, 1.0f); // vValue.setDuration(1000L); // vValue.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { // @Override // public void onAnimationUpdate(ValueAnimator animation) { // float scale = (Float) animation.getAnimatedValue(); // img_babi.setScaleX(scale); // img_babi.setScaleY(scale); // } // }); // vValue.setInterpolator(new LinearInterpolator()); // vValue.start(); } //旋轉(zhuǎn)的同時(shí)透明度變化 private void raAnimator(){ ValueAnimator rValue = ValueAnimator.ofInt(0, 360); rValue.setDuration(1000L); rValue.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { int rotateValue = (Integer) animation.getAnimatedValue(); img_babi.setRotation(rotateValue); float fractionValue = animation.getAnimatedFraction(); img_babi.setAlpha(fractionValue); } }); rValue.setInterpolator(new DecelerateInterpolator()); rValue.start(); } //圓形旋轉(zhuǎn) protected void circleAnimator() { width = ly_root.getWidth(); height = ly_root.getHeight(); final int R = width / 4; ValueAnimator tValue = ValueAnimator.ofFloat(0, (float) (2.0f * Math.PI)); tValue.setDuration(1000); tValue.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { // 圓的參數(shù)方程 x = R * sin(t) y = R * cos(t) float t = (Float) animation.getAnimatedValue(); int x = (int) (R * Math.sin(t) + width / 2); int y = (int) (R * Math.cos(t) + height / 2); moveView(img_babi, x, y); } }); tValue.setInterpolator(new DecelerateInterpolator()); tValue.start(); } }
好的,使用的流程非常簡(jiǎn)單,先創(chuàng)建ValueAnimator對(duì)象,調(diào)用ValueAnimator.ofInt/ofFloat 獲得,然后設(shè)置動(dòng)畫持續(xù)時(shí)間,addUpdateListener添加AnimatorUpdateListener事件監(jiān)聽(tīng), 然后使用參數(shù)animation的getAnimatedValue()獲得當(dāng)前的值,然后我們可以拿著這個(gè)值 來(lái)修改View的一些屬性,從而形成所謂的動(dòng)畫效果,接著設(shè)置setInterpolator動(dòng)畫渲染模式, 最后調(diào)用start()開(kāi)始動(dòng)畫的播放~
臥槽,直線方程,圓的參數(shù)方程,我都開(kāi)始方了,這不是高數(shù)的東西么,
掛科學(xué)渣連三角函數(shù)都忘了...
例子參考自github:MoveViewValueAnimator
3.ObjectAnimator簡(jiǎn)單使用
比起ValueAnimator,ObjectAnimator顯得更為易用,通過(guò)該類我們可以直接對(duì)任意對(duì)象的任意屬性進(jìn)行動(dòng)畫操作!沒(méi)錯(cuò),是任意對(duì)象,而不單單只是View對(duì)象,
不斷地對(duì)對(duì)象中的某個(gè)屬性值進(jìn)行賦值,然后根據(jù)對(duì)象屬性值的改變?cè)賮?lái)決定如何展現(xiàn)
出來(lái)!比如為TextView設(shè)置如下動(dòng)畫:ObjectAnimator.ofFloat(textview, "alpha", 1f, 0f);
這里就是不斷改變alpha的值,從1f - 0f,然后對(duì)象根據(jù)屬性值的變化來(lái)刷新界面顯示,從而
展現(xiàn)出淡入淡出的效果,而在TextView類中并沒(méi)有alpha這個(gè)屬性,ObjectAnimator內(nèi)部機(jī)制是:尋找傳輸?shù)膶傩悦麑?duì)應(yīng)的get和set方法~,而非找這個(gè)屬性值!不信的話你可以到TextView的源碼里找找是否有alpha這個(gè)屬性!
好的,下面我們利用ObjectAnimator來(lái)實(shí)現(xiàn)四種補(bǔ)間動(dòng)畫的效果吧~
運(yùn)行效果圖:
代碼實(shí)現(xiàn):
布局直接用的上面那個(gè)布局,加了個(gè)按鈕,把ImageView換成了TextView,這里就不貼代碼了, 直接上MainActivity.java部分的代碼,其實(shí)都是大同小異的~
public class MainActivity extends AppCompatActivity implements View.OnClickListener { private Button btn_one; private Button btn_two; private Button btn_three; private Button btn_four; private Button btn_five; private LinearLayout ly_root; private TextView tv_pig; private int height; private ObjectAnimator animator1; private ObjectAnimator animator2; private ObjectAnimator animator3; private ObjectAnimator animator4; private AnimatorSet animSet; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); bindViews(); initAnimator(); } private void bindViews() { ly_root = (LinearLayout) findViewById(R.id.ly_root); btn_one = (Button) findViewById(R.id.btn_one); btn_two = (Button) findViewById(R.id.btn_two); btn_three = (Button) findViewById(R.id.btn_three); btn_four = (Button) findViewById(R.id.btn_four); btn_five = (Button) findViewById(R.id.btn_five); tv_pig = (TextView) findViewById(R.id.tv_pig); height = ly_root.getHeight(); btn_one.setOnClickListener(this); btn_two.setOnClickListener(this); btn_three.setOnClickListener(this); btn_four.setOnClickListener(this); btn_five.setOnClickListener(this); tv_pig.setOnClickListener(this); } //初始化動(dòng)畫 private void initAnimator() { animator1 = ObjectAnimator.ofFloat(tv_pig, "alpha", 1f, 0f, 1f, 0f, 1f); animator2 = ObjectAnimator.ofFloat(tv_pig, "rotation", 0f, 360f, 0f); animator3 = ObjectAnimator.ofFloat(tv_pig, "scaleX", 2f, 4f, 1f, 0.5f, 1f); animator4 = ObjectAnimator.ofFloat(tv_pig, "translationY", height / 8, -100, height / 2); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.btn_one: animator1.setDuration(3000l); animator1.start(); break; case R.id.btn_two: animator2.setDuration(3000l); animator2.start(); break; case R.id.btn_three: animator3.setDuration(3000l); animator3.start(); break; case R.id.btn_four: animator4.setDuration(3000l); animator4.start(); break; case R.id.btn_five: //將前面的動(dòng)畫集合到一起~ animSet = new AnimatorSet(); animSet.play(animator4).with(animator3).with(animator2).after(animator1); animSet.setDuration(5000l); animSet.start(); break; case R.id.tv_pig: Toast.makeText(MainActivity.this, "不愧是coder-pig~", Toast.LENGTH_SHORT).show(); break; } } }
用法也非常簡(jiǎn)單,上面涉及到的組合動(dòng)畫我們下面講~
4.組合動(dòng)畫與AnimatorListener
從上面兩個(gè)例子中我們都體驗(yàn)了一把組合動(dòng)畫,用到了AnimatorSet這個(gè)類!
我們調(diào)用的play()方法,然后傳入第一個(gè)開(kāi)始執(zhí)行的動(dòng)畫,此時(shí)他會(huì)返回一個(gè)Builder類給我們:
接下來(lái)我們可以調(diào)用Builder給我們提供的四個(gè)方法,來(lái)組合其他的動(dòng)畫:
- after(Animator anim) 將現(xiàn)有動(dòng)畫插入到傳入的動(dòng)畫之后執(zhí)行
- after(long delay) 將現(xiàn)有動(dòng)畫延遲指定毫秒后執(zhí)行
- before(Animator anim) 將現(xiàn)有動(dòng)畫插入到傳入的動(dòng)畫之前執(zhí)行
- with(Animator anim) 將現(xiàn)有動(dòng)畫和傳入的動(dòng)畫同時(shí)執(zhí)行
嗯,很簡(jiǎn)單,接下來(lái)要說(shuō)下動(dòng)畫事件的監(jiān)聽(tīng),上面我們ValueAnimator的監(jiān)聽(tīng)器是AnimatorUpdateListener,當(dāng)值狀態(tài)發(fā)生改變時(shí)候會(huì)回調(diào)onAnimationUpdate方法!
除了這種事件外還有:動(dòng)畫進(jìn)行狀態(tài)的監(jiān)聽(tīng)~ AnimatorListener,我們可以調(diào)用addListener方法 添加監(jiān)聽(tīng)器,然后重寫下面四個(gè)回調(diào)方法:
- onAnimationStart():動(dòng)畫開(kāi)始
- onAnimationRepeat():動(dòng)畫重復(fù)執(zhí)行
- onAnimationEnd():動(dòng)畫結(jié)束
- onAnimationCancel():動(dòng)畫取消
沒(méi)錯(cuò),加入你真的用AnimatorListener的話,四個(gè)方法你都要重寫,當(dāng)然和前面的手勢(shì)那一節(jié)一樣, Android已經(jīng)給我們提供好一個(gè)適配器類:AnimatorListenerAdapter,該類中已經(jīng)把每個(gè)接口 方法都實(shí)現(xiàn)好了,所以我們這里只寫一個(gè)回調(diào)方法也可以額!
5.使用XML來(lái)編寫動(dòng)畫
使用XML來(lái)編寫動(dòng)畫,畫的時(shí)間可能比Java代碼長(zhǎng)一點(diǎn),但是重用起來(lái)就輕松很多! 對(duì)應(yīng)的XML標(biāo)簽分別為:<animator><objectAnimator><set> 相關(guān)的屬性解釋如下:
- android:ordering:指定動(dòng)畫的播放順序:sequentially(順序執(zhí)行),together(同時(shí)執(zhí)行)
- android:duration:動(dòng)畫的持續(xù)時(shí)間
- android:propertyName="x":這里的x,還記得上面的"alpha"嗎?加載動(dòng)畫的那個(gè)對(duì)象里需要 定義getx和setx的方法,objectAnimator就是通過(guò)這里來(lái)修改對(duì)象里的值的!
- android:valueFrom="1" :動(dòng)畫起始的初始值
- android:valueTo="0" :動(dòng)畫結(jié)束的最終值
- android:valueType="floatType":變化值的數(shù)據(jù)類型
使用例子如下:
①從0到100平滑過(guò)渡的動(dòng)畫:
<animator xmlns:android="http://schemas.android.com/apk/res/android" android:valueFrom="0" android:valueTo="100" android:valueType="intType"/>
②將一個(gè)視圖的alpha屬性從1變成0:
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android" android:valueFrom="1" android:valueTo="0" android:valueType="floatType" android:propertyName="alpha"/>
③set動(dòng)畫使用演示:
<set android:ordering="sequentially" > <set> <objectAnimator android:duration="500" android:propertyName="x" android:valueTo="400" android:valueType="intType" /> <objectAnimator android:duration="500" android:propertyName="y" android:valueTo="300" android:valueType="intType" /> </set> <objectAnimator android:duration="500" android:propertyName="alpha" android:valueTo="1f" /></set>
加載我們的動(dòng)畫文件:
AnimatorSet set = (AnimatorSet)AnimatorInflater.loadAnimator(mContext, R.animator.property_animator); animator.setTarget(view); animator.start();
6.本節(jié)示例代碼下載:
AnimatorDemo1.zip
AnimatorDemo2.zip
本節(jié)小結(jié):
好的,本節(jié)給大家捋了一捋安卓中屬性動(dòng)畫的基本用法,不知道你get了沒(méi),內(nèi)容還是比較簡(jiǎn)單 的,而且例子比較有趣,相信大家會(huì)喜歡,嗯,就說(shuō)這么多,謝謝~
感謝郭神的文章~