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)生成動畫效果绑咱,補間動畫變換可以變換視圖對象的位置绰筛、大小、旋轉角度描融、透明度铝噩。Android的animation package提供了使用補間動畫所有的類。目前View動畫提供了平移動畫窿克、縮放動畫骏庸、旋轉動畫、透明度動畫年叮。各自對應的類和相關標簽如下表所示:
名 稱 | 標 簽 | 子 類 | 效 果 |
---|---|---|---|
平移動畫 | <translate> | TranslateAnimation | 移動View |
縮放動畫 | <scale> | ScaleAnimation | 縮放View |
旋轉動畫 | <rotate> | RotateAnimation | 旋轉View |
透明度動畫 | alpha | AlphaAnimation | 改變View的透明度 |
既可以在代碼中定義動畫具被,也可以在XML中配置,但優(yōu)先采用在XML配置的方式只损,因為復用性一姿、可讀性更高。既可以順序播放動畫跃惫,也可同時播放動畫叮叹。如果采用XML配置的方式,需要將XML文件保存到Android項目的res/anim/
目錄下面爆存,XML只能有一個根元素蛉顽,XML只能為單個的<alpha>
、<scale>
先较、<translate>
蜂林、<rotate>
遥诉、插值元素或者組織多個動畫的<set>
標簽(內部還可以嵌套其他<set>
標簽).下面是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>
假設文件在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()設置動畫的啟動時間睁蕾,然后用View.setAnimation()將動畫注冊到View上面苞笨。
注意:.不管動畫如何移動或者調整,View的邊界域不會自動地調整以便將動畫包含在View邊界域內子眶。即便如此瀑凝,當動畫超出了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))方法中獲取變換矩陣做相應的變換。參考ApiDemos中Rotate3dAnimation示例磁奖。
2囊拜、Property Animation
屬性動畫的強大之處在于它可以給所有的對象而不僅僅是View對象設置動畫。屬性動畫通過在特定的時間段內改變對象的某個屬性(需要為所添加動畫的對象的該屬性提供setter方法,getter方法則是可選)比搭,可以設置動畫的持續(xù)時間(Duration)冠跷、動畫在持續(xù)時間段內的屬性改變規(guī)則(Time interpolation時間插值)、動畫的重復次數(shù)身诺、動畫結束之后是否倒放蜜托、動畫組合(順序播放一組動畫或者同時播放一組動畫)、幀刷新的頻率(默認每隔10ms刷新一次幀霉赡,設置刷新頻率不一定就會按照所指定的頻率來橄务,取決于當前系統(tǒng)負載的任務數(shù)目、底層計時器有多快等)
2.1同廉、屬性動畫的工作原理
屬性動畫根據動畫流逝時間的百分比來計算出當前的屬性值的改變的百分比,然后根據計算出來的屬性值改變百分比、初值和終值來計算當前的當前時間點的屬性值(調用所添加的對象的某個屬性的set方法)柑司。
如上圖所示迫肖,ValueAnimator通過封裝定義動畫插值的TimeInterpolator以及定義如何計算添加了動畫的屬性的值TypeEvaluator。
要想啟動一個動畫攒驰,需要創(chuàng)建ValueAnimator蟆湖,并設置初值、終值玻粪、動畫的持續(xù)時間隅津。調用start()方法啟動诬垂。在整個動畫的執(zhí)行過程中,ValueAnimator會計算時間流逝率( elapsed fraction )伦仍,也就是動畫已經消耗的時間所占總持續(xù)時間的百分比结窘,0表示0%,1表示100%充蓝。以上面的線性動畫示例圖為例隧枫,當 t = 10 ms
時,因為總時間為40ms
谓苟,流逝率為fraction=(10-40)/40=0.25
官脓。當其計算出時間流逝率fraction
后,會將該它傳入當前的ValueAnimator所設置的TimeInterpolator(默認是LinearInterpolator)的getInterpolation方法涝焙,該方法只需一個參數(shù)卑笨,對傳入的fraction
通過某種算法生成一個插值率(interpolated fraction),不同的TimeInterpolator會有不同的實現(xiàn)仑撞,因此得到的插值率會不一樣赤兴,對于LinearInterpolator,得到的插值率就是傳入的流逝率派草,所以所以屬性的變化會隨著時間的變化是線性關系而搀缠,對于下面的圖,采用的是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;
}
當fraction = 0.25
,返回的值約為0.1464
,顯然小于傳入的0.25
鉴竭,最后將getInterpolation方法的返回值歧譬、初值、終值帶入相應的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));
}
因此當 t = 10 ms
時,對于線性動畫瑰步,X=(int)(0+0.25x(40-0))=10
,對于非線性動畫X=(int)(0+0.1464x(40-0))=6
璧眠,如果是ObjectAnimator,還會在添加動畫的對象上通過反射調用修改屬性的set方法缩焦。
2.2、Property Animation與View Animation的不同之處
Property Animation不僅僅可以對view對象添加動畫责静,還能對非view對象添加動畫袁滥,而View Animation如果要對非View對象添加動畫,需要自己實現(xiàn)比較麻煩灾螃,并且view Animation所能操縱的屬性也很少只能對view的縮放题翻、旋轉、平移設置動畫腰鬼,比如不能修改背景色嵌赠。view animation只是修改了view所繪制的地方塑荒,但并沒有修改view本身,比如對一個按鈕添加平移動畫姜挺,按鈕仍然處在原來的地方齿税,Property Animation則消除了這些限制〕跫遥可以對view和非View對象添加動畫偎窘,并且在動畫播放過程中修改自身。提供了插值(參見2.9)溜在、多動畫功能(參見2.5)陌知。
2.3、使用ValueAnimator設置動畫
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)聽器窖铡,調用getAnimatedValue()方法獲取刷新到某個特定幀時計算所得屬性值,然后做相應的邏輯處理坊谁。
2.4费彼、使用ObjectAnimator設置動畫
ObjectAnimator是ValueAnimator的子類,它內部封裝了計時引擎和ValueAnimator的值計算口芍,可以自動更新設置動畫的某個屬性而無需實現(xiàn)ValueAnimator.AnimatorUpdateListener的onAnimationUpdate()方法獲取每一幀的屬性值來手動刷新箍铲,參見2.6節(jié),從而讓代碼更加簡潔鬓椭,如下所示:
ObjectAnimator anim = ObjectAnimator.ofFloat(foo, "alpha", 0f, 1f);
anim.setDuration(1000);
anim.start();
注意
- ObjectAnimator能夠自動更新屬性值颠猴,實現(xiàn)原理最終是通過反射調用在某個屬性上的setter方法。因此當對此某個對象某個屬性使用ObjectAnimator設置動畫的時候一定要確保該屬性有setter(setXxx()格式)方法小染。如果沒有對應的setter方法翘瓮,如果有權限,添加相關屬性的setter方法氧映,如果沒有添加該屬性setter方法的權限春畔,則使用一個包裝類脱货,或者改用ValueAnimator
- 如果在調用ObjectAnimator的工廠方法(比如
ofFloat()
)方法在參數(shù)values...
上只傳入了一個值岛都,則只會將該值作為終值律姨,因此在設置動畫的某個屬性需要有getter方法(getXxx()格式),因為需要通過getter方法獲取初值臼疫。 - 屬性的setter和getter方法操作的屬性值必須是同一數(shù)據類型作為傳入 ObjectAnimator的
values...
參數(shù)的初值和終值择份,比如當采用的是
ObjectAnimator.ofFloat(targetObject, "propName", 1f)
則屬性的setter和getter參數(shù)必須是float
類型.
- 有些屬性需要手動調用invalidate()刷新值。比如
Drawable
對象的顏色值烫堤。參見2.6
2.5荣赶、使用AnimatorSet同時設置多種動畫
使用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变擒、設置動畫監(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);
}
......
}
需要說明的是,對于某些設置動畫的屬性寝志,需要手動調用invalidate()方法才能確保將屏幕區(qū)域用動畫得到的新的屬性值重新繪制自己娇斑。比如一個Drawable
對象的顏色屬性,然而對于setAlpha()和setTranslationX()則不需要手動調用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設置布局變換時動畫
![拼命寫代碼](https://media.giphy.com/media/JIX9t2j0ZTN9S/giphy.gif)
Android屬性動畫提供了給ViewGroups布局改變時添加動畫的功能。所謂布局改變動畫是指在ViewGroup里的View败富,當你添加或者刪除它悔醋、或者調用它的setVisibility()方法將對應的值設為VISIBLE、GONE兽叮、INVISIBLE時候所呈現(xiàn)出的出場動畫芬骄、退場動畫效果,當對某個ViewGroup內部的view做添加或者刪除的時候鹦聪,還可以給在ViewGroup中的其他view從原來的位置到新的位置添加動畫账阻。
用法:創(chuàng)建一個LayoutTransition對象,然后調用該對象的[setAnimator](https://developer.android.com/reference/android/animation/LayoutTransition.html#setAnimator(int, android.animation.Animator))(int transitionType, Animator animator)泽本,然后在需要添加ViewGroup布局改變動畫的某個ViewGroup對象上調用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)時的設置動畫 -
CHANGE_APPEARING
當新的View出現(xiàn)在ViewGroup中出現(xiàn)時給已經存在于ViewGroup中的其他View設置動畫 -
DISAPPEARING
某個View在ViewGroup中消失時的設置動畫 -
CHANGE_DISAPPEARING
當某個View從ViewGroup中消失時給ViewGroup中的其他View設置動畫。
代碼示例如下:
/**
* 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)已經實現(xiàn)的TypeEvaluator的有IntEvaluator, FloatEvaluator和 ArgbEvaluator。只需要 [evaluate()](http://developer.android.com/reference/android/animation/TypeEvaluator.html#evaluate(float, T, T))方法就可以計算出設置動畫的屬性當前時間點的相應的值冰抢。 FloatEvaluator類的實現(xiàn)如下所示:
public class FloatEvaluator implements TypeEvaluator {
/**
* @param fraction 實現(xiàn)TimeInterpolator接口的某個特定插值器的getInterpolation(float fraction)的返回值
* @param startValue 添加動畫效果的屬性的起始值
* @param endValue 添加動畫效果的屬性的結束值
* @return 添加動畫效果的屬性在當前時間點的值
*/
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é)有關屬性動畫的原理中已經說到TimeInterpolator的作用是根據時間流逝率來計算當前屬性的插值率。Android系統(tǒng)內置許多插值器如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布局中設置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
內部的子view做添加龙致、刪除蛀缝、顯示和隱藏操作等就有布局發(fā)生改變的動畫效果。
2.10目代、使用Keyframe設置動畫
Keyframe的作用通過傳入一對值(時間流失率與屬性值)申明動畫在特定時間點的特定狀態(tài)屈梁。每一個keyframe 可以有自己的插值器來控制從前一幀到當前幀的動畫表現(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設置動畫
使用ViewPropertyAnimator的好處是只需要一個底層的Animator對象就可以同時給各多個屬性添加動畫,效果跟于ObjectAnimator一樣战坤,都是通過實際通過修改view的屬性曙强,并且采用流式風格的代碼,通過鏈式調用途茫,代碼更加精簡易讀碟嘴。下面是同時給一個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方式更加精簡易讀。更多有關ViewPropertyAnimator的內容需要參考Android開發(fā)官方博客Introducing ViewPropertyAnimator
2.12囊卜、XML聲明方式設置動畫
為了能夠區(qū)分出View Animation和Property Animation,配置View Animation的XML文件存放在Android項目的res/anim/
目錄下面娜扇,而配置Property Animation的XML文件存放在res/animator/
目錄下面错沃。
類 名 稱 | 標 簽 |
---|---|
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ù)旋轉已經拍好的膠片放映電影捎废。雖然可以使用AnimationDrawable來在代碼中創(chuàng)建動畫,但最常用的方式是在你的Android項目的res/drawable/
目錄下的XML文件定義動畫致燥,這個XML文件包含了構成該動畫的所有幀以及每一幀的持續(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
屬性設為true
表示該動畫只播放一次,并且當動畫結束之后停留在最后一幀祭示,如果為false
則表示動畫在播放完以后會循環(huán)播放断傲。先假設該上面的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()
方法中調用AnimationDrawable 的start()
方法,因為AnimationDrawable 并沒有完全加載到窗口里面來认罩,如果想在沒有交互的情況下(比如點擊某按鈕觸發(fā)動畫播放事件)就播放動畫箱蝠,在Activity的onWindowFocusChanged()方法中調用該方法。該方法會在窗口獲取到焦點時被調用垦垂。
4宦搬、注意事項
- OOM問題
主要出現(xiàn)在Drawable Animation
中,應盡量避免加載數(shù)量較多且較大的圖片劫拗,盡量避免使用Drawable Animation
间校。 - 內存泄漏
屬性動畫中的無限循環(huán)動畫需要在Activity
退出的時候及時停止,否則將導致Activity
無法釋放而造成內存泄露页慷。View Animation
不存在這個問題憔足。 - 兼容問題
某些動畫在3.0以下系統(tǒng)上有兼容性問題,主要是Property Animation
在Android 3.0以下不能使用酒繁。 - View Animation的問題
滓彰。View Animation
是對View
的影像做動畫,并不是真正的改變View
的狀態(tài)州袒,因此有時候動畫完成之后view無法隱藏揭绑,即setVisibility(View.GONE)
失效了,此時需要調用view.clearAnimation()
清除view動畫才行郎哭。 - 不要使用px
盡量使用dp,px在不同的分辨率的手機下面會有不同的效果洗做。 - 動畫交互問題
在android3.0以前的系統(tǒng)上,view動畫和屬性動畫彰居,新位置均無法觸發(fā)點擊事件诚纸,同時,老位置仍然可以觸發(fā)單擊事件陈惰。從3.0開始畦徘,屬性動畫的單擊事件觸發(fā)位置為移動后的位置毕籽,view動畫仍然在原位置。 - 動畫交互問題
使用動畫的過程中井辆,建議開啟硬件加速关筒,這樣會提高動畫的流暢性。