Android框架提供了兩種類型的動畫:View Animation(也稱視圖動畫)和Property Animation (也稱屬性動畫),Property Animation只能在Android 3.0即以上版本才能使用。View Animation則不受版本限制,但是優(yōu)先考慮使用Property Animation 。除此之外可以利用Drawable Animation(也稱幀動畫)實現(xiàn)一幀一幀地顯示資源文件(多為圖片)來展示動畫。
1、View Animation
1.1、View Animation分類
View Animation通過在視圖上面執(zhí)行補間動畫(tweened animation)生成動畫效果,補間動畫變換可以變換視圖對象的位置、大小、旋轉(zhuǎn)角度、透明度。Android的animation package提供了使用補間動畫所有的類。目前View動畫提供了平移動畫、縮放動畫、旋轉(zhuǎn)動畫、透明度動畫。各自對應(yīng)的類和相關(guān)標(biāo)簽如下表所示:
名 稱 | 標(biāo) 簽 | 子 類 | 效 果 |
---|---|---|---|
平移動畫 | <translate> | TranslateAnimation | 移動View |
縮放動畫 | <scale> | ScaleAnimation | 縮放View |
旋轉(zhuǎn)動畫 | <rotate> | RotateAnimation | 旋轉(zhuǎn)View |
透明度動畫 | alpha | AlphaAnimation | 改變View的透明度 |
既可以在代碼中定義動畫,也可以在XML中配置,但優(yōu)先采用在XML配置的方式,因為復(fù)用性、可讀性更高。既可以順序播放動畫,也可同時播放動畫。如果采用XML配置的方式,需要將XML文件保存到Android項目的res/anim/
目錄下面,XML只能有一個根元素,XML只能為單個的<alpha>
、<scale>
、<translate>
、<rotate>
、插值元素或者組織多個動畫的<set>
標(biāo)簽(內(nèi)部還可以嵌套其他<set>
標(biāo)簽).下面是XML中配置示例,具體語法說明參見Animation Resources.:
<set android:shareInterpolator="false">
<scale
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
android:fromXScale="1.0"
android:toXScale="1.4"
android:fromYScale="1.0"
android:toYScale="0.6"
android:pivotX="50%"
android:pivotY="50%"
android:fillAfter="false"
android:duration="700" />
<set android:interpolator="@android:anim/decelerate_interpolator">
<scale
android:fromXScale="1.4"
android:toXScale="0.0"
android:fromYScale="0.6"
android:toYScale="0.0"
android:pivotX="50%"
android:pivotY="50%"
android:startOffset="700"
android:duration="400"
android:fillBefore="false" />
<rotate
android:fromDegrees="0"
android:toDegrees="-45"
android:toYScale="0.0"
android:pivotX="50%"
android:pivotY="50%"
android:startOffset="700"
android:duration="400" />
</set>
</set>
假設(shè)文件在res/anim/
目錄下保存為hyperspace_jump.xml
,如果要想將其用在一個ImageView對象上面,下面是代碼示例:
ImageView spaceshipImage = (ImageView) findViewById(R.id.spaceshipImage);
Animation hyperspaceJumpAnimation = AnimationUtils.loadAnimation(this, R.anim.hyperspace_jump);
spaceshipImage.startAnimation(hyperspaceJumpAnimation);
如果是用硬編碼的方式創(chuàng)建動畫,如下所示:
ImageView spaceshipImage = (ImageView) findViewById(R.id.spaceshipImage);
AlphaAnimation animation=new AlphaAnimation(0,1);
animation.setDuration(300);
spaceshipImage.startAnimation(animation);
除了使用startAnimation()
啟動動畫外,你也可以用Animation.setStartTime()設(shè)置動畫的啟動時間,然后用View.setAnimation()將動畫注冊到View上面。
注意:.不管動畫如何移動或者調(diào)整,View的邊界域不會自動地調(diào)整以便將動畫包含在View邊界域內(nèi)。即便如此,當(dāng)動畫超出了View的邊界域時仍然會繪制而不會發(fā)生剪切,但是如果超出了容納該View的父容器View的邊界域時候就會出現(xiàn)剪的現(xiàn)象
1.2、自定義View Animation
自定義動畫要繼承Animation,然后重寫它的[initialize](http://developer.android.com/reference/android/view/animation/Animation.html#initialize(int, int, int, int))和[applyTransformation](http://developer.android.com/reference/android/view/animation/Animation.html#applyTransformation(float, android.view.animation.Transformation))方法。在initialize中做一些初始化工作,比如Camera,然后在[applyTransformation](http://developer.android.com/reference/android/view/animation/Animation.html#applyTransformation(float, android.view.animation.Transformation))方法中獲取變換矩陣做相應(yīng)的變換。參考ApiDemos中Rotate3dAnimation示例。
2、Property Animation
屬性動畫的強大之處在于它可以給所有的對象而不僅僅是View對象設(shè)置動畫。屬性動畫通過在特定的時間段內(nèi)改變對象的某個屬性(需要為所添加動畫的對象的該屬性提供setter方法,getter方法則是可選),可以設(shè)置動畫的持續(xù)時間(Duration)、動畫在持續(xù)時間段內(nèi)的屬性改變規(guī)則(Time interpolation時間插值)、動畫的重復(fù)次數(shù)、動畫結(jié)束之后是否倒放、動畫組合(順序播放一組動畫或者同時播放一組動畫)、幀刷新的頻率(默認每隔10ms刷新一次幀,設(shè)置刷新頻率不一定就會按照所指定的頻率來,取決于當(dāng)前系統(tǒng)負載的任務(wù)數(shù)目、底層計時器有多快等)
2.1、屬性動畫的工作原理
屬性動畫根據(jù)動畫流逝時間的百分比來計算出當(dāng)前的屬性值的改變的百分比,然后根據(jù)計算出來的屬性值改變百分比、初值和終值來計算當(dāng)前的當(dāng)前時間點的屬性值(調(diào)用所添加的對象的某個屬性的set方法)。
如上圖所示,ValueAnimator通過封裝定義動畫插值的TimeInterpolator以及定義如何計算添加了動畫的屬性的值TypeEvaluator。
要想啟動一個動畫,需要創(chuàng)建ValueAnimator,并設(shè)置初值、終值、動畫的持續(xù)時間。調(diào)用start()方法啟動。在整個動畫的執(zhí)行過程中,ValueAnimator會計算時間流逝率( elapsed fraction ),也就是動畫已經(jīng)消耗的時間所占總持續(xù)時間的百分比,0表示0%,1表示100%。以上面的線性動畫示例圖為例,當(dāng) t = 10 ms
時,因為總時間為40ms
,流逝率為fraction=(10-40)/40=0.25
。當(dāng)其計算出時間流逝率fraction
后,會將該它傳入當(dāng)前的ValueAnimator所設(shè)置的TimeInterpolator(默認是LinearInterpolator)的getInterpolation方法,該方法只需一個參數(shù),對傳入的fraction
通過某種算法生成一個插值率(interpolated fraction),不同的TimeInterpolator會有不同的實現(xiàn),因此得到的插值率會不一樣,對于LinearInterpolator,得到的插值率就是傳入的流逝率,所以所以屬性的變化會隨著時間的變化是線性關(guān)系而,對于下面的圖,采用的是AccelerateDecelerateInterpolator,也就是先加速后減速的時間插值,在 t = 10 ms
時雖然時間流失率為 0.25
,但是AccelerateDecelerateInterpolator的getInterpolation方法實現(xiàn)如下所示:
public float getInterpolation(float fraction) {
return (float)(Math.cos((fraction+ 1) * Math.PI) / 2.0f) + 0.5f;
}
當(dāng)fraction = 0.25
,返回的值約為0.1464
,顯然小于傳入的0.25
,最后將getInterpolation方法的返回值、初值、終值帶入相應(yīng)的TypeEvaluator的[evaluate](http://developer.android.com/reference/android/animation/TypeEvaluator.html#evaluate(float, T, T))方法得到此時的屬性值,例如IntEvaluator的[evaluate](http://developer.android.com/reference/android/animation/TypeEvaluator.html#evaluate(float, T, T))的實現(xiàn)為:
public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
int startInt = startValue;
return (int)(startInt + fraction * (endValue - startInt));
}
因此當(dāng) t = 10 ms
時,對于線性動畫,X=(int)(0+0.25x(40-0))=10
,對于非線性動畫X=(int)(0+0.1464x(40-0))=6
,如果是ObjectAnimator,還會在添加動畫的對象上通過反射調(diào)用修改屬性的set方法。
2.2、Property Animation與View Animation的不同之處
Property Animation不僅僅可以對view對象添加動畫,還能對非view對象添加動畫,而View Animation如果要對非View對象添加動畫,需要自己實現(xiàn)比較麻煩,并且view Animation所能操縱的屬性也很少只能對view的縮放、旋轉(zhuǎn)、平移設(shè)置動畫,比如不能修改背景色。view animation只是修改了view所繪制的地方,但并沒有修改view本身,比如對一個按鈕添加平移動畫,按鈕仍然處在原來的地方,Property Animation則消除了這些限制。可以對view和非View對象添加動畫,并且在動畫播放過程中修改自身。提供了插值(參見2.9)、多動畫功能(參見2.5)。
2.3、使用ValueAnimator設(shè)置動畫
ValueAnimator提供了ofInt()、 ofFloat()和 [ofObject()](https://developer.android.com/reference/android/animation/ValueAnimator.html#ofObject(android.animation.TypeEvaluator, java.lang.Object...))三中工廠方法來創(chuàng)建對象,ofInt()與 ofFloat()方法類似,前者傳入整數(shù)序列,后者傳入浮點數(shù)序列,而 [ofObject()](https://developer.android.com/reference/android/animation/ValueAnimator.html#ofObject(android.animation.TypeEvaluator, java.lang.Object...))是用于自定義類型。
ofFloat()方法示例:
ValueAnimator animation = ValueAnimator.ofFloat(0f, 1f);
animation.setDuration(1000);
animation.start();
[ofObject()](https://developer.android.com/reference/android/animation/ValueAnimator.html#ofObject(android.animation.TypeEvaluator, java.lang.Object...))方法示例,其中MyTypeEvaluator需要實現(xiàn) TypeEvaluator,參見2.8:
ValueAnimator animation = ValueAnimator.ofObject(new MyTypeEvaluator(), startPropertyValue, endPropertyValue);
animation.setDuration(1000);
animation.start();
由于上面兩處示例代碼并沒有作用到某個對象上,因此該動畫并沒有效果,要想動畫作用到某對象上上,需要添加動畫監(jiān)聽器,調(diào)用getAnimatedValue()方法獲取刷新到某個特定幀時計算所得屬性值,然后做相應(yīng)的邏輯處理。
2.4、使用ObjectAnimator設(shè)置動畫
ObjectAnimator是ValueAnimator的子類,它內(nèi)部封裝了計時引擎和ValueAnimator的值計算,可以自動更新設(shè)置動畫的某個屬性而無需實現(xiàn)ValueAnimator.AnimatorUpdateListener的onAnimationUpdate()方法獲取每一幀的屬性值來手動刷新,參見2.6節(jié),從而讓代碼更加簡潔,如下所示:
ObjectAnimator anim = ObjectAnimator.ofFloat(foo, "alpha", 0f, 1f);
anim.setDuration(1000);
anim.start();
注意
- ObjectAnimator能夠自動更新屬性值,實現(xiàn)原理最終是通過反射調(diào)用在某個屬性上的setter方法。因此當(dāng)對此某個對象某個屬性使用ObjectAnimator設(shè)置動畫的時候一定要確保該屬性有setter(setXxx()格式)方法。如果沒有對應(yīng)的setter方法,如果有權(quán)限,添加相關(guān)屬性的setter方法,如果沒有添加該屬性setter方法的權(quán)限,則使用一個包裝類,或者改用ValueAnimator
- 如果在調(diào)用ObjectAnimator的工廠方法(比如
ofFloat()
)方法在參數(shù)values...
上只傳入了一個值,則只會將該值作為終值,因此在設(shè)置動畫的某個屬性需要有g(shù)etter方法(getXxx()格式),因為需要通過getter方法獲取初值。 - 屬性的setter和getter方法操作的屬性值必須是同一數(shù)據(jù)類型作為傳入 ObjectAnimator的
values...
參數(shù)的初值和終值,比如當(dāng)采用的是
ObjectAnimator.ofFloat(targetObject, "propName", 1f)
則屬性的setter和getter參數(shù)必須是float
類型.
- 有些屬性需要手動調(diào)用invalidate()刷新值。比如
Drawable
對象的顏色值。參見2.6
2.5、使用AnimatorSet同時設(shè)置多種動畫
使用AnimatorSet類可以實現(xiàn)同時播放、順序播放、延時播放多個動畫。下面的代碼是Android SDK中模擬小球落體壓扁后有反彈起來一段距離,然后消失的動畫,先播放bounceAnim
,然后同時播放squashAnim1
、squashAnim2
、stretchAnim1
、stretchAnim2
。接著播放bounceBackAnim
,最后播放fadeAnim
。所播放的動畫均為ValueAnimator類型
AnimatorSet bouncer = new AnimatorSet();
bouncer.play(bounceAnim).before(squashAnim1);
bouncer.play(squashAnim1).with(squashAnim2);
bouncer.play(squashAnim1).with(stretchAnim1);
bouncer.play(squashAnim1).with(stretchAnim2);
bouncer.play(bounceBackAnim).after(stretchAnim2);
ValueAnimator fadeAnim = ObjectAnimator.ofFloat(newBall, "alpha", 1f, 0f);
fadeAnim.setDuration(250);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.play(bouncer).before(fadeAnim);
animatorSet.start();
2.6、設(shè)置動畫監(jiān)聽器
Android提供了監(jiān)聽動畫播放事件的接口Animator.AnimatorListener和ValueAnimator.AnimatorUpdateListener。下面是各自的源代碼:
Animator.AnimatorListener源碼:
/**
* This is the superclass for classes which provide basic support for animations which can be
* started, ended, and have <code>AnimatorListeners</code> added to them.
*/
public abstract class Animator implements Cloneable {
......
/**
* <p>An animation listener receives notifications from an animation.
* Notifications indicate animation related events, such as the end or the
* repetition of the animation.</p>
*/
public static interface AnimatorListener {
/**
* <p>Notifies the start of the animation.</p>
*
* @param animation The started animation.
*/
void onAnimationStart(Animator animation);
/**
* <p>Notifies the end of the animation. This callback is not invoked
* for animations with repeat count set to INFINITE.</p>
*
* @param animation The animation which reached its end.
*/
void onAnimationEnd(Animator animation);
/**
* <p>Notifies the cancellation of the animation. This callback is not invoked
* for animations with repeat count set to INFINITE.</p>
*
* @param animation The animation which was canceled.
*/
void onAnimationCancel(Animator animation);
/**
* <p>Notifies the repetition of the animation.</p>
*
* @param animation The animation which was repeated.
*/
void onAnimationRepeat(Animator animation);
}
......
}
ValueAnimator.AnimatorUpdateListener源碼
public class ValueAnimator extends Animator {
......
/**
* Implementors of this interface can add themselves as update listeners
* to an <code>ValueAnimator</code> instance to receive callbacks on every animation
* frame, after the current frame's values have been calculated for that
* <code>ValueAnimator</code>.
*/
public static interface AnimatorUpdateListener {
/**
* <p>Notifies the occurrence of another frame of the animation.</p>
*
* @param animation The animation which was repeated.
*/
void onAnimationUpdate(ValueAnimator animation);
}
......
}
需要說明的是,對于某些設(shè)置動畫的屬性,需要手動調(diào)用invalidate()方法才能確保將屏幕區(qū)域用動畫得到的新的屬性值重新繪制自己。比如一個Drawable
對象的顏色屬性,然而對于setAlpha()和setTranslationX()則不需要手動調(diào)用invalidate()。
監(jiān)聽示例代碼:
ValueAnimatorAnimator fadeAnim = ObjectAnimator.ofFloat(newBall, "alpha", 1f, 0f);
fadeAnim.setDuration(250);
fadeAnim.addListener(new AnimatorListenerAdapter() {
public void onAnimationEnd(Animator animation) {
balls.remove(((ObjectAnimator)animation).getTarget());
}
2.7、給ViewGroup設(shè)置布局變換時動畫

Android屬性動畫提供了給ViewGroups布局改變時添加動畫的功能。所謂布局改變動畫是指在ViewGroup里的View,當(dāng)你添加或者刪除它、或者調(diào)用它的setVisibility()方法將對應(yīng)的值設(shè)為VISIBLE、GONE、INVISIBLE時候所呈現(xiàn)出的出場動畫、退場動畫效果,當(dāng)對某個ViewGroup內(nèi)部的view做添加或者刪除的時候,還可以給在ViewGroup中的其他view從原來的位置到新的位置添加動畫。
用法:創(chuàng)建一個LayoutTransition對象,然后調(diào)用該對象的[setAnimator](https://developer.android.com/reference/android/animation/LayoutTransition.html#setAnimator(int, android.animation.Animator))(int transitionType, Animator animator),然后在需要添加ViewGroup布局改變動畫的某個ViewGroup對象上調(diào)用setLayoutTransition(LayoutTransition transition)方法。
其中[setAnimator](https://developer.android.com/reference/android/animation/LayoutTransition.html#setAnimator(int, android.animation.Animator))(int transitionType, Animator animator)方法的第一個參數(shù)只能為LayoutTransition類中下面四個靜態(tài)常量。
-
APPEARING
某個View在ViewGroup中出現(xiàn)時的設(shè)置動畫 -
CHANGE_APPEARING
當(dāng)新的View出現(xiàn)在ViewGroup中出現(xiàn)時給已經(jīng)存在于ViewGroup中的其他View設(shè)置動畫 -
DISAPPEARING
某個View在ViewGroup中消失時的設(shè)置動畫 -
CHANGE_DISAPPEARING
當(dāng)某個View從ViewGroup中消失時給ViewGroup中的其他View設(shè)置動畫。
代碼示例如下:
/**
* This application demonstrates how to use LayoutTransition to automate transition animations
* as items are removed from or added to a container.
*/
public class LayoutAnimations extends Activity {
private int numButtons = 1;
ViewGroup container = null;
Animator defaultAppearingAnim, defaultDisappearingAnim;
Animator defaultChangingAppearingAnim, defaultChangingDisappearingAnim;
Animator customAppearingAnim, customDisappearingAnim;
Animator customChangingAppearingAnim, customChangingDisappearingAnim;
Animator currentAppearingAnim, currentDisappearingAnim;
Animator currentChangingAppearingAnim, currentChangingDisappearingAnim;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_animations);
container = new FixedGridLayout(this);
container.setClipChildren(false);
((FixedGridLayout)container).setCellHeight(90);
((FixedGridLayout)container).setCellWidth(100);
final LayoutTransition transitioner = new LayoutTransition();
container.setLayoutTransition(transitioner);
defaultAppearingAnim = transitioner.getAnimator(LayoutTransition.APPEARING);
defaultDisappearingAnim =
transitioner.getAnimator(LayoutTransition.DISAPPEARING);
defaultChangingAppearingAnim =
transitioner.getAnimator(LayoutTransition.CHANGE_APPEARING);
defaultChangingDisappearingAnim =
transitioner.getAnimator(LayoutTransition.CHANGE_DISAPPEARING);
createCustomAnimations(transitioner);
currentAppearingAnim = defaultAppearingAnim;
currentDisappearingAnim = defaultDisappearingAnim;
currentChangingAppearingAnim = defaultChangingAppearingAnim;
currentChangingDisappearingAnim = defaultChangingDisappearingAnim;
ViewGroup parent = (ViewGroup) findViewById(R.id.parent);
parent.addView(container);
parent.setClipChildren(false);
Button addButton = (Button) findViewById(R.id.addNewButton);
addButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
Button newButton = new Button(LayoutAnimations.this);
newButton.setText(String.valueOf(numButtons++));
newButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
container.removeView(v);
}
});
container.addView(newButton, Math.min(1, container.getChildCount()));
}
});
CheckBox customAnimCB = (CheckBox) findViewById(R.id.customAnimCB);
customAnimCB.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
setupTransition(transitioner);
}
});
// Check for disabled animations
CheckBox appearingCB = (CheckBox) findViewById(R.id.appearingCB);
appearingCB.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
setupTransition(transitioner);
}
});
CheckBox disappearingCB = (CheckBox) findViewById(R.id.disappearingCB);
disappearingCB.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
setupTransition(transitioner);
}
});
CheckBox changingAppearingCB = (CheckBox) findViewById(R.id.changingAppearingCB);
changingAppearingCB.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
setupTransition(transitioner);
}
});
CheckBox changingDisappearingCB = (CheckBox) findViewById(R.id.changingDisappearingCB);
changingDisappearingCB.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
setupTransition(transitioner);
}
});
}
private void setupTransition(LayoutTransition transition) {
CheckBox customAnimCB = (CheckBox) findViewById(R.id.customAnimCB);
CheckBox appearingCB = (CheckBox) findViewById(R.id.appearingCB);
CheckBox disappearingCB = (CheckBox) findViewById(R.id.disappearingCB);
CheckBox changingAppearingCB = (CheckBox) findViewById(R.id.changingAppearingCB);
CheckBox changingDisappearingCB = (CheckBox) findViewById(R.id.changingDisappearingCB);
transition.setAnimator(LayoutTransition.APPEARING, appearingCB.isChecked() ?
(customAnimCB.isChecked() ? customAppearingAnim : defaultAppearingAnim) : null);
transition.setAnimator(LayoutTransition.DISAPPEARING, disappearingCB.isChecked() ?
(customAnimCB.isChecked() ? customDisappearingAnim : defaultDisappearingAnim) : null);
transition.setAnimator(LayoutTransition.CHANGE_APPEARING, changingAppearingCB.isChecked() ?
(customAnimCB.isChecked() ? customChangingAppearingAnim :
defaultChangingAppearingAnim) : null);
transition.setAnimator(LayoutTransition.CHANGE_DISAPPEARING,
changingDisappearingCB.isChecked() ?
(customAnimCB.isChecked() ? customChangingDisappearingAnim :
defaultChangingDisappearingAnim) : null);
}
private void createCustomAnimations(LayoutTransition transition) {
// Changing while Adding
PropertyValuesHolder pvhLeft =
PropertyValuesHolder.ofInt("left", 0, 1);
PropertyValuesHolder pvhTop =
PropertyValuesHolder.ofInt("top", 0, 1);
PropertyValuesHolder pvhRight =
PropertyValuesHolder.ofInt("right", 0, 1);
PropertyValuesHolder pvhBottom =
PropertyValuesHolder.ofInt("bottom", 0, 1);
PropertyValuesHolder pvhScaleX =
PropertyValuesHolder.ofFloat("scaleX", 1f, 0f, 1f);
PropertyValuesHolder pvhScaleY =
PropertyValuesHolder.ofFloat("scaleY", 1f, 0f, 1f);
customChangingAppearingAnim = ObjectAnimator.ofPropertyValuesHolder(
this, pvhLeft, pvhTop, pvhRight, pvhBottom, pvhScaleX, pvhScaleY).
setDuration(transition.getDuration(LayoutTransition.CHANGE_APPEARING));
customChangingAppearingAnim.addListener(new AnimatorListenerAdapter() {
public void onAnimationEnd(Animator anim) {
View view = (View) ((ObjectAnimator) anim).getTarget();
view.setScaleX(1f);
view.setScaleY(1f);
}
});
// Changing while Removing
Keyframe kf0 = Keyframe.ofFloat(0f, 0f);
Keyframe kf1 = Keyframe.ofFloat(.9999f, 360f);
Keyframe kf2 = Keyframe.ofFloat(1f, 0f);
PropertyValuesHolder pvhRotation =
PropertyValuesHolder.ofKeyframe("rotation", kf0, kf1, kf2);
customChangingDisappearingAnim = ObjectAnimator.ofPropertyValuesHolder(
this, pvhLeft, pvhTop, pvhRight, pvhBottom, pvhRotation).
setDuration(transition.getDuration(LayoutTransition.CHANGE_DISAPPEARING));
customChangingDisappearingAnim.addListener(new AnimatorListenerAdapter() {
public void onAnimationEnd(Animator anim) {
View view = (View) ((ObjectAnimator) anim).getTarget();
view.setRotation(0f);
}
});
// Adding
customAppearingAnim = ObjectAnimator.ofFloat(null, "rotationY", 90f, 0f).
setDuration(transition.getDuration(LayoutTransition.APPEARING));
customAppearingAnim.addListener(new AnimatorListenerAdapter() {
public void onAnimationEnd(Animator anim) {
View view = (View) ((ObjectAnimator) anim).getTarget();
view.setRotationY(0f);
}
});
// Removing
customDisappearingAnim = ObjectAnimator.ofFloat(null, "rotationX", 0f, 90f).
setDuration(transition.getDuration(LayoutTransition.DISAPPEARING));
customDisappearingAnim.addListener(new AnimatorListenerAdapter() {
public void onAnimationEnd(Animator anim) {
View view = (View) ((ObjectAnimator) anim).getTarget();
view.setRotationX(0f);
}
});
}
}
2.8、使用類型求值器TypeEvaluator
通過實現(xiàn)TypeEvaluator接口可以創(chuàng)建Android系統(tǒng)沒有實現(xiàn)的類型。Android系統(tǒng)已經(jīng)實現(xiàn)的TypeEvaluator的有IntEvaluator, FloatEvaluator和 ArgbEvaluator。只需要 [evaluate()](http://developer.android.com/reference/android/animation/TypeEvaluator.html#evaluate(float, T, T))方法就可以計算出設(shè)置動畫的屬性當(dāng)前時間點的相應(yīng)的值。 FloatEvaluator類的實現(xiàn)如下所示:
public class FloatEvaluator implements TypeEvaluator {
/**
* @param fraction 實現(xiàn)TimeInterpolator接口的某個特定插值器的getInterpolation(float fraction)的返回值
* @param startValue 添加動畫效果的屬性的起始值
* @param endValue 添加動畫效果的屬性的結(jié)束值
* @return 添加動畫效果的屬性在當(dāng)前時間點的值
*/
public Object evaluate(float fraction, Object startValue, Object endValue) {
float startFloat = ((Number) startValue).floatValue();
return startFloat + fraction * (((Number) endValue).floatValue() - startFloat);
}
}
2.9、使用插值器Interpolators
在2.1節(jié)有關(guān)屬性動畫的原理中已經(jīng)說到TimeInterpolator的作用是根據(jù)時間流逝率來計算當(dāng)前屬性的插值率。Android系統(tǒng)內(nèi)置許多插值器如LinearInterpolator(勻速)、AccelerateInterpolator(一直加速)AccelerateDecelerateInterpolator(先加速后減速)、BounceInterpolator(回彈效果)等多種類型,如果要實現(xiàn)自定義類型,需要實現(xiàn)TimeInterpolator接口的getInterpolation方法,下面Android回彈效果插值器BounceInterpolator的源碼:
/**
* An interpolator where the change bounces at the end.
*/
@HasNativeInterpolator
public class BounceInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
public BounceInterpolator() {
}
@SuppressWarnings({"UnusedDeclaration"})
public BounceInterpolator(Context context, AttributeSet attrs) {
}
private static float bounce(float t) {
return t * t * 8.0f;
}
public float getInterpolation(float t) {
// _b(t) = t * t * 8
// bs(t) = _b(t) for t < 0.3535
// bs(t) = _b(t - 0.54719) + 0.7 for t < 0.7408
// bs(t) = _b(t - 0.8526) + 0.9 for t < 0.9644
// bs(t) = _b(t - 1.0435) + 0.95 for t <= 1.0
// b(t) = bs(t * 1.1226)
t *= 1.1226f;
if (t < 0.3535f) return bounce(t);
else if (t < 0.7408f) return bounce(t - 0.54719f) + 0.7f;
else if (t < 0.9644f) return bounce(t - 0.8526f) + 0.9f;
else return bounce(t - 1.0435f) + 0.95f;
}
/** @hide */
@Override
public long createNativeInterpolator() {
return NativeInterpolatorFactoryHelper.createBounceInterpolator();
}
}
看起來BounceInterpolator 并沒有實現(xiàn)TimeInterpolator接口的getInterpolation方法,實際上BaseInterpolator
是一個抽象類,它實現(xiàn)了Interpolator
接口但沒有給出實現(xiàn),而Interpolator
接口又是繼承自TimeInterpolator
的。
如果嫌自定義布局改變動畫比較麻煩,可以使用默認的效果,方法是在某個XML布局中設(shè)置android:animateLayoutchanges
屬性為true
。例如
<LinearLayout
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:id="@+id/verticalContainer"
android:animateLayoutChanges="true" />
現(xiàn)在對上面id
為verticalContainer
的LinearLayout
內(nèi)部的子view做添加、刪除、顯示和隱藏操作等就有布局發(fā)生改變的動畫效果。
2.10、使用Keyframe設(shè)置動畫
Keyframe的作用通過傳入一對值(時間流失率與屬性值)申明動畫在特定時間點的特定狀態(tài)。每一個keyframe 可以有自己的插值器來控制從前一幀到當(dāng)前幀的動畫表現(xiàn)形式。
可以使用Keyframe的ofInt()、ofFloat()或者ofObject()工廠方法來實例化特定的Keyframe對象。然后通過PropertyValuesHolder 的工廠方法 [ofKeyframe()](http://developer.android.com/reference/android/animation/PropertyValuesHolder.html#ofKeyframe(android.util.Property, android.animation.Keyframe...))獲取一個PropertyValuesHolder對象,然和將獲取到的PropertyValuesHolder對象和需要添加動畫的對象傳入ObjectAnimator的工廠方法[ofPropertyValuesHolder](http://developer.android.com/reference/android/animation/ObjectAnimator.html#ofPropertyValuesHolder(java.lang.Object, android.animation.PropertyValuesHolder...))從而獲取一個ObjectAnimator對象.
Keyframe kf0 = Keyframe.ofFloat(0f, 0f);
Keyframe kf1 = Keyframe.ofFloat(.5f, 360f);
Keyframe kf2 = Keyframe.ofFloat(1f, 0f);
PropertyValuesHolder pvhRotation = PropertyValuesHolder.ofKeyframe("rotation", kf0, kf1, kf2);
ObjectAnimator rotationAnim = ObjectAnimator.ofPropertyValuesHolder(target, pvhRotation)
rotationAnim.setDuration(5000ms);
具體示例參考APIDemos里的 MultiPropertyAnimation 。
2.11、使用ViewPropertyAnimator設(shè)置動畫
使用ViewPropertyAnimator的好處是只需要一個底層的Animator對象就可以同時給各多個屬性添加動畫,效果跟于ObjectAnimator一樣,都是通過實際通過修改view的屬性,并且采用流式風(fēng)格的代碼,通過鏈式調(diào)用,代碼更加精簡易讀。下面是同時給一個View的x
和y
屬性添加動畫的三種不同的寫法比較:
- 多個ObjectAnimator對象方式
ObjectAnimator animX = ObjectAnimator.ofFloat(myView, "x", 50f);
ObjectAnimator animY = ObjectAnimator.ofFloat(myView, "y", 100f);
AnimatorSet animSetXY = new AnimatorSet();
animSetXY.playTogether(animX, animY);
animSetXY.start();
- 一個ObjectAnimator對象方式
PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat("x", 50f);
PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat("y", 100f);
ObjectAnimator.ofPropertyValuesHolder(myView, pvhX, pvyY).start();
- ViewPropertyAnimator方式
myView.animate().x(50f).y(100f);
通過比較可以明顯看出ViewPropertyAnimator方式更加精簡易讀。更多有關(guān)ViewPropertyAnimator的內(nèi)容需要參考Android開發(fā)官方博客Introducing ViewPropertyAnimator
2.12、XML聲明方式設(shè)置動畫
為了能夠區(qū)分出View Animation和Property Animation,配置View Animation的XML文件存放在Android項目的res/anim/
目錄下面,而配置Property Animation的XML文件存放在res/animator/
目錄下面。
類 名 稱 | 標(biāo) 簽 |
---|---|
ValueAnimator | <animator> |
ObjectAnimator | <objectAnimator> |
AnimatorSet | <set> |
XML配置動畫集:
<set android:ordering="sequentially">
<set>
<objectAnimator
android:propertyName="x"
android:duration="500"
android:valueTo="400"
android:valueType="intType"/>
<objectAnimator
android:propertyName="y"
android:duration="500"
android:valueTo="300"
android:valueType="intType"/>
</set>
<objectAnimator
android:propertyName="alpha"
android:duration="500"
android:valueTo="1f"/>
</set>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="1000"
android:repeatCount="1"
android:repeatMode="reverse">
<propertyValuesHolder android:propertyName="x" android:valueTo="400"/>
<propertyValuesHolder android:propertyName="y" android:valueTo="200"/>
</objectAnimator>
配置Keyframe
<propertyValuesHolder android:propertyName="x" >
<keyframe android:fraction="0" android:value="800" />
<keyframe android:fraction=".2"
android:interpolator="@android:anim/accelerate_interpolator"
android:value="1000" />
<keyframe android:fraction="1"
android:interpolator="@android:anim/accelerate_interpolator"
android:value="400" />
</propertyValuesHolder>
<propertyValuesHolder android:propertyName="y" >
<keyframe/>
<keyframe android:fraction=".2"
android:interpolator="@android:anim/accelerate_interpolator"
android:value="300"/>
<keyframe android:interpolator="@android:anim/accelerate_interpolator"
android:value="1000" />
</propertyValuesHolder>
加載動畫資源:
AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(myContext, R.anim.property_animator);
set.setTarget(myObject);
set.start();
3、Drawable Animation
Drawable Animation(幀動畫)通過加載一個接一個Drawable資源文件來生成動畫,類似于老式電影通過連續(xù)旋轉(zhuǎn)已經(jīng)拍好的膠片放映電影。雖然可以使用AnimationDrawable來在代碼中創(chuàng)建動畫,但最常用的方式是在你的Android項目的res/drawable/
目錄下的XML文件定義動畫,這個XML文件包含了構(gòu)成該動畫的所有幀以及每一幀的持續(xù)時間。如下所示:
<animation-list
xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="true">
<item android:drawable="@drawable/rocket_thrust1" android:duration="200" />
<item android:drawable="@drawable/rocket_thrust2" android:duration="200" />
<item android:drawable="@drawable/rocket_thrust3" android:duration="200" />
</animation-list>
其中<animation-list>
為該XML的根節(jié)點,android:oneshot
屬性設(shè)為true
表示該動畫只播放一次,并且當(dāng)動畫結(jié)束之后停留在最后一幀,如果為false
則表示動畫在播放完以后會循環(huán)播放。先假設(shè)該上面的XML文件在res/drawable/
目錄下的保存的文件名為rocket_thrust.xml
,在一個Activity的ImageView中添加動畫的實例代碼如下:
AnimationDrawable rocketAnimation;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
ImageView rocketImage = (ImageView) findViewById(R.id.rocket_image);
rocketImage.setBackgroundResource(R.drawable.rocket_thrust);
rocketAnimation = (AnimationDrawable) rocketImage.getBackground();
}
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
rocketAnimation.start();
return true;
}
return super.onTouchEvent(event);
}
注意:不能在Activity的onCreate()
方法中調(diào)用AnimationDrawable 的start()
方法,因為AnimationDrawable 并沒有完全加載到窗口里面來,如果想在沒有交互的情況下(比如點擊某按鈕觸發(fā)動畫播放事件)就播放動畫,在Activity的onWindowFocusChanged()方法中調(diào)用該方法。該方法會在窗口獲取到焦點時被調(diào)用。
4、注意事項
- OOM問題
主要出現(xiàn)在Drawable Animation
中,應(yīng)盡量避免加載數(shù)量較多且較大的圖片,盡量避免使用Drawable Animation
。 - 內(nèi)存泄漏
屬性動畫中的無限循環(huán)動畫需要在Activity
退出的時候及時停止,否則將導(dǎo)致Activity
無法釋放而造成內(nèi)存泄露。View Animation
不存在這個問題。 - 兼容問題
某些動畫在3.0以下系統(tǒng)上有兼容性問題,主要是Property Animation
在Android 3.0以下不能使用。 - View Animation的問題
。View Animation
是對View
的影像做動畫,并不是真正的改變View
的狀態(tài),因此有時候動畫完成之后view無法隱藏,即setVisibility(View.GONE)
失效了,此時需要調(diào)用view.clearAnimation()
清除view動畫才行。 - 不要使用px
盡量使用dp,px在不同的分辨率的手機下面會有不同的效果。 - 動畫交互問題
在android3.0以前的系統(tǒng)上,view動畫和屬性動畫,新位置均無法觸發(fā)點擊事件,同時,老位置仍然可以觸發(fā)單擊事件。從3.0開始,屬性動畫的單擊事件觸發(fā)位置為移動后的位置,view動畫仍然在原位置。 - 動畫交互問題
使用動畫的過程中,建議開啟硬件加速,這樣會提高動畫的流暢性。