動畫基礎概念
動畫分類
Android 中動畫分為兩種,一種是 Tween 動畫、還有一種是 Frame 動畫。
補間動畫(Tween動畫)
這種實現方式可以使視圖組件移動、放大、縮小以及產生透明度的變化;
幀動畫(Frame 動畫)
傳統的動畫方法,通過順序的播放排列好的圖片來實現,類似電影、gif。
屬性動畫(Property animation)
自Android 3.0版本開始,系統給我們提供了一種全新的動畫模式,屬性動畫(property animation),它的功能非常強大,彌補了之前補間動畫的一些缺陷,幾乎是可以完全替代掉補間動畫了。
補間動畫(Tween動畫)
1、透明度動畫AlphaAnimation
// 透明度動畫
public void alpha(View v) {
AlphaAnimation alphaAnimation = new AlphaAnimation(0, 1);
/* AlphaAnimation (float fromAlpha, float toAlpha)
fromAlpha: 動畫的起始alpha值 (范圍:0:完全透明 -1:完全不透明)
toAlpha:終止的值,動畫結束的值 */
alphaAnimation.setDuration(3000);// 每次動畫持續時間3秒
alphaAnimation.setFillAfter(true);// 動畫最后是否停留在終止的狀態
alphaAnimation.setRepeatCount(3);// 動畫重復的次數
alphaAnimation.setRepeatMode(Animation.REVERSE);// REVERSE: 反轉模式
// RESTART:重新開始
// 動畫監聽
alphaAnimation.setAnimationListener(new AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
System.out.println("動畫開始回調");
}
@Override
public void onAnimationRepeat(Animation animation) {
System.out.println("動畫重復回調");
}
@Override
public void onAnimationEnd(Animation animation) {
System.out.println("動畫結束回調");
Toast.makeText(getApplicationContext(), "動畫結束,進入陌陌關心你界面",
Toast.LENGTH_LONG).show();
}
});
iconIv.startAnimation(alphaAnimation);
}
2、平移動畫TranslateAnimation
/* TranslateAnimation (int fromXType,
float fromXValue,
int toXType,
float toXValue,
int fromYType,
float fromYValue,
int toYType,
float toYValue)
原點:控件第一次繪制的左上角的坐標點
fromXType(起點,相對于原點偏移方式):
Animation.ABSOLUTE 絕對值,像素值
Animation.RELATIVE_TO_SELF 相對于自己
Animation.RELATIVE_TO_PARENT 相對于父控件.
fromXValue(起點,相對于原點偏移量):
絕對值/百分比
*/
TranslateAnimation translateAnimation = new TranslateAnimation(
Animation.ABSOLUTE,
iconIv.getWidth(), // 當前屏幕密度 :240 標準的屏幕密度:160 則dp轉px :
// px=dp*240/160
Animation.ABSOLUTE, iconIv.getWidth(),
Animation.ABSOLUTE, 0,
Animation.RELATIVE_TO_SELF, 1);
translateAnimation.setDuration(3000);// 每次動畫持續時間3秒
translateAnimation.setFillAfter(true);// 動畫最后停留在終止的狀態
translateAnimation.setRepeatCount(3);// 動畫重復的次數
translateAnimation.setRepeatMode(Animation.REVERSE);// REVERSE: 反轉模式
// RESTART:重新開始
translateAnimation.setInterpolator(new BounceInterpolator());// 設置特效,彈簧效果
iconIv.startAnimation(translateAnimation);
System.out.println("控件的寬度" + iconIv.getWidth());
}
3、縮放動畫ScaleAnimation
/* ScaleAnimation (float fromX,
float toX,
float fromY,
float toY,
int pivotXType,
float pivotXValue,
int pivotYType,
float pivotYValue)
fromX: 縮放起始比例-水平方向
toX: 縮放最終比例-水平方向
pivotXType(中心點相較于原點 x方向的類型):
Animation.ABSOLUTE
Animation.RELATIVE_TO_SELF
RELATIVE_TO_PARENT.
pivotXValue: 絕對值/百分比
*/
public void scale(View v) {
ScaleAnimation scaleAnimation =new ScaleAnimation
(0, 2, 0, 2, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
scaleAnimation.setDuration(3000);// 每次動畫持續時間3秒
scaleAnimation.setFillAfter(true);// 動畫最后停留在終止的狀態
scaleAnimation.setRepeatCount(1);// 動畫重復的次數
iconIv.startAnimation(scaleAnimation);
}
4、旋轉動畫 rotate
創建一個Animation類型的XML文件;
<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:fromDegrees="0"
android:toDegrees="180"
android:duration="3000"
android:interpolator="@android:anim/overshoot_interpolator"
android:fillAfter="true"
android:repeatCount="2"
android:repeatMode="reverse"
android:pivotX="50%"
android:pivotY="50%"
>
<!--fromDegrees:起始的度數
toDegrees:終止的度數
infinite:無限次數
起始度數大于終止度數,則能逆時針旋轉,否則順時針
android:pivotX="50%":旋轉圍繞的軸心,x方向位置,相對于自己的寬度的一半
android:pivotX="50%p":相對于父控件寬度的一半
-->
</rotate>
Animation animation1 = AnimationUtils.loadAnimation(this,R.anim.rotate);
imageView.startAnimation(animation1);
復合動畫
AnimationSet animationSet=new AnimationSet(false);
animationSet.addAnimation(animation1);
//這樣在這里面添加就可以了;
Animation rotateAnimation = AnimationUtils.loadAnimation(this, R.anim.rotate);
animationSet.addAnimation(rotateAnimation);
幀動畫(Frame 動畫)
方式一;使用XML的方式;
1、創建一個AnimationDrawable 的XML文件;
<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android" android:oneshot="false" >//這個是反復執行的設置;
<item android:drawable="@drawable/emoji_088" android:duration="150" />
<item android:drawable="@drawable/emoji_095" android:duration="150" />
<item android:drawable="@drawable/emoji_096" android:duration="150" />
<item android:drawable="@drawable/emoji_097" android:duration="150" />
<item android:drawable="@drawable/emoji_098" android:duration="150" />
<item android:drawable="@drawable/emoji_099" android:duration="150" />
<item android:drawable="@drawable/emoji_100" android:duration="150" />
<item android:drawable="@drawable/emoji_101" android:duration="150" />
<item android:drawable="@drawable/emoji_102" android:duration="50" />
<item android:drawable="@drawable/emoji_103" android:duration="50" />
<item android:drawable="@drawable/emoji_104" android:duration="50" />
</animation-list>
<!--android:drawable[drawable]//加載Drawable對象
android:duration[long]//每一幀動畫的持續時間(單位ms)
android:oneshot[boolean]//動畫是否只運行一次,true運行一次,false重復運行
android:visible[boolean]//Drawable對象的初始能見度狀態,true可見,false不可見(默認為false)-->
2、第二步就是需要將這個幀動畫設置到一個容器中去;imageview;
android:src="@drawable/animation" />
3、可以從控件中獲取到這個幀動畫的圖片然后再對他進行操作;
drawable = (AnimationDrawable) imageView.getDrawable();
if (drawable.isRunning()) {
drawable.stop();
}else{
drawable.start();
}
方式二:使用代碼的方式進行;
方式1:添加多個幀 Drawable
mAnimationDrawable = new AnimationDrawable();
mAnimationDrawable.setOneShot(false);//是否執行一次
//添加多個幀 Drawable
for(int i=0;i<11;i++){
Drawable frame = getResources().getDrawable(R.drawable.girl_1+i);
mAnimationDrawable.addFrame(frame, 200);
}
mImageView.setImageDrawable(mAnimationDrawable);//設置幀動畫,默認是停止狀態
方式2:引用xml 幀動畫drawable
// 引用xml 幀動畫drawable
mAnimationDrawable=(AnimationDrawable) getResources().getDrawable(R.drawable.frame);
mImageView.setImageDrawable(mAnimationDrawable);
屬性動畫(Property animation)
為什么要引入屬性動畫?
Android之前的補間動畫機制其實還算是比較健全的,在android.view.animation包下面有好多的類可以供我們操作,來完成一系列的動畫效果,比如說對View進行移動、縮放、旋轉和淡入淡出,并且我們還可以借助AnimationSet來將這些動畫效果組合起來使用,除此之外還可以通過配置Interpolator來控制動畫的播放速度等等等等。那么這里大家可能要產生疑問了,既然之前的動畫機制已經這么健全了,為什么還要引入屬性動畫呢?
其實上面所謂的健全都是相對的,如果你的需求中只需要對View進行移動、縮放、旋轉和淡入淡出操作,那么補間動畫確實已經足夠健全了。但是很顯然,這些功能是不足以覆蓋所有的場景的,一旦我們的需求超出了移動、縮放、旋轉和淡入淡出這四種對View的操作,那么補間動畫就不能再幫我們忙了,也就是說它在功能和可擴展方面都有相當大的局限性,那么下面我們就來看看補間動畫所不能勝任的場景。
注意上面我在介紹補間動畫的時候都有使用“對View進行操作”這樣的描述,沒錯,補間動畫是只能夠作用在View上的。也就是說,我們可以對一個Button、TextView、甚至是LinearLayout、或者其它任何繼承自View的組件進行動畫操作,但是如果我們想要對一個非View的對象進行動畫操作,抱歉,補間動畫就幫不上忙了。可能有的朋友會感到不能理解,我怎么會需要對一個非View的對象進行動畫操作呢?這里我舉一個簡單的例子,比如說我們有一個自定義的View,在這個View當中有一個Point對象用于管理坐標,然后在onDraw()方法當中就是根據這個Point對象的坐標值來進行繪制的。也就是說,如果我們可以對Point對象進行動畫操作,那么整個自定義View的動畫效果就有了。顯然,補間動畫是不具備這個功能的,這是它的第一個缺陷。
然后補間動畫還有一個缺陷,就是它只能夠實現移動、縮放、旋轉和淡入淡出這四種動畫操作,那如果我們希望可以對View的背景色進行動態地改變呢?很遺憾,我們只能靠自己去實現了。說白了,之前的補間動畫機制就是使用硬編碼的方式來完成的,功能限定死就是這些,基本上沒有任何擴展性可言。
最后,補間動畫還有一個致命的缺陷,就是它只是改變了View的顯示效果而已,而不會真正去改變View的屬性。什么意思呢?比如說,現在屏幕的左上角有一個按鈕,然后我們通過補間動畫將它移動到了屏幕的右下角,現在你可以去嘗試點擊一下這個按鈕,點擊事件是絕對不會觸發的,因為實際上這個按鈕還是停留在屏幕的左上角,只不過補間動畫將這個按鈕繪制到了屏幕的右下角而已。
也正是因為這些原因,Android開發團隊決定在3.0版本當中引入屬性動畫這個功能,那么屬性動畫是不是就把上述的問題全部解決掉了?下面我們就來一起看一看。
新引入的屬性動畫機制已經不再是針對于View來設計的了,也不限定于只能實現移動、縮放、旋轉和淡入淡出這幾種動畫操作,同時也不再只是一種視覺上的動畫效果了。它實際上是一種不斷地對值進行操作的機制,并將值賦值到指定對象的指定屬性上,可以是任意對象的任意屬性。所以我們仍然可以將一個View進行移動或者縮放,但同時也可以對自定義View中的Point對象進行動畫操作了。我們只需要告訴系統動畫的運行時長,需要執行哪種類型的動畫,以及動畫的初始值和結束值,剩下的工作就可以全部交給系統去完成了。
既然屬性動畫的實現機制是通過對目標對象進行賦值并修改其屬性來實現的,那么之前所說的按鈕顯示的問題也就不復存在了,如果我們通過屬性動畫來移動一個按鈕,那么這個按鈕就是真正的移動了,而不再是僅僅在另外一個位置繪制了而已。
ValueAnimator
ValueAnimator是整個屬性動畫機制當中最核心的一個類,前面我們已經提到了,屬性動畫的運行機制是通過不斷地對值進行操作來實現的,而初始值和結束值之間的動畫過渡就是由ValueAnimator這個類來負責計算的。
它的內部使用一種時間循環的機制來計算值與值之間的動畫過渡,我們只需要將初始值和結束值提供給ValueAnimator,并且告訴它動畫所需運行的時長,那么ValueAnimator就會自動幫我們完成從初始值平滑地過渡到結束值這樣的效果。除此之外,ValueAnimator還負責管理動畫的播放次數、播放模式、以及對動畫設置監聽器等,確實是一個非常重要的類。
但是ValueAnimator的用法卻一點都不復雜,我們先從最簡單的功能看起吧,比如說想要將一個值從0平滑過渡到1,時長300毫秒,就可以這樣寫:
ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
anim.setDuration(300);
anim.start();
怎么樣?很簡單吧,調用ValueAnimator的ofFloat()方法就可以構建出一個ValueAnimator的實例,ofFloat()方法當中允許傳入多個float類型的參數,這里傳入0和1就表示將值從0平滑過渡到1,然后調用ValueAnimator的setDuration()方法來設置動畫運行的時長,最后調用start()方法啟動動畫。
用法就是這么簡單,現在如果你運行一下上面的代碼,動畫就會執行了。可是這只是一個將值從0過渡到1的動畫,又看不到任何界面效果,我們怎樣才能知道這個動畫是不是已經真正運行了呢?這就需要借助監聽器來實現了,如下所示:
ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
anim.setDuration(300);
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float currentValue = (float) animation.getAnimatedValue();
Log.d("TAG", "cuurent value is " + currentValue);
}
});
anim.start();
可以看到,這里我們通過addUpdateListener()方法來添加一個動畫的監聽器,在動畫執行的過程中會不斷地進行回調,我們只需要在回調方法當中將當前的值取出并打印出來,就可以知道動畫有沒有真正運行了。運行上述代碼,控制臺打印如下所示:
從打印日志的值我們就可以看出,ValueAnimator確實已經在正常工作了,值在300毫秒的時間內從0平滑過渡到了1,而這個計算工作就是由ValueAnimator幫助我們完成的。另外ofFloat()方法當中是可以傳入任意多個參數的,因此我們還可以構建出更加復雜的動畫邏輯,比如說將一個值在5秒內從0過渡到5,再過渡到3,再過渡到10,就可以這樣寫:
ValueAnimator anim = ValueAnimator.ofFloat(0f, 5f, 3f, 10f);
anim.setDuration(5000);
anim.start();
當然也許你并不需要小數位數的動畫過渡,可能你只是希望將一個整數值從0平滑地過渡到100,那么也很簡單,只需要調用ValueAnimator的ofInt()方法就可以了,如下所示:
ValueAnimator anim = ValueAnimator.ofInt(0, 100);
常用方法
ValueAnimator當中最常用的應該就是ofFloat()和ofInt()這兩個方法了,另外還有一個ofObject()方法,我會在下篇文章進行講解。
setStartDelay():動畫延遲播放的時間
setRepeatCount():動畫循環播放的次數
setRepeatMode():循環播放的模式,循環模式包括RESTART和REVERSE兩種,分別表示重新播放和倒序播放的意思。
ObjectAnimator
對任意對象的任意屬性進行動畫操作
相比于ValueAnimator,ObjectAnimator可能才是我們最常接觸到的類,因為ValueAnimator只不過是對值進行了一個平滑的動畫過渡,但我們實際使用到這種功能的場景好像并不多。而ObjectAnimator則就不同了,它是可以直接任意對象的任意屬性進行動畫操作的,比如說View的alpha屬性。
不過雖說ObjectAnimator會更加常用一些,但是它其實是繼承自ValueAnimator的,底層的動畫實現機制也是基于ValueAnimator來完成的,因此ValueAnimator仍然是整個屬性動畫當中最核心的一個類。
ObjectAnimator.ofFloat
ObjectAnimator ofFloat (Object target,
String propertyName,
float... values)
Constructs and returns an ObjectAnimator that animates between float values. A single value implies that that value is the one being animated to, in which case the start value will be derived from the property being animated and the target object when start() is called for the first time. Two values imply starting and ending values. More than two values imply a starting value, values to animate through along the way, and an ending value (these values will be distributed evenly across the duration of the animation).
Parameters
target
Object: The object whose property is to be animated. This object should have a public method on it called setName(), where name is the value of the propertyName parameter.
propertyName
String: The name of the property being animated.
values
float: A set of values that the animation will animate between over time.
Returns
ObjectAnimator
An ObjectAnimator object that is set up to animate between the given values.
那么既然是繼承關系,說明ValueAnimator中可以使用的方法在ObjectAnimator中也是可以正常使用的,它們的用法也非常類似。
alpha
這里如果我們想要將一個TextView在5秒中內從常規變換成全透明,再從全透明變換成常規,就可以這樣寫:
ObjectAnimator animator = ObjectAnimator.ofFloat(textview, "alpha", 1f, 0f, 1f);
/*
參數1:傳入一個object對象,我們想要對哪個對象進行動畫操作就傳入什么,這里我傳入了一個textview。
參數2:想要對該對象的哪個屬性進行動畫操作,由于我們想要改變TextView的不透明度,因此這里傳入"alpha"。
參數3:不固定長度,想要完成什么樣的動畫就傳入什么值,這里傳入的值就表示將TextView從常規變換成全透明,再從全透明變換成常規。
*/
animator.setDuration(5000);
animator.start();
rotation
學會了這一個用法之后,其它的用法我們就可以舉一反三了,那比如說我們想要將TextView進行一次360度的旋轉,就可以這樣寫:
ObjectAnimator animator = ObjectAnimator.ofFloat(textview, "rotation", 0f, 360f);
animator.setDuration(5000);
animator.start();
可以看到,這里我們將第二個參數改成了"rotation",然后將動畫的初始值和結束值分別設置成0和360,現在運行一下代碼,效果如下圖所示:
translationX
那么如果想要將TextView先向左移出屏幕,然后再移動回來,就可以這樣寫:
float curTranslationX = textview.getTranslationX();
ObjectAnimator animator = ObjectAnimator.ofFloat(textview, "translationX", curTranslationX, -500f, curTranslationX);
animator.setDuration(5000);
animator.start();
這里我們先是調用了TextView的getTranslationX()方法來獲取到當前TextView的translationX的位置,然后ofFloat()方法的第二個參數傳入"translationX",緊接著后面三個參數用于告訴系統TextView應該怎么移動,現在運行一下代碼,效果如下圖所示:
scaleY
然后我們還可以TextView進行縮放操作,比如說將TextView在垂直方向上放大3倍再還原,就可以這樣寫:
ObjectAnimator animator = ObjectAnimator.ofFloat(textview, "scaleY", 1f, 3f, 1f);
animator.setDuration(5000);
animator.start();
這里將ofFloat()方法的第二個參數改成了"scaleY",表示在垂直方向上進行縮放,現在重新運行一下程序,效果如下圖所示:
工作機制
ofFloat()方法的第二個參數到底可以傳哪些值呢?目前我們使用過了alpha、rotation、translationX和scaleY這幾個值,分別可以完成淡入淡出、旋轉、水平移動、垂直縮放這幾種動畫,那么還有哪些值是可以使用的呢?
我們可以傳入任意的值到ofFloat()方法的第二個參數當中。
因為ObjectAnimator在設計的時候就沒有針對于View來進行設計,而是針對于任意對象的,它所負責的工作就是不斷地向某個對象中的某個屬性進行賦值,然后對象根據屬性值的改變再來決定如何展現出來。
那么比如說我們調用下面這樣一段代碼:
ObjectAnimator.ofFloat(textview, "alpha", 1f, 0f);
其實這段代碼的意思就是ObjectAnimator會幫我們不斷地改變textview對象中alpha屬性的值,從1f變化到0f。然后textview對象需要根據alpha屬性值的改變來不斷刷新界面的顯示,從而讓用戶可以看出淡入淡出的動畫效果。
那么textview對象中是不是有alpha屬性這個值呢?沒有,不僅textview沒有這個屬性,連它所有的父類也是沒有這個屬性的!這就奇怪了,textview當中并沒有alpha這個屬性,ObjectAnimator是如何進行操作的呢?其實ObjectAnimator內部的工作機制并不是直接對我們傳入的屬性名進行操作的,而是會去尋找這個屬性名對應的get和set方法,因此alpha屬性所對應的get和set方法應該就是:
public void setAlpha(float value);
public float getAlpha();
那么textview對象中是否有這兩個方法呢?確實有,并且這兩個方法是由View對象提供的,也就是說不僅TextView可以使用這個屬性來進行淡入淡出動畫操作,任何繼承自View的對象都可以的。
既然alpha是這個樣子,相信大家一定已經明白了,前面我們所用的所有屬性都是這個工作原理,那么View當中一定也存在著setRotation()、getRotation()、setTranslationX()、getTranslationX()、setScaleY()、getScaleY()這些方法,不信的話你可以到View當中去找一下。
組合動畫-AnimatorSet
實現組合動畫功能主要需要借助AnimatorSet這個類,這個類提供了一個play()方法,如果我們向這個方法中傳入一個Animator對象(ValueAnimator或ObjectAnimator)將會返回一個AnimatorSet.Builder的實例。
AnimatorSet.Builder
AnimatorSet.Builder中包括以下四個方法:
after(Animator anim) 將現有動畫插入到傳入的動畫之后執行
after(long delay) 將現有動畫延遲指定毫秒后執行
before(Animator anim) 將現有動畫插入到傳入的動畫之前執行
with(Animator anim) 將現有動畫和傳入的動畫同時執行
好的,有了這四個方法,我們就可以完成組合動畫的邏輯了,那么比如說我們想要讓TextView先從屏幕外移動進屏幕,然后開始旋轉360度,旋轉的同時進行淡入淡出操作,就可以這樣寫:
ObjectAnimator moveIn = ObjectAnimator.ofFloat(textview, "translationX", -500f, 0f);
ObjectAnimator rotate = ObjectAnimator.ofFloat(textview, "rotation", 0f, 360f);
ObjectAnimator fadeInOut = ObjectAnimator.ofFloat(textview, "alpha", 1f, 0f, 1f);
AnimatorSet animSet = new AnimatorSet();
animSet.play(rotate).with(fadeInOut).after(moveIn);
animSet.setDuration(5000);
animSet.start();
可以看到,這里我們先是把三個動畫的對象全部創建出來,然后new出一個AnimatorSet對象之后將這三個動畫對象進行播放排序,讓旋轉和淡入淡出動畫同時進行,并把它們插入到了平移動畫的后面,最后是設置動畫時長以及啟動動畫。運行一下上述代碼,效果如下圖所示:
Animator監聽器
AnimatorListener
Animator類當中提供了一個addListener()方法,這個方法接收一個AnimatorListener,我們只需要去實現這個AnimatorListener就可以監聽動畫的各種事件了。
大家已經知道,ObjectAnimator是繼承自ValueAnimator的,而ValueAnimator又是繼承自Animator的,因此不管是ValueAnimator還是ObjectAnimator都是可以使用addListener()這個方法的。另外AnimatorSet也是繼承自Animator的,因此addListener()這個方法算是個通用的方法。
添加一個監聽器的代碼如下所示:
anim.addListener(new AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
//動畫開始的時候調用
}
@Override
public void onAnimationRepeat(Animator animation) {
//動畫重復執行的時候調用
}
@Override
public void onAnimationEnd(Animator animation) {
//動畫結束的時候調用
}
@Override
public void onAnimationCancel(Animator animation) {
//動畫被取消的時候調用
}
});
可以看到,我們需要實現接口中的四個方法,onAnimationStart()方法會在動畫開始的時候調用,onAnimationRepeat()方法會在動畫重復執行的時候調用,onAnimationEnd()方法會在動畫結束的時候調用,onAnimationCancel()方法會在動畫被取消的時候調用。
AnimatorListenerAdapter
但是也許很多時候我們并不想要監聽那么多個事件,可能我只想要監聽動畫結束這一個事件,那么每次都要將四個接口全部實現一遍就顯得非常繁瑣。沒關系,為此Android提供了一個適配器類,叫作AnimatorListenerAdapter,使用這個類就可以解決掉實現接口繁瑣的問題了,如下所示:
anim.addListener(new AnimatorListenerAdapter() {
});
這里我們向addListener()方法中傳入這個適配器對象,由于AnimatorListenerAdapter中已經將每個接口都實現好了,所以這里不用實現任何一個方法也不會報錯。那么如果我想監聽動畫結束這個事件,就只需要單獨重寫這一個方法就可以了,如下所示:
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
}
});
使用XML編寫動畫
我們可以使用代碼來編寫所有的動畫功能,這也是最常用的一種做法。不過,過去的補間動畫除了使用代碼編寫之外也是可以使用XML編寫的,因此屬性動畫也提供了這一功能,即通過XML來完成和代碼一樣的屬性動畫功能。
通過XML來編寫動畫可能會比通過代碼來編寫動畫要慢一些,但是在重用方面將會變得非常輕松,比如某個將通用的動畫編寫到XML里面,我們就可以在各個界面當中輕松去重用它。
如果想要使用XML來編寫動畫,首先要在res目錄下面新建一個animator文件夾,所有屬性動畫的XML文件都應該存放在這個文件夾當中。
XML標簽
animato
對應代碼中的ValueAnimator
objectAnimator
對應代碼中的ObjectAnimator
set
對應代碼中的AnimatorSet
那么比如說我們想要實現一個從0到100平滑過渡的動畫,在XML當中就可以這樣寫:
<animator xmlns:android="http://schemas.android.com/apk/res/android"
android:valueFrom="0"
android:valueTo="100"
android:valueType="intType"/>
而如果我們想將一個視圖的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"/>
另外,我們也可以使用XML來完成復雜的組合動畫操作,比如將一個視圖先從屏幕外移動進屏幕,然后開始旋轉360度,旋轉的同時進行淡入淡出操作,就可以這樣寫:
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:ordering="sequentially" >
<objectAnimator
android:duration="2000"
android:propertyName="translationX"
android:valueFrom="-500"
android:valueTo="0"
android:valueType="floatType" >
</objectAnimator>
<set android:ordering="together" >
<objectAnimator
android:duration="3000"
android:propertyName="rotation"
android:valueFrom="0"
android:valueTo="360"
android:valueType="floatType" >
</objectAnimator>
<set android:ordering="sequentially" >
<objectAnimator
android:duration="1500"
android:propertyName="alpha"
android:valueFrom="1"
android:valueTo="0"
android:valueType="floatType" >
</objectAnimator>
<objectAnimator
android:duration="1500"
android:propertyName="alpha"
android:valueFrom="0"
android:valueTo="1"
android:valueType="floatType" >
</objectAnimator>
</set>
</set>
</set>
這段XML實現的效果和我們剛才通過代碼來實現的組合動畫的效果是一模一樣的。
在代碼中加載XML文件-AnimatorInflater
在代碼中把文件加載進來并將動畫啟動:
Animator animator = AnimatorInflater.loadAnimator(context, R.animator.anim_file);
animator.setTarget(view);
animator.start();
調用AnimatorInflater的loadAnimator來將XML動畫文件加載進來,然后再調用setTarget()方法將這個動畫設置到某一個對象上面,最后再調用start()方法啟動動畫就可以了。
ValueAnimator的高級用法
目標:通過對對象進行值操作來實現動畫效果
實現目標
比如說我們有一個自定義的View,在這個View當中有一個Point對象用于管理坐標,然后在onDraw()方法當中就是根據這個Point對象的坐標值來進行繪制的。也就是說,如果我們可以對Point對象進行動畫操作,那么整個自定義View的動畫效果就有了。
TypeEvaluator
簡單來說,就是告訴動畫系統如何從初始值過度到結束值。
ValueAnimator.ofFloat()方法就是實現了初始值與結束值之間的平滑過度,那么這個平滑過度是怎么做到的呢?
其實就是系統內置了一個FloatEvaluator,它通過計算告知動畫系統如何從初始值過度到結束值,我們來看一下FloatEvaluator的代碼實現:
public class FloatEvaluator implements TypeEvaluator {
public Object evaluate(float fraction, Object startValue, Object endValue) {
float startFloat = ((Number) startValue).floatValue();
return startFloat + fraction * (((Number) endValue).floatValue() - startFloat);
}
}
FloatEvaluator實現了TypeEvaluator接口,然后重寫evaluate()方法。
evaluate()方法當中傳入了三個參數:
參數1:fraction用于表示動畫的完成度的,我們應該根據它來計算當前動畫的值應該是多少,
參數2、3:分別表示動畫的初始值和結束值。
代碼邏輯:用結束值減去初始值,算出它們之間的差值,然后乘以fraction這個系數,再加上初始值,那么就得到當前動畫的值了。
ValueAnimator的ofFloat()和ofInt()方法,分別用于對浮點型和整型的數據進行動畫操作的,ValueAnimator中還有一個ofObject()方法,是用于對任意對象進行動畫操作的。
但是相比于浮點型或整型數據,對象的動畫操作明顯要更復雜一些,因為系統將完全無法知道如何從初始對象過度到結束對象,因此這個時候我們就需要實現一個自己的TypeEvaluator來告知系統如何進行過度。
實現步驟
先定義一個Point類:
Point類非常簡單,只有x和y兩個變量用于記錄坐標的位置,并提供了構造方法來設置坐標,以及get方法來獲取坐標。
public class Point {
private float x;
private float y;
public Point(float x, float y) {
this.x = x;
this.y = y;
}
public float getX() {
return x;
}
public float getY() {
return y;
}
}
自定義TypeEvaluator:
實現TypeEvaluator接口并重寫了evaluate()方法:先是將startValue和endValue強轉成Point對象,然后同樣根據fraction來計算當前動畫的x和y的值,最后組裝到一個新的Point對象當中并返回。
public class PointEvaluator implements TypeEvaluator{
@Override
public Object evaluate(float fraction, Object startValue, Object endValue) {
Point startPoint = (Point) startValue;
Point endPoint = (Point) endValue;
float x = startPoint.getX() + fraction * (endPoint.getX() - startPoint.getX());
float y = startPoint.getY() + fraction * (endPoint.getY() - startPoint.getY());
Point point = new Point(x, y);
return point;
}
}
這樣我們就將PointEvaluator編寫完成了,接下來我們就可以非常輕松地對Point對象進行動畫操作了,比如說我們有兩個Point對象,現在需要將Point1通過動畫平滑過度到Point2,就可以這樣寫:
需要注意的是,ofObject()方法要求多傳入一個TypeEvaluator參數,這里我們只需要傳入剛才定義好的PointEvaluator的實例就可以了。
Point point1 = new Point(0, 0);
Point point2 = new Point(300, 300);
ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(), point1, point2);
anim.setDuration(5000);
anim.start();
好的,這就是自定義TypeEvaluator的全部用法,掌握了這些知識之后,我們就可以來嘗試一下如何通過對Point對象進行動畫操作,從而實現整個自定義View的動畫效果。
自定義MyAnimView:
首先在自定義View的構造方法當中初始化了一個Paint對象作為畫筆,并將畫筆顏色設置為藍色,接著在onDraw()方法當中進行繪制。
這里我們繪制的邏輯是由currentPoint這個對象控制的,如果currentPoint對象不等于空,那么就調用drawCircle()方法在currentPoint的坐標位置畫出一個半徑為50的圓,如果currentPoint對象是空,那么就調用startAnimation()方法來啟動動畫。
public class MyAnimView extends View {
public static final float RADIUS = 50f;
private Point currentPoint;
private Paint mPaint;
public MyAnimView(Context context, AttributeSet attrs) {
super(context, attrs);
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.BLUE);
}
@Override
protected void onDraw(Canvas canvas) {
if (currentPoint == null) {
currentPoint = new Point(RADIUS, RADIUS);
drawCircle(canvas);
startAnimation();
} else {
drawCircle(canvas);
}
}
private void drawCircle(Canvas canvas) {
float x = currentPoint.getX();
float y = currentPoint.getY();
canvas.drawCircle(x, y, RADIUS, mPaint);
}
private void startAnimation() {
Point startPoint = new Point(RADIUS, RADIUS);
Point endPoint = new Point(getWidth() - RADIUS, getHeight() - RADIUS);
ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(), startPoint, endPoint);
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
currentPoint = (Point) animation.getAnimatedValue();
invalidate();
}
});
anim.setDuration(5000);
anim.start();
}
}
startAnimation()方法中的代碼:
就是對Point對象進行了一個動畫操作而已。
這里我們定義了一個startPoint和一個endPoint,坐標分別是View的左上角和右下角,并將動畫的時長設為5秒。然后有一點需要大家注意的,就是我們通過監聽器對動畫的過程進行了監聽,每當Point值有改變的時候都會回調onAnimationUpdate()方法。在這個方法當中,我們對currentPoint對象進行了重新賦值,并調用了invalidate()方法,這樣的話onDraw()方法就會重新調用,并且由于currentPoint對象的坐標已經改變了,那么繪制的位置也會改變,于是一個平移的動畫效果也就實現了。
在布局文件當中引入這個自定義控件:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<com.example.tony.myapplication.MyAnimView
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
ObjectAnimator的高級用法
實現目標
動態改變View的顏色。
實現步驟
自定義MyAnimView屬性
ObjectAnimator內部的工作機制是通過尋找特定屬性的get和set方法,然后通過方法不斷地對值進行改變,從而實現動畫效果的。
因此我們就需要在MyAnimView中定義一個color屬性,并提供它的get和set方法。這里我們可以將color屬性設置為字符串類型,使用#RRGGBB這種格式來表示顏色值,代碼如下所示:
在setColor()方法當中,將畫筆的顏色設置成方法參數傳入的顏色,然后調用了invalidate()方法。即在改變了畫筆顏色之后立即刷新視圖,然后onDraw()方法就會調用。在onDraw()方法當中會根據當前畫筆的顏色來進行繪制,這樣顏色也就會動態進行改變了。
public class MyAnimView extends View {
...
private String color;
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
mPaint.setColor(Color.parseColor(color));
invalidate();
}
...
}
自定義TypeEvaluator:
要借助ObjectAnimator類讓setColor()方法得到調用了,在使用ObjectAnimator之前我們還要完成一個非常重要的工作,就是編寫一個用于告知系統如何進行顏色過度的TypeEvaluator。
創建ColorEvaluator并實現TypeEvaluator接口,代碼如下所示:
首先在evaluate()方法當中獲取到顏色的初始值和結束值,并通過字符串截取的方式將顏色分為RGB三個部分,并將RGB的值轉換成十進制數字,那么每個顏色的取值范圍就是0-255。接下來計算一下初始顏色值到結束顏色值之間的差值,這個差值很重要,決定著顏色變化的快慢,如果初始顏色值和結束顏色值很相近,那么顏色變化就會比較緩慢,而如果顏色值相差很大,比如說從黑到白,那么就要經歷255*3這個幅度的顏色過度,變化就會非常快。
那么控制顏色變化的速度是通過getCurrentColor()這個方法來實現的,這個方法會根據當前的fraction值來計算目前應該過度到什么顏色,并且這里會根據初始和結束的顏色差值來控制變化速度,最終將計算出的顏色進行返回。
最后,由于我們計算出的顏色是十進制數字,這里還需要調用一下getHexString()方法把它們轉換成十六進制字符串,再將RGB顏色拼裝起來之后作為最終的結果返回。
public class ColorEvaluator implements TypeEvaluator {
private int mCurrentRed = -1;
private int mCurrentGreen = -1;
private int mCurrentBlue = -1;
@Override
public Object evaluate(float fraction, Object startValue, Object endValue) {
String startColor = (String) startValue;
String endColor = (String) endValue;
int startRed = Integer.parseInt(startColor.substring(1, 3), 16);
int startGreen = Integer.parseInt(startColor.substring(3, 5), 16);
int startBlue = Integer.parseInt(startColor.substring(5, 7), 16);
int endRed = Integer.parseInt(endColor.substring(1, 3), 16);
int endGreen = Integer.parseInt(endColor.substring(3, 5), 16);
int endBlue = Integer.parseInt(endColor.substring(5, 7), 16);
// 初始化顏色的值
if (mCurrentRed == -1) {
mCurrentRed = startRed;
}
if (mCurrentGreen == -1) {
mCurrentGreen = startGreen;
}
if (mCurrentBlue == -1) {
mCurrentBlue = startBlue;
}
// 計算初始顏色和結束顏色之間的差值
int redDiff = Math.abs(startRed - endRed);
int greenDiff = Math.abs(startGreen - endGreen);
int blueDiff = Math.abs(startBlue - endBlue);
int colorDiff = redDiff + greenDiff + blueDiff;
if (mCurrentRed != endRed) {
mCurrentRed = getCurrentColor(startRed, endRed, colorDiff, 0,
fraction);
} else if (mCurrentGreen != endGreen) {
mCurrentGreen = getCurrentColor(startGreen, endGreen, colorDiff,
redDiff, fraction);
} else if (mCurrentBlue != endBlue) {
mCurrentBlue = getCurrentColor(startBlue, endBlue, colorDiff,
redDiff + greenDiff, fraction);
}
// 將計算出的當前顏色的值組裝返回
String currentColor = "#" + getHexString(mCurrentRed)
+ getHexString(mCurrentGreen) + getHexString(mCurrentBlue);
return currentColor;
}
/**
* 根據fraction值來計算當前的顏色。
*/
private int getCurrentColor(int startColor, int endColor, int colorDiff,
int offset, float fraction) {
int currentColor;
if (startColor > endColor) {
currentColor = (int) (startColor - (fraction * colorDiff - offset));
if (currentColor < endColor) {
currentColor = endColor;
}
} else {
currentColor = (int) (startColor + (fraction * colorDiff - offset));
if (currentColor > endColor) {
currentColor = endColor;
}
}
return currentColor;
}
/**
* 將10進制顏色值轉換成16進制。
*/
private String getHexString(int value) {
String hexString = Integer.toHexString(value);
if (hexString.length() == 1) {
hexString = "0" + hexString;
}
return hexString;
}
}
調用
比如說我們想要實現從藍色到紅色的動畫過度,歷時5秒,就可以這樣寫:
ObjectAnimator anim = ObjectAnimator.ofObject(myAnimView, "color", new ColorEvaluator(),
"#0000FF", "#FF0000");
anim.setDuration(5000);
anim.start();
接下來我們需要將上面一段代碼移到MyAnimView類當中,讓它和剛才的Point移動動畫可以結合到一起播放,這就要借助我們在上篇文章當中學到的組合動畫的技術了。修改MyAnimView中的代碼,如下所示:
public class MyAnimView extends View {
...
private void startAnimation() {
Point startPoint = new Point(RADIUS, RADIUS);
Point endPoint = new Point(getWidth() - RADIUS, getHeight() - RADIUS);
ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(), startPoint, endPoint);
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
currentPoint = (Point) animation.getAnimatedValue();
invalidate();
}
});
ObjectAnimator anim2 = ObjectAnimator.ofObject(this, "color", new ColorEvaluator(),
"#0000FF", "#FF0000");
AnimatorSet animSet = new AnimatorSet();
animSet.play(anim).with(anim2);
animSet.setDuration(5000);
animSet.start();
}
}
可以看到,我們并沒有改動太多的代碼,重點只是修改了startAnimation()方法中的部分內容。這里先是將顏色過度的代碼邏輯移動到了startAnimation()方法當中,注意由于這段代碼本身就是在MyAnimView當中執行的,因此ObjectAnimator.ofObject()的第一個參數直接傳this就可以了。接著我們又創建了一個AnimatorSet,并把兩個動畫設置成同時播放,動畫時長為五秒,最后啟動動畫。
Interpolator的用法
Interpolator這個東西很難進行翻譯,直譯過來的話是補間器的意思,它的主要作用是可以控制動畫的變化速率,比如去實現一種非線性運動的動畫效果。那么什么叫做非線性運動的動畫效果呢?就是說動畫改變的速率不是一成不變的,像加速運動以及減速運動都屬于非線性運動。
不過Interpolator并不是屬性動畫中新增的技術,實際上從Android 1.0版本開始就一直存在Interpolator接口了,而之前的補間動畫當然也是支持這個功能的。只不過在屬性動畫中新增了一個TimeInterpolator接口,這個接口是用于兼容之前的Interpolator的,這使得所有過去的Interpolator實現類都可以直接拿過來放到屬性動畫當中使用。
TimeInterpolator接口的所有實現類:
TimeInterpolator接口已經有非常多的實現類了,這些都是Android系統內置好的并且我們可以直接使用的Interpolator。每個Interpolator都有它各自的實現效果,比如說AccelerateInterpolator就是一個加速運動的Interpolator,而DecelerateInterpolator就是一個減速運動的Interpolator。
AccelerateDecelerateInterpolator
上文使用ValueAnimator所打印的值如下所示:
可以看到,一開始的值變化速度明顯比較慢,僅0.0開頭的就打印了4次,之后開始加速,最后階段又開始減速,因此我們可以很明顯地看出這一個先加速后減速的Interpolator。
上文中的小球也是:一開始運動速度比較慢,然后逐漸加速,中間的部分運動速度就比較快,接下來開始減速,最后緩緩停住。另外顏色變化也是這種規律,一開始顏色變化的比較慢,中間顏色變化的很快,最后階段顏色變化的又比較慢。
從以上幾點我們就可以總結出一個結論了,使用屬性動畫時,系統默認的Interpolator其實就是一個先加速后減速的Interpolator,對應的實現類就是AccelerateDecelerateInterpolator。
我們可以很輕松地修改這一默認屬性,將它替換成任意一個系統內置好的Interpolator。
比如,上文MyAnimView中的startAnimation()方法是開啟動畫效果的入口,這里我們對Point對象的坐標稍做一下修改,讓它變成一種垂直掉落的效果,代碼如下所示:
對Point構造函數中的坐標值進行了一下改動,那么現在小球運動的動畫效果應該是從屏幕正中央的頂部掉落到底部。
private void startAnimation() {
Point startPoint = new Point(getWidth() / 2, RADIUS);
Point endPoint = new Point(getWidth() / 2, getHeight() - RADIUS);
ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(), startPoint, endPoint);
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
currentPoint = (Point) animation.getAnimatedValue();
invalidate();
}
});
anim.setDuration(2000);
anim.start();
}
但是默認情況下小球的下降速度肯定是先加速后減速的,我們需要改為下降速度越來越快的。
調用Animator的setInterpolator()方法就可以了,這個方法要求傳入一個實現TimeInterpolator接口的實例,那么比如說我們想要實現小球下降越來越快的效果,就可以使用AccelerateInterpolator,代碼如下所示:
private void startAnimation() {
Point startPoint = new Point(getWidth() / 2, RADIUS);
Point endPoint = new Point(getWidth() / 2, getHeight() - RADIUS);
ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(), startPoint, endPoint);
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
currentPoint = (Point) animation.getAnimatedValue();
invalidate();
}
});
anim.setInterpolator(new AccelerateInterpolator(2f));//實現小球下降越來越快
/*AccelerateInterpolator的構建函數可以接收一個float類型的參數,這個參數是用于控制加速度的*/
anim.setDuration(2500);
anim.start();
}
效果如下:
BounceInterpolator
現在要讓小球撞擊到地面之后應該要反彈起來,然后再次落下,接著再反彈起來,又再次落下,以此反復,最后靜止。這個功能我們當然可以自己去寫,只不過比較復雜,所幸的是,Android系統中已經提供好了這樣一種Interpolator,我們只需要簡單地替換一下就可以完成上面的描述的效果,代碼如下所示:
將設置的Interpolator換成了BounceInterpolator的實例,而BounceInterpolator就是一種可以模擬物理規律,實現反復彈起效果的Interpolator。
private void startAnimation() {
Point startPoint = new Point(getWidth() / 2, RADIUS);
Point endPoint = new Point(getWidth() / 2, getHeight() - RADIUS);
ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(), startPoint, endPoint);
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
currentPoint = (Point) animation.getAnimatedValue();
invalidate();
}
});
anim.setInterpolator(new BounceInterpolator());
anim.setDuration(3000);
anim.start();
}
效果如下:
Interpolator內部實現機制
首先看一下TimeInterpolator的接口定義,代碼如下所示:
/**
* A time interpolator defines the rate of change of an animation. This allows animations
* to have non-linear motion, such as acceleration and deceleration.
*/
public interface TimeInterpolator {
/**
* Maps a value representing the elapsed fraction of an animation to a value that represents
* the interpolated fraction. This interpolated value is then multiplied by the change in
* value of an animation to derive the animated value at the current elapsed animation time.
*
* @param input A value between 0 and 1.0 indicating our current point
* in the animation where 0 represents the start and 1.0 represents
* the end
* @return The interpolation value. This value can be more than 1.0 for
* interpolators which overshoot their targets, or less than 0 for
* interpolators that undershoot their targets.
*/
float getInterpolation(float input);
}
只有一個getInterpolation()方法。
getInterpolation()方法中接收一個input參數,這個參數的值會隨著動畫的運行而不斷變化,不過它的變化是非常有規律的,就是根據設定的動畫時長勻速增加,變化范圍是0到1。也就是說當動畫一開始的時候input的值是0,到動畫結束的時候input的值是1,而中間的值則是隨著動畫運行的時長在0到1之間變化的。
input的值決定了上文中fraction的值。
input的值是由系統經過計算后傳入到getInterpolation()方法中的,然后我們可以自己實現getInterpolation()方法中的算法,根據input的值來計算出一個返回值,而這個返回值就是fraction了。
因此,最簡單的情況就是input值和fraction值是相同的,這種情況由于input值是勻速增加的,因而fraction的值也是勻速增加的,所以動畫的運動情況也是勻速的。
系統中內置的LinearInterpolator就是一種勻速運動的Interpolator,那么我們來看一下它的源碼是怎么實現的:
/**
* An interpolator where the rate of change is constant
*/
@HasNativeInterpolator
public class LinearInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
public LinearInterpolator() {
}
public LinearInterpolator(Context context, AttributeSet attrs) {
}
public float getInterpolation(float input) {
return input; //把參數中傳遞的input值直接返回了,因此fraction的值就是等于input的值的,這就是勻速運動的Interpolator的實現方式。
}
/** @hide */
@Override
public long createNativeInterpolator() {
return NativeInterpolatorFactoryHelper.createLinearInterpolator();
}
}
當然這是最簡單的一種Interpolator的實現了,我們再來看一個稍微復雜一點的。既然現在大家都知道了系統在默認情況下使用的是AccelerateDecelerateInterpolator,那我們就來看一下它的源碼吧,如下所示:
/**
* An interpolator where the rate of change starts and ends slowly but
* accelerates through the middle.
*
*/
@HasNativeInterpolator
public class AccelerateDecelerateInterpolator implements Interpolator, NativeInterpolatorFactory {
public AccelerateDecelerateInterpolator() {
}
@SuppressWarnings({"UnusedDeclaration"})
public AccelerateDecelerateInterpolator(Context context, AttributeSet attrs) {
}
public float getInterpolation(float input) {
return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;
/*算法中主要使用了余弦函數,由于input的取值范圍是0到1,那么cos函數中的取值范圍就是π到2π。而cos(π)的結果是-1,cos(2π)的結果是1,那么這個值再除以2加上0.5之后,getInterpolation()方法最終返回的結果值還是在0到1之間。只不過經過了余弦運算之后,最終的結果不再是勻速增加的了,而是經歷了一個先加速后減速的過程。*/
}
/** @hide */
@Override
public long createNativeInterpolator() {
return NativeInterpolatorFactoryHelper.createAccelerateDecelerateInterpolator();
}
}
getInterpolation()方法中的邏輯變復雜了,我們可以將這個算法的執行情況通過曲線圖的方式繪制出來,結果如下圖所示:
可以看到,這是一個S型的曲線圖,當橫坐標從0變化到0.2的時候,縱坐標的變化幅度很小,但是之后就開始明顯加速,最后橫坐標從0.8變化到1的時候,縱坐標的變化幅度又變得很小。
自定義Interpolator
實現:先減速后加速Interpolator
新建DecelerateAccelerateInterpolator類,讓它實現TimeInterpolator接口,代碼如下所示:
public class DecelerateAccelerateInterpolator implements TimeInterpolator{
@Override
public float getInterpolation(float input) {
float result;
if (input <= 0.5) {
result = (float) (Math.sin(Math.PI * input)) / 2;
} else {
result = (float) (2 - Math.sin(Math.PI * input)) / 2;
}
return result;
}
}
這段代碼是使用正弦函數來實現先減速后加速的功能的,因為正弦函數初始弧度的變化值非常大,剛好和余弦函數是相反的,而隨著弧度的增加,正弦函數的變化值也會逐漸變小,這樣也就實現了減速的效果。當弧度大于π/2之后,整個過程相反了過來,現在正弦函數的弧度變化值非常小,漸漸隨著弧度繼續增加,變化值越來越大,弧度到π時結束,這樣從0過度到π,也就實現了先減速后加速的效果。
同樣我們可以將這個算法的執行情況通過曲線圖的方式繪制出來,結果如下圖所示:
可以看到,這也是一個S型的曲線圖,只不過曲線的方向和剛才是相反的。從上圖中我們可以很清楚地看出來,一開始縱坐標的變化幅度很大,然后逐漸變小,橫坐標到0.5的時候縱坐標變化幅度趨近于零,之后隨著橫坐標繼續增加縱坐標的變化幅度又開始變大,的確是先減速后加速的效果。
那么現在我們將DecelerateAccelerateInterpolator在代碼中進行替換,如下所示:
private void startAnimation() {
Point startPoint = new Point(getWidth() / 2, RADIUS);
Point endPoint = new Point(getWidth() / 2, getHeight() - RADIUS);
ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(), startPoint, endPoint);
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
currentPoint = (Point) animation.getAnimatedValue();
invalidate();
}
});
anim.setInterpolator(new DecelerateAccelerateInterpolator());//替換成自定義Interpolator
anim.setDuration(3000);
anim.start();
}
非常簡單,就是將DecelerateAccelerateInterpolator的實例傳入到setInterpolator()方法當中。重新運行一下代碼,效果如下圖所示:
ViewPropertyAnimator
它并不是在3.0系統當中引入的,而是在3.1系統當中附增的一個新的功能。為View的動畫操作提供一種更加便捷的用法。
屬性動畫的機制已經不是再針對于View而進行設計的了,而是一種不斷地對值進行操作的機制,它可以將值賦值到指定對象的指定屬性上。但是,在絕大多數情況下,我相信大家主要都還是對View進行動畫操作的。
那我們先來回顧一下之前的用法吧,比如我們想要讓一個TextView從常規狀態變成透明狀態,就可以這樣寫:
ObjectAnimator animator = ObjectAnimator.ofFloat(textview, "alpha", 0f);
animator.start();
用法
使用ViewPropertyAnimator來實現同樣的效果,ViewPropertyAnimator提供了更加易懂、更加面向對象的API,如下所示:
textview.animate().alpha(0f); //將當前的textview變成透明狀態
animate()方法就是在Android 3.1系統上新增的一個方法,這個方法的返回值是一個ViewPropertyAnimator對象,也就是說拿到這個對象之后我們就可以調用它的各種方法來實現動畫效果了,這里我們調用了alpha()方法并轉入0,表示將當前的textview變成透明狀態。
ViewPropertyAnimator還可以很輕松地將多個動畫組合到一起,比如我們想要讓textview運動到500,500這個坐標點上,就可以這樣寫:
textview.animate().x(500).y(500); //讓textview運動到500,500這個坐標點
ViewPropertyAnimator是支持連綴用法的,我們想讓textview移動到橫坐標500這個位置上時調用了x(500)這個方法,然后讓textview移動到縱坐標500這個位置上時調用了y(500)這個方法,將所有想要組合的動畫通過這種連綴的方式拼接起來,這樣全部動畫就都會一起被執行。
設定動畫的運行時長:
textview.animate().x(500).y(500).setDuration(5000); //設定動畫的運行時長
Interpolator也可以應用在ViewPropertyAnimator上面:
textview.animate().x(500).y(500).setDuration(5000)
.setInterpolator(new BounceInterpolator()); //使用Interpolator
ViewPropertyAnimator用法基本大同小異,需要用到什么功能就連綴一下,因此更多的用法大家只需要去查閱一下文檔,看看還支持哪些功能,有哪些接口可以調用就可以了。
注意
整個ViewPropertyAnimator的功能都是建立在View類新增的animate()方法之上的,這個方法會創建并返回一個ViewPropertyAnimator的實例,之后的調用的所有方法,設置的所有屬性都是通過這個實例完成的。
在使用ViewPropertyAnimator時,我們自始至終沒有調用過start()方法,這是因為新的接口中使用了隱式啟動動畫的功能,只要我們將動畫定義完成之后,動畫就會自動啟動。并且這個機制對于組合動畫也同樣有效,只要我們不斷地連綴新的方法,那么動畫就不會立刻執行,等到所有在ViewPropertyAnimator上設置的方法都執行完畢后,動畫就會自動啟動。當然如果不想使用這一默認機制的話,我們也可以顯式地調用start()方法來啟動動畫。
ViewPropertyAnimator的所有接口都是使用連綴的語法來設計的,每個方法的返回值都是它自身的實例,因此調用完一個方法之后可以直接連綴調用它的另一個方法,這樣把所有的功能都串接起來,我們甚至可以僅通過一行代碼就完成任意復雜度的動畫功能。
動畫常見問題
修改 Activity 進入和退出動畫
可以通過兩種方式,一是通過定義 Activity 的主題,二是通過覆寫 Activity 的 overridePendingTransition 方法。
方式1:通過設置主題樣式
在 styles.xml 中編輯如下代碼:
<style name="AnimationActivity" parent="@android:style/Animation.Activity">
<item name="android:activityOpenEnterAnimation">@anim/slide_in_left</item>
<item name="android:activityOpenExitAnimation">@anim/slide_out_left</item>
<item name="android:activityCloseEnterAnimation">@anim/slide_in_right</item>
<item name="android:activityCloseExitAnimation">@anim/slide_out_right</item>
</style>
添加 themes.xml 文件:
<style name="ThemeActivity">
<item name="android:windowAnimationStyle">@style/AnimationActivity</item>
<item name="android:windowNoTitle">true</item>
</style>
在 AndroidManifest.xml 中給指定的 Activity 指定 theme。
方式2:覆寫 overridePendingTransition 方法
overridePendingTransition(R.anim.fade, R.anim.hold);
屬性動畫,例如一個 button 從 A 移動到 B 點,B 點還是可以響應點擊事件,這個原理是什么?
補間動畫只是顯示的位置變動,View 的實際位置未改變,表現為 View 移動到其他地方,點擊事件仍在原處才能響應。而屬性動畫控件移動后事件相應就在控件移動后本身進行處理。
引用:
Android屬性動畫完全解析(上),初識屬性動畫的基本用法
Android屬性動畫完全解析(中),ValueAnimator和ObjectAnimator的高級用法
Android屬性動畫完全解析(下),Interpolator和ViewPropertyAnimator的用法