[TOC]
屬性動(dòng)畫(huà)我決定用兩篇文章做總結(jié)
一暂幼、屬性動(dòng)畫(huà)基礎(chǔ)內(nèi)容
二搅轿、ValueAnimator
從名字就可以看出琴昆,這是針對(duì)值的動(dòng)畫(huà),它并不會(huì)對(duì)View做出任何動(dòng)畫(huà)效果嚎京。使用ValueAnimator可以讓某一個(gè)值在設(shè)定時(shí)間內(nèi)平滑過(guò)渡成另一個(gè)值嗡贺,根據(jù)值的變換過(guò)程,自己操作View的變換鞍帝。
2.1诫睬、使用ValueAnimator
要使用ValueAnimator非常簡(jiǎn)單,首先通過(guò)ValueAnimator提供的靜態(tài)方法創(chuàng)建其對(duì)象帕涌,然后設(shè)置動(dòng)畫(huà)的時(shí)長(zhǎng)摄凡,最后調(diào)用start()方法開(kāi)啟動(dòng)畫(huà)即可。下面我們來(lái)創(chuàng)建一個(gè)ValueAnimator蚓曼,它能在2秒內(nèi)從0變換到500:
ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 500);
valueAnimator.setDuration(2000);
valueAnimator.start();
沒(méi)錯(cuò)就是這么簡(jiǎn)單亲澡,運(yùn)行后的確在兩秒內(nèi)值從0一直平滑過(guò)渡到了500∪野妫可是我們需要過(guò)程而不是結(jié)果呀谷扣,如果沒(méi)辦法監(jiān)聽(tīng)到值變換的過(guò)程,就沒(méi)辦法利用變換中的值給View設(shè)置動(dòng)畫(huà)了。要想監(jiān)聽(tīng)值變化的過(guò)程会涎,我們可以使用addUpdateListener()
方法給valueAnimator加上監(jiān)聽(tīng)事件:
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int value = (int) animation.getAnimatedValue();
Log.i("HurryYu", "value:" + value);
}
});
在回調(diào)中裹匙,animation表示當(dāng)前ValueAnimator對(duì)象,使用animation.getAnimatedValue()
就可以獲取到當(dāng)前變換的值(默認(rèn)是返回Object類型末秃,由于使用的是ofInt()創(chuàng)建的ValueAnimator對(duì)象砚著,因此可以直接強(qiáng)轉(zhuǎn)為int類型)夸政。此時(shí)觀察Logcat留搔,得到如下輸出:
2019-05-11 15:19:36.858 14906-14906/com.hurryyu.viewdemo I/HurryYu: value:0
2019-05-11 15:19:36.960 14906-14906/com.hurryyu.viewdemo I/HurryYu: value:0
2019-05-11 15:19:37.253 14906-14906/com.hurryyu.viewdemo I/HurryYu: value:26
2019-05-11 15:19:37.626 14906-14906/com.hurryyu.viewdemo I/HurryYu: value:121
2019-05-11 15:19:37.654 14906-14906/com.hurryyu.viewdemo I/HurryYu: value:134
2019-05-11 15:19:37.669 14906-14906/com.hurryyu.viewdemo I/HurryYu: value:140
2019-05-11 15:19:37.695 14906-14906/com.hurryyu.viewdemo I/HurryYu: value:147
2019-05-11 15:19:37.705 14906-14906/com.hurryyu.viewdemo I/HurryYu: value:153
.
.
.
2019-05-11 15:19:38.885 14906-14906/com.hurryyu.viewdemo I/HurryYu: value:498
2019-05-11 15:19:38.903 14906-14906/com.hurryyu.viewdemo I/HurryYu: value:499
2019-05-11 15:19:38.941 14906-14906/com.hurryyu.viewdemo I/HurryYu: value:499
2019-05-11 15:19:38.958 14906-14906/com.hurryyu.viewdemo I/HurryYu: value:500
現(xiàn)在明白ValueAnimator了吧译红,實(shí)際上就是對(duì)給定區(qū)間的值進(jìn)行平滑變換,我們需要自己監(jiān)聽(tīng)值的變換過(guò)程铃将,然后自己對(duì)View執(zhí)行相應(yīng)的動(dòng)畫(huà)效果项鬼。就拿上面的這個(gè)從0平滑過(guò)渡到500的ValueAnimator做實(shí)驗(yàn),現(xiàn)在我們準(zhǔn)備利用它讓View在X軸方向上向右平移劲阎,首先我們完成布局:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<View
android:id="@+id/view"
android:layout_width="50dp"
android:layout_height="50dp"
android:background="@color/colorPrimary" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="HurryYu" />
</LinearLayout>
效果如下圖:
下面完成代碼部分:
public class MainActivity extends AppCompatActivity {
private View view;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
view = findViewById(R.id.view);
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "看看有效果嗎?",
Toast.LENGTH_SHORT).show();
}
});
startAnim();
}
private void startAnim() {
ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 500);
valueAnimator.setDuration(2000);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int value = (int) animation.getAnimatedValue();
view.setTranslationX(value);
}
});
valueAnimator.start();
}
}
請(qǐng)注意看ValueAnimator的回調(diào)中绘盟,我們使用了view.setTranslationX(value)
來(lái)改變view在X軸上的偏移量,其中value會(huì)在兩秒內(nèi)從0變換到500悯仙。最終動(dòng)畫(huà)完成后的效果如圖:
點(diǎn)擊動(dòng)畫(huà)完成后的View龄毡,仍然可以響應(yīng)點(diǎn)擊事件。不過(guò)奇怪的是锡垄,父布局是水平線性布局沦零,View向右移動(dòng)后,TextView應(yīng)該也會(huì)跟著向右移動(dòng)呀货岭,可是為什么沒(méi)有呢路操?因?yàn)槲覀兪褂玫氖?code>setTranslationX()去改變view的偏移量,它的確能改變view的位置千贯,但它并不會(huì)改變view的LayoutParams中的margin屬性值寻拂,即不會(huì)影響getLeft()與getRight()。
2.2丈牢、ValueAnimator的常用方法
2.1.1、其它靜態(tài)工廠方法
之前我們已經(jīng)使用過(guò)ValueAnimator.ofInt()方法創(chuàng)建了對(duì)象瞄沙,除了ofInt()以外己沛,它還提供了如下靜態(tài)工程方法:
①、來(lái)看ofArgb距境,ofInt是對(duì)int類型的數(shù)字進(jìn)行變換申尼,而它的作用是可以對(duì)顏色進(jìn)行過(guò)度變換,我們還注意到垫桂,這些方法接收的都是可變參數(shù)师幕,意味可以傳入任意個(gè)參數(shù),它將依次變換。現(xiàn)在我想把按鈕的顏色從紅色過(guò)度到綠色再過(guò)度到藍(lán)色霹粥,首先創(chuàng)建ValueAnimator:
private void changeColor() {
ValueAnimator valueAnimator = ValueAnimator.ofArgb(0xFFFF5454, 0xFF5DDE5D, 0xFF5DBEDE);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int color = (int) animation.getAnimatedValue();
button.setBackgroundColor(color);
}
});
valueAnimator.setDuration(2000);
valueAnimator.start();
}
這里注意ofArgb需要接收ARGB的顏色灭将。效果如下所示:
②、ofFloat就不必多說(shuō)了吧后控。
③庙曙、ofObject這個(gè)我們后面再來(lái)研究,因?yàn)樗枰覀兲峁┳远x的Evaluator浩淘。
2.1.2捌朴、常用對(duì)象方法
前面我們已經(jīng)接觸過(guò)了幾個(gè)ValueAnimator提供的方法,比如setDuration用來(lái)設(shè)置動(dòng)畫(huà)時(shí)長(zhǎng)张抄,getAnimatedValue用來(lái)獲取當(dāng)前變換中的值砂蔽,start用來(lái)開(kāi)始動(dòng)畫(huà)。下面我們?cè)賮?lái)學(xué)習(xí)幾個(gè)常用的方法:
①署惯、setRepeatCount用于設(shè)置動(dòng)畫(huà)的重復(fù)次數(shù)左驾,傳入0表示不重復(fù),傳入ValueAnimator.INFINITE表示無(wú)限重復(fù)泽台,默認(rèn)是不重復(fù)什荣,這個(gè)方法比較好理解。
②怀酷、setRepeatMode用于設(shè)置動(dòng)畫(huà)重復(fù)的模式稻爬,有兩種選擇,一種是正序重復(fù)(ValueAnimator.RESTART)蜕依,另一種是倒序重復(fù)(ValueAnimator.REVERSE)桅锄,默認(rèn)是正序重復(fù)。這兩種有什么區(qū)別呢样眠?拿按鈕變顏色的例子來(lái)說(shuō)友瘤,如果我們把重復(fù)次數(shù)設(shè)定為無(wú)限重復(fù),那么每一次重復(fù)時(shí)檐束,都會(huì)從最后的藍(lán)色突然變成開(kāi)始的紅色然后繼續(xù)新一輪動(dòng)畫(huà)辫秧,這就是正序:
而如果將RepeatMode設(shè)置為ValueAnimator.REVERSE,將使用倒序的方式進(jìn)行動(dòng)畫(huà)的重復(fù)被丧,即:
第一輪:紅-綠-藍(lán)
第二輪:藍(lán)-綠-紅
第三輪:紅-綠-藍(lán)
以此類推盟戏,這樣就不存在突然從第三種顏色變?yōu)榈谝环N顏色的情況:
③、cancel用于取消當(dāng)前動(dòng)畫(huà)甥桂。
2.1.3柿究、常用監(jiān)聽(tīng)方法
我們已經(jīng)使用過(guò)一個(gè)監(jiān)聽(tīng)(addUpdateListener),它用于監(jiān)聽(tīng)動(dòng)畫(huà)執(zhí)行過(guò)程中值的變化黄选。ValueAnimator除了能監(jiān)聽(tīng)值的變化以外蝇摸,還能監(jiān)聽(tīng)動(dòng)畫(huà)執(zhí)行過(guò)程中的狀態(tài),可以使用addListener進(jìn)行添加:
valueAnimator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
onAnimationStart:顯然是在開(kāi)始執(zhí)行動(dòng)畫(huà)的時(shí)候調(diào)用。
onAnimationRepeat:在每次重復(fù)執(zhí)行時(shí)調(diào)用貌夕。
onAnimationCancel:在取消動(dòng)畫(huà)的時(shí)候調(diào)用律歼。
onAnimationEnd:在動(dòng)畫(huà)結(jié)束的時(shí)候調(diào)用,注意蜂嗽,無(wú)論是正常結(jié)束還是調(diào)用cancel結(jié)束苗膝,此方法都會(huì)被回調(diào)。
如果我們只想監(jiān)聽(tīng)這四種動(dòng)畫(huà)狀態(tài)中的其中一個(gè)或是少數(shù)幾個(gè)植旧,實(shí)現(xiàn)Animator.AnimatorListener這個(gè)接口的匿名內(nèi)部類代價(jià)就太大了辱揭,因此我們可以使用AnimatorListenerAdapter這個(gè)抽象類,實(shí)際上它對(duì)Animator.AnimatorListener里面的方法都做了空實(shí)現(xiàn)病附,我們需要用到哪個(gè)方法就去重寫(xiě)哪個(gè)方法就行了:
valueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
}
});
順便提一句问窃,為何對(duì)Animator.AnimatorListener里的方法都提供了空實(shí)現(xiàn),還要把類聲明成abstract的呢完沪?這是因?yàn)閍bstract類不能被直接實(shí)例化域庇,這樣就能逼迫調(diào)用者至少去重寫(xiě)里面的一個(gè)方法。
2.3覆积、Interpolator插值器
插值器的作用是控制動(dòng)畫(huà)在執(zhí)行過(guò)程中的速度听皿,比如有一個(gè)valueAnimator,它能使一個(gè)值在兩秒內(nèi)從0過(guò)渡到200宽档,那過(guò)渡中的速度到底是怎么樣的呢尉姨?先快后慢?勻速吗冤?先慢后快又厉?控制值在變化中的速度,就是靠Interpolator來(lái)完成的椎瘟。
系統(tǒng)已經(jīng)為我們提供了非常多的插值器供我們選擇覆致,這里選擇最為簡(jiǎn)單的一個(gè)插值器(LinearInterpolator)來(lái)研究下Interpolator:
public class LinearInterpolator implements Interpolator {
public LinearInterpolator() {
}
@override
public float getInterpolation(float input) {
return input;
}
}
我對(duì)LinearInterpolator類的代碼稍作刪減,便于查看肺蔚。首先它實(shí)現(xiàn)了Interpolator接口煌妈,只要實(shí)現(xiàn)了Interpolator(TimeInterpolator)接口的類就可以是一個(gè)插值器。我們來(lái)看看Interpolator接口的代碼:
public interface Interpolator extends TimeInterpolator {
}
這個(gè)接口只是繼承了TimeInterpolator接口宣羊,除此之外什么都沒(méi)做璧诵。來(lái)看最終的TimeInterpolator接口:
public interface TimeInterpolator {
float getInterpolation(float input);
}
因此在插值器中只需要實(shí)現(xiàn)getInterpolation()方法就可以了。重點(diǎn)也就是在這個(gè)方法上段只,下面我將詳細(xì)說(shuō)明一下這個(gè)方法的作用:它接收一個(gè)float類型的參數(shù),這個(gè)參數(shù)的意思是表示當(dāng)前動(dòng)畫(huà)執(zhí)行到的進(jìn)度鉴扫,取值范圍是0~1赞枕,在動(dòng)畫(huà)剛剛開(kāi)始時(shí),值為0,在動(dòng)畫(huà)完成時(shí)炕婶,值為1姐赡。這個(gè)進(jìn)度值時(shí)系統(tǒng)幫我們計(jì)算出的,它永遠(yuǎn)是勻速增加的柠掂,不受任何設(shè)置的影響项滑。比如一秒內(nèi)值從1變換成10,在0.5秒時(shí)涯贞,值應(yīng)該為5枪狂,在0.8秒時(shí),值應(yīng)該為8宋渔,這就是勻速變化州疾。我們可以參考系統(tǒng)計(jì)算出的當(dāng)前動(dòng)畫(huà)的進(jìn)度值,計(jì)算并返回另一個(gè)進(jìn)度值皇拣,這個(gè)進(jìn)度值將會(huì)影響到AnimatorUpdateListener中數(shù)值的取值严蓖,最終達(dá)到控制數(shù)值變化速度的作用。LinearInterpolator是一個(gè)線性的插值器氧急,因此它能使動(dòng)畫(huà)在執(zhí)行過(guò)程中始終保持勻速執(zhí)行颗胡,因此它在getInterpolation方法中直接返回了參數(shù)input。我們可以使用setInterpolator()方法給動(dòng)畫(huà)設(shè)置插值器吩坝,在ValueAnimator中毒姨,如果沒(méi)有顯式設(shè)置插值器,會(huì)默認(rèn)使用AccelerateDecelerateInterpolator作為默認(rèn)插值器钾恢。AccelerateDecelerateInterpolator會(huì)使動(dòng)畫(huà)在開(kāi)始和結(jié)束時(shí)變化緩慢手素,在中間部分變化較快。除此之外還有
AccelerateDecelerateInterpolator, AccelerateInterpolator, AnticipateInterpolator, AnticipateOvershootInterpolator, BaseInterpolator, BounceInterpolator, CycleInterpolator, DecelerateInterpolator, LinearInterpolator, OvershootInterpolator, PathInterpolator
這些插值器瘩蚪,具體效果可查閱相關(guān)資料泉懦。
2.4、Evaluator
Evaluator的作用就是將插值器返回的進(jìn)度值轉(zhuǎn)換成對(duì)應(yīng)的數(shù)值疹瘦。在詳細(xì)介紹Evaluator前崩哩,我們先來(lái)梳理一下AnimatorUpdateListener中是怎么得到當(dāng)前變化的值?
通過(guò)上圖相信已經(jīng)能很清晰的區(qū)別出Interpolator與Evaluator的區(qū)別了言沐,前者是返回?cái)?shù)值區(qū)間變化的進(jìn)度邓嘹,范圍只能是0~1之間,而后者是返回當(dāng)前進(jìn)度對(duì)應(yīng)的具體值险胰,這個(gè)就跟我們使用哪種靜態(tài)工廠有關(guān)了汹押,如果我們使用ofInt,那么Evaluator計(jì)算后的返回值應(yīng)該是int類型起便;如果使用ofFloat棚贾,那么Evaluator計(jì)算后的返回值應(yīng)該是float類型窖维。所以說(shuō)Interpolator是可以通用的,而Evaluator是專用的妙痹。ValueAnimator中提供了設(shè)置Evaluator的方法:setEvaluator()铸史,下面我們以ofFloat為例,分析一下這個(gè)Evaluator:
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 200);
valueAnimator.setDuration(2000);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
}
});
valueAnimator.start();
我們知道怯伊,如果沒(méi)有顯式指定Interpolator琳轿,它將默認(rèn)使用AccelerateDecelerateInterpolator,但是Evaluator呢耿芹?在PropertyValuesHolder這個(gè)類中崭篡,找到了如下內(nèi)容:
private static final TypeEvaluator sIntEvaluator = new IntEvaluator();
private static final TypeEvaluator sFloatEvaluator = new FloatEvaluator();
void init() {
if (mEvaluator == null) {
mEvaluator = (mValueType == Integer.class) ? sIntEvaluator :
(mValueType == Float.class) ? sFloatEvaluator :null;
}
if (mEvaluator != null) {
mKeyframes.setEvaluator(mEvaluator);
}
}
如果是ofFloat,就會(huì)使用FloatEvaluator猩系,如果是ofInt媚送,就會(huì)使用IntEvaluator。如果之前設(shè)置過(guò)自定義Evaluator寇甸,就會(huì)使用自定義的Evaluator塘偎,如:
public static ValueAnimator ofArgb(int... values) {
ValueAnimator anim = new ValueAnimator();
anim.setIntValues(values);
anim.setEvaluator(ArgbEvaluator.getInstance());
return anim;
}
ofArgb()就在靜態(tài)工廠方法中給ValueAnimator設(shè)置了ArgbEvaluator。下面我將詳細(xì)分析FloatEvaluator:
public class FloatEvaluator implements TypeEvaluator<Number> {
public Float evaluate(float fraction, Number startValue, Number endValue) {
float startFloat = startValue.floatValue();
return startFloat + fraction * (endValue.floatValue() - startFloat);
}
}
可以發(fā)現(xiàn)拿霉,它實(shí)現(xiàn)了TypeEvaluator接口:
public interface TypeEvaluator<T> {
public T evaluate(float fraction, T startValue, T endValue);
}
下面對(duì)參數(shù)作出解釋:
- fraction:這個(gè)參數(shù)就是插值器返回的值吟秩,表示當(dāng)前動(dòng)畫(huà)對(duì)應(yīng)的值進(jìn)度(0~1)
- startValue:表示我們所設(shè)置的值區(qū)間的開(kāi)始值(例如ofFloat(0, 200),則startValue為0)
- endValue:表示我們所設(shè)置的值區(qū)間的結(jié)束值(例如ofFloat(0, 200)绽淘,則startValue為200)
- 返回值表示計(jì)算后的具體值涵防,也就是我們?cè)贏nimatorUpdateListener->onAnimationUpdate中通過(guò)
animation.getAnimatedValue()
所得到的值
現(xiàn)在我們回過(guò)頭來(lái)看FloatEvaluator中的evaluate()方法:
public Float evaluate(float fraction, Number startValue, Number endValue) {
float startFloat = startValue.floatValue();
return startFloat + fraction * (endValue.floatValue() - startFloat);
}
這就應(yīng)該很好理解了,為了計(jì)算出在某一進(jìn)度時(shí)對(duì)應(yīng)的值沪铭,采用了如下公式:
當(dāng)前值 = 開(kāi)始值 + 進(jìn)度值 * (結(jié)束值 - 開(kāi)始值)
2.5壮池、ofObject
學(xué)習(xí)完了Evaluator以后,我們?cè)趤?lái)看下ValueAnimator提供的靜態(tài)工廠方法:
其中大部分我們都已經(jīng)使用過(guò)了杀怠,但是還有一個(gè)ofObject似乎我們從未提起過(guò)椰憋,之所以之前沒(méi)有提起,是因?yàn)檫€沒(méi)有學(xué)習(xí)Evaluator赔退,而現(xiàn)在是時(shí)候了解一下它了橙依!
public static ValueAnimator ofObject(TypeEvaluator evaluator, Object... values)
這就是ofObject()方法的原型,可以看到它接收一個(gè)Evaluator和一個(gè)Object類型的可變參數(shù)硕旗。這就意味著我們可以處理任意類型的值窗骑。可是為什么還要傳入Evaluator呢漆枚?這是因?yàn)镋valuator的作用是根據(jù)進(jìn)度計(jì)算出當(dāng)前進(jìn)度所對(duì)應(yīng)的值创译,而現(xiàn)在這個(gè)Object是我們自己傳入的,系統(tǒng)并不知道如何去轉(zhuǎn)換墙基,因此必須要我們手動(dòng)提供一個(gè)自定義Evaluator软族。下面我將使用ofObject()方法來(lái)實(shí)現(xiàn)模擬小球下落(X坐標(biāo)與Y坐標(biāo)都會(huì)發(fā)生變化)辛藻,首先完成布局:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<View
android:id="@+id/view_ball"
android:layout_width="50dp"
android:layout_height="50dp"
android:background="@drawable/shape_ball" />
</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
<solid android:color="#35A9F7"/>
</shape>
因?yàn)槲覀儨?zhǔn)備實(shí)現(xiàn)的小球下落是X坐標(biāo)與Y坐標(biāo)都會(huì)發(fā)生變化,因此我們需要借助Point類來(lái)保存X坐標(biāo)與Y坐標(biāo)的值互订,在這里,我將自己定義Point類痘拆,而不使用android.graphics.Point仰禽,自定義Point如下:
public class Point {
private int x;
private int y;
public Point() {
}
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
}
好了,基本的東西都準(zhǔn)備好了纺蛆,下面創(chuàng)建ValueAnimator:
ValueAnimator.ofObject(???, new Point(0, 0), new Point(300, 500));
一開(kāi)始就卡住了吐葵,原因是還沒(méi)有提供自定義Evaluator。下面我們定義一個(gè)BallEvaluator:
public class BallEvaluator implements TypeEvaluator<Point> {
private Point point = new Point();
@Override
public Point evaluate(float fraction, Point startValue, Point endValue) {
point.setX((int) (startValue.getX() + fraction * (endValue.getX() - startValue.getX())));
point.setY((int) (startValue.getY() + fraction * (endValue.getY() - startValue.getY())));
return point;
}
}
現(xiàn)在我們可以繼續(xù)實(shí)現(xiàn)代碼了:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final View viewBall = findViewById(R.id.view_ball);
ValueAnimator valueAnimator = ValueAnimator.ofObject(new BallEvaluator(),
new Point(0, 0), new Point(300, 500));
valueAnimator.setDuration(2000);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
Point point = (Point) animation.getAnimatedValue();
viewBall.layout(point.getX(), point.getY(), point.getX() + viewBall.getWidth(),
point.getY() + viewBall.getHeight());
}
});
valueAnimator.start();
}
}
運(yùn)行效果:
三桥氏、ObjectAnimator
3.1温峭、這是什么
前面我們學(xué)習(xí)了ValueAnimator,難道大家沒(méi)發(fā)現(xiàn)一個(gè)問(wèn)題嗎字支?ValueAnimator只能針對(duì)值進(jìn)行動(dòng)畫(huà)改變凤藏,如果我們需要關(guān)聯(lián)到View的變化,就需要設(shè)置監(jiān)聽(tīng)事件堕伪,根據(jù)值得變化手動(dòng)去操作這個(gè)View變化揖庄。這相比補(bǔ)間動(dòng)畫(huà)要麻煩很多。為了能讓動(dòng)畫(huà)直接作用于View欠雌,Google基于ValueAnimator編寫(xiě)了ObjectAnimator蹄梢。也就是說(shuō)ObjectAnimator繼承自ValueAnimator,ValueAnimator能用的方法在ObjectAnimator中照樣可以用富俄。但是ObjectAnimator重新編寫(xiě)了幾個(gè)方法禁炒,例如:ofInt()、ofFloat()等霍比。
3.2幕袱、使用ObjectAnimator
既然說(shuō)ObjectAnimator能直接作用于View,想必一定很好用吧桂塞。下面我將使用ObjectAnimator實(shí)現(xiàn)讓TextView從不透明->透明->不透明的alpha動(dòng)畫(huà)凹蜂。按照慣例,先編寫(xiě)界面:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<TextView
android:id="@+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="HurryYu"
android:textSize="18sp" />
<Button
android:id="@+id/btn_start"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="開(kāi)始動(dòng)畫(huà)" />
</LinearLayout>
然后編寫(xiě)業(yè)務(wù)代碼:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final TextView tv = findViewById(R.id.tv);
findViewById(R.id.btn_start).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(tv,"alpha",1,0,1);
objectAnimator.setDuration(2000);
objectAnimator.start();
}
});
}
}
關(guān)鍵部分就是ObjectAnimator了:
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(tv, "alpha", 1, 0, 1);
objectAnimator.setDuration(2000);
objectAnimator.start();
ObjectAnimator的靜態(tài)工廠方法比ValueAnimator的多出了兩個(gè)參數(shù)阁危,下面我將介紹多出的兩個(gè)參數(shù)的含義:
public static ObjectAnimator ofFloat(Object target, String propertyName, float... values)
- target:指定操作的是哪個(gè)控件
- propertyName:指定要操作這個(gè)控件的哪個(gè)屬性
我們?nèi)extView查找是否真的存在alpha這個(gè)屬性玛痊,發(fā)現(xiàn)并沒(méi)有。接著我們?nèi)ニ母割愔胁檎铱翊颍矝](méi)有擂煞!那ObjectAnimator是如何改變透明度的呢?實(shí)際上它并不是直接修改第二個(gè)參數(shù)傳入的屬性趴乡,而是查找它對(duì)應(yīng)的set方法來(lái)設(shè)置值对省。例如上面的例子中蝗拿,我們傳入“alpha”,則它會(huì)去TextView中找setAlpha()方法蒿涎。那TextView中有這個(gè)方法嗎哀托?確實(shí)是有的,繼承自View劳秋。
因此我們要使用ObjectAnimator實(shí)現(xiàn)動(dòng)畫(huà)仓手,必須保證如下兩點(diǎn):
- 在要操作的控件中,必須存在對(duì)應(yīng)屬性的set方法玻淑,且該方法接收的參數(shù)類型要與靜態(tài)工廠方法所用類型一致嗽冒,例如ofInt()就要求set方法中接收的參數(shù)類型為int類型
- set方法的命名必須滿足駝峰命名法,例如屬性名為rotate补履,則對(duì)應(yīng)的set方法必須為setRotate()
注意ObjectAnimator會(huì)在設(shè)定時(shí)間內(nèi)不斷調(diào)用對(duì)應(yīng)屬性的set方法添坊,就像ValueAnimator中加監(jiān)聽(tīng)后會(huì)不斷回調(diào)onAnimationUpdate()方法一樣。不過(guò)它也是僅僅調(diào)用對(duì)應(yīng)屬性的set方法箫锤,而set方法中對(duì)控件的操作還是要我們自己去實(shí)現(xiàn)的贬蛙,只不過(guò)常用的操作系統(tǒng)已經(jīng)為我們實(shí)現(xiàn)好。
3.2.1谚攒、什么時(shí)候需要提供對(duì)應(yīng)屬性的get方法速客?
如果我們只給第三個(gè)參數(shù)傳入一個(gè)值,系統(tǒng)在執(zhí)行動(dòng)畫(huà)以前就會(huì)先去調(diào)用對(duì)應(yīng)屬性的get方法獲取初始值五鲫,如果沒(méi)有提供get方法溺职,則會(huì)使用默認(rèn)值。通過(guò)ofInt()構(gòu)造出的ObjectAnimator位喂,屬性的默認(rèn)值為0浪耘;ofFloat()構(gòu)造出來(lái)的,屬性的默認(rèn)值為0.0塑崖;ofObject()構(gòu)造出來(lái)的七冲,屬性的默認(rèn)值為null,這就有問(wèn)題了规婆,著名異常NullPointerException澜躺。因此,當(dāng)動(dòng)畫(huà)只傳入了一個(gè)過(guò)渡值時(shí)抒蚜,系統(tǒng)會(huì)調(diào)用屬性對(duì)應(yīng)的get方法來(lái)獲取初始值掘鄙,如果沒(méi)有提供get方法,則會(huì)使用屬性類型對(duì)應(yīng)的默認(rèn)值嗡髓,當(dāng)無(wú)法正常獲取到屬性的初始值時(shí)操漠,會(huì)直接報(bào)異常。
四饿这、AnimatorSet
之前我們使用的ObjectAnimator和ValueAnimator都只能同時(shí)播放一種動(dòng)畫(huà)浊伙,能不能讓多個(gè)動(dòng)畫(huà)同時(shí)播放或者按順序依次播放呢撞秋?答案是使用AnimatorSet。我們先來(lái)看看最基本的使用方法:
4.1嚣鄙、playSequentially()
AnimatorSet為我們提供了playSequentially()方法吻贿,此方法可以接收一個(gè)可變長(zhǎng)度的Animator或是一個(gè)List集合:
public void playSequentially(Animator... items)
public void playSequentially(List<Animator> items)
當(dāng)然我認(rèn)為一般情況下使用可變參數(shù)的那個(gè)更爽,省去了創(chuàng)建集合的麻煩哑子。這個(gè)方法的作用是能依次執(zhí)行傳入的動(dòng)畫(huà)廓八,一般情況下我們會(huì)傳入ObjectAnimator而不是ValueAnimator,原因相信大家看過(guò)前面的內(nèi)容后都能懂赵抢。需要特別說(shuō)明的是:它只能依次執(zhí)行傳入的動(dòng)畫(huà),如果其中一個(gè)動(dòng)畫(huà)是無(wú)限重復(fù)的声功,那么它后面的動(dòng)畫(huà)將都不會(huì)執(zhí)行烦却。必須確保執(zhí)行完成一個(gè)動(dòng)畫(huà)之后,才回去執(zhí)行下一個(gè)先巴。它的完整用法如下:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final TextView tv = findViewById(R.id.tv);
findViewById(R.id.btn_start).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ObjectAnimator alphaAnimator =
ObjectAnimator.ofFloat(tv, "alpha", 1, 0, 1);
ObjectAnimator rotateAnimator =
ObjectAnimator.ofFloat(tv, "rotation", 0, 360, 0);
ObjectAnimator scaleAnimator =
ObjectAnimator.ofFloat(tv, "scaleX", 1, 2, 1);
ObjectAnimator translateAnimator =
ObjectAnimator.ofFloat(tv, "translationX", 0, 100, 0);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playSequentially(alphaAnimator, rotateAnimator, scaleAnimator, translateAnimator);
animatorSet.setDuration(2000);
animatorSet.start();
}
});
}
}
當(dāng)然也可以單獨(dú)給里面的每一個(gè)動(dòng)畫(huà)設(shè)置執(zhí)行時(shí)間其爵。但是請(qǐng)注意,如果單獨(dú)給每個(gè)動(dòng)畫(huà)設(shè)置了執(zhí)行時(shí)間伸蚯,就不要再去調(diào)用AnimatorSet的setDuration()摩渺,否則單個(gè)動(dòng)畫(huà)設(shè)置的時(shí)間會(huì)被覆蓋。
4.2剂邮、playTogether()
這個(gè)看名字就應(yīng)該能猜出是讓動(dòng)畫(huà)一起執(zhí)行的方法摇幻,同樣它也有一個(gè)重載:
public void playTogether(Animator... items)
public void playTogether(Collection<Animator> items)
其實(shí)這個(gè)方法只負(fù)責(zé)同時(shí)開(kāi)始傳入的所有動(dòng)畫(huà),至于里面的動(dòng)畫(huà)執(zhí)行時(shí)間是多長(zhǎng)挥萌?是不是一直重復(fù)绰姻?等等這些問(wèn)題都與它沒(méi)有關(guān)系。它的完整用法和上面的playSequentially()是一模一樣的引瀑。
4.3狂芋、AnimatorSet.Builder
Builder是AnimatorSet類中的內(nèi)部類,它里面提供了一些方法憨栽,我們可以使用這些方法來(lái)組合出一組動(dòng)畫(huà)帜矾,它可以控制這組動(dòng)畫(huà)中先執(zhí)行什么,后執(zhí)行什么屑柔,什么與什么一起執(zhí)行屡萤。下面我將列舉Builder中每個(gè)方法的含義:
方法名 | 說(shuō)明 |
---|---|
with | 設(shè)置當(dāng)前動(dòng)畫(huà)與前一個(gè)動(dòng)畫(huà)一起執(zhí)行 |
before | 設(shè)置當(dāng)前動(dòng)畫(huà)在之前所有動(dòng)畫(huà)之后執(zhí)行,也可理解為之前的動(dòng)畫(huà)都在這個(gè)動(dòng)畫(huà)之前執(zhí)行 |
after | 設(shè)置當(dāng)前動(dòng)畫(huà)在之前所有動(dòng)畫(huà)之前執(zhí)行掸宛,也可理解為之前的所有動(dòng)畫(huà)都在這個(gè)動(dòng)畫(huà)之后執(zhí)行 |
關(guān)于before與after的說(shuō)明灭衷,我個(gè)人的理解與網(wǎng)上很多文章的理解有一些不同,但實(shí)驗(yàn)結(jié)果卻是我這個(gè)說(shuō)法是對(duì)的旁涤。如果讀者發(fā)現(xiàn)我的理解錯(cuò)誤翔曲,請(qǐng)指出迫像,謝謝!
要得到AnimatorSet.Builder對(duì)象瞳遍,只能使用AnimatorSet的play()方法闻妓,下面我將使用AnimatorSet.Builder實(shí)現(xiàn)一個(gè)組合動(dòng)畫(huà):
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final TextView tv = findViewById(R.id.tv);
findViewById(R.id.btn_start).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ObjectAnimator alphaAnimator =
ObjectAnimator.ofFloat(tv, "alpha", 1, 0, 1);
ObjectAnimator rotateAnimator =
ObjectAnimator.ofFloat(tv, "rotation", 0, 360, 0);
rotateAnimator.setDuration(15000);
ObjectAnimator scaleAnimator =
ObjectAnimator.ofFloat(tv, "scaleX", 1, 2, 1);
ObjectAnimator translateAnimator =
ObjectAnimator.ofFloat(tv, "translationX", 0, 100, 0);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.play(alphaAnimator)
.with(rotateAnimator)
.after(scaleAnimator)
.before(translateAnimator);
animatorSet.setDuration(2000).start();
}
});
}
}
這個(gè)動(dòng)畫(huà)的執(zhí)行過(guò)程是:先執(zhí)行scaleAnimator,當(dāng)scaleAnimator執(zhí)行完成后掠械,alphaAnimator與rotateAnimator一起執(zhí)行由缆,等它們兩個(gè)執(zhí)行完成后,執(zhí)行translateAnimator猾蒂。