1. 程式人生 > >Android屬性動畫簡析

Android屬性動畫簡析

簡析

大家知道,我們在開發一款產品的時候為了達到良好的使用者體驗,我們可以在應用中適當的加上一些動畫效果,譬如平移、縮放、旋轉等等,但是這些常用的動畫在Android很早期的版本中就存在了,我們稱之為傳統動畫,傳統動畫一般分為Tween動畫和Frame動畫,這也是我們最常用的的動畫,統稱為Animation。傳統的Animation動畫實現上是通過不停的呼叫View的onDraw方法來重新繪製View來實現的。 在Android3.0以後,Google為Android新增了屬性動畫框架Animator,為什麼叫做屬性動畫呢?因為屬性動畫Animator不像傳統動畫那樣需要不停呼叫onDraw方法繪製介面,而且可以通過get、set方法,去真實的改變一個view的屬性的。Animation動畫僅僅給使用者一種“虛假”的動畫效果,其執行動畫view並沒有正在的改變自身的屬性,例如位置。而屬性動畫Animator,是真真正正的通過程式碼將view“動畫”到了指定的位置了。

傳統動畫不能改變view真實屬性

下面,我們來看看傳統動畫實現的效果,我們使用很簡單的介面,給指定的ImageView設定上平移動畫,給ImageView指定點選事件監聽,方便我們測試傳統動畫的侷限性。
public class MainActivity extends Activity {

	private Button mButton;
	private ImageView mImageView;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		requestWindowFeature(Window.FEATURE_NO_TITLE);
		setContentView(R.layout.activity_main);
		mButton = (Button) findViewById(R.id.button);
		mImageView = (ImageView) findViewById(R.id.imageview);

		mButton.setOnClickListener(new OnClickListener() {

			@Override
			public void onClick(View v) {
				playAnim();
			}
		});
		mImageView.setOnClickListener(new OnClickListener() {

			@Override
			public void onClick(View v) {
				showToast();
			}
		});
	}

	/**
	 * 播放動畫
	 */
	protected void playAnim() {
		TranslateAnimation ta = new TranslateAnimation(0.0f, 200.0f, 0.0f, 0.0f);
		ta.setDuration(1000);
		ta.setFillAfter(true);
		mImageView.startAnimation(ta);
	}

	protected void showToast() {
		Toast.makeText(MainActivity.this, "Hello", Toast.LENGTH_SHORT).show();
	}

}
看到執行效果了吧,我們給ImageView的小機器人設定了點選事件並且彈出Toast了,當我們點選Button時,ImageView上的平移動畫被執行了,並且平移到了指定的位置(200px),然而我們在此時的ImageView上點選時,並沒有彈出Toast提示,也就是說點選的位置不在那裡,當我回頭再次點選ImageView最初的位置時,Toast又被show了出來,這是不是很“詭異”?是的,上面的小例子足以說明,傳統的Animaion動畫只能改變動態視覺效果的動畫,並不能真實的去改變一個View的屬性(位置等)。而且,傳統的Animation動畫是通過不停的呼叫onDraw方法去繪製而完成的效果,這樣的實現方式很消耗資源(cpu)的。

API概述

描述
ValueAnimator 屬性動畫時序引擎也計算屬性動畫的值。它擁有所有的核心功能,計算動畫值,幷包含每個動畫,有關時序的詳細資訊是否動畫重複,聽眾接收更新事件,並設定自定義型別的能力評估。有兩件,以生動活潑的屬性:動畫值計算和設定這些物件的屬性動畫值。ValueAnimator不進行第二件,所以你一定要更新計算值ValueAnimator和修改你想用自己的邏輯動畫的物件。
ObjectAnimator ValueAnimator的子類,允許你設定一個目標物件和物件屬性的動畫。當計算出一個新的動畫值,本類更新相應的屬性。你大部分情況使用ObjectAnimator,因為它使得動畫的目標物件的值更簡單。然而,有時你直接使用ValueAnimator,因為ObjectAnimator有一些限制,如對目標物件目前要求的具體acessor方法。
AnimatorSet 提供機制,以組合動畫一起,讓他們關聯性執行。你可以設定動畫一起播放,順序,或在指定的延遲之後。
內容來自於:http://developer.android.com/guide/topics/graphics/prop-animation.html

ObjectAnimator

執行單個動畫

通常我們會使用ObjectAnimator類來為我們的view設定動畫,下面我們來簡單的看一下,使用了屬性動畫後,我們上述的小例子中的ImageView會發生哪些變化?修改上面的playAnim方法,將普通動畫換成屬性動畫:
protected void playAnim() {
		ObjectAnimator.ofFloat(mImageView, "translationX", 0.0f, 200.0f).setDuration(1000).start();
}
執行效果如上圖所示。對比上面的那幅圖看,發現當屬性動畫執行後,不但從視覺上改變了ImageView的位置,而且ImageView上的點選事件的位置也跟著變化了,說明使用屬性動畫的View,實際上是真實的改變了一個View的屬性的。 從上面的一行程式碼中,可以發現屬性動畫使用上是非常簡單的,ObjectAnimator中的ofFloat方法實際上是static方法,而且返回值還是一個ObjectAnimator物件。ofFloat的引數也很簡單,第1個引數是指定需要執行動畫的view,第2個引數是動畫模式,第三個引數是可變的陣列,這裡需要描述動畫的初始位置和終點位置的座標。 除了上面例子的中的translationX屬性,還可以指定translationY屬性,表示ImageView沿著Y軸的方向平移,然後我們也可以指定X或者Y,那麼translationX和X或者translationY和Y的區別,就是translationX是指定了ImageView在X軸上的偏移量,而單純的指定X表示ImageView被移動到指定的X軸上的位置,這點跟View的scrollTo和scrollBy方法有點類似。此外除了平移之外,還可以指定動畫的模式有: translationX、translationY rotation、rotationX、rotationY scaleX、scaleY X、Y alpha

ObjectAnimator繼承自ValueAnimator,要指定一個物件及該物件的一個屬性,當屬性值計算完成時自動設定為該物件的相應屬性,即完成了Property Animation的全部兩步操作。實際應用中一般都會用ObjectAnimator來改變某一物件的某一屬性,但用ObjectAnimator有一定的限制,要想使用ObjectAnimator,應該滿足以下條件:

  • 物件應該有一個setter函式:set<PropertyName>(駝峰命名法)
  • 如上面的例子中,像ofFloat之類的工場方法,第一個引數為物件名,第二個為屬性名,後面的引數為可變引數,如果values…引數只設置了一個值的話,那麼會假定為目的值,屬性值的變化範圍為當前值到目的值,為了獲得當前值,該物件要有相應屬性的getter方法:get<PropertyName>
  • 如果有getter方法,其應返回值型別應與相應的setter方法的引數型別一致。

如果上述條件不滿足,則不能用ObjectAnimator,應用ValueAnimator代替。


多個動畫同時執行

PropertyValuesHolder

有時候我們需要同時執行多個屬性動畫的疊加效果的時候,可以使用PropertyValuesHolder工具類來“裝載”多種動畫,然後呼叫ObjectAnimator.ofPropertyValuesHolder()方法將裝載好的動畫交給ObjectAnimator去執行,例如:
protected void playAnim() {
		PropertyValuesHolder p1 = PropertyValuesHolder.ofFloat("translationX",0.0f, 200.0f);
		PropertyValuesHolder p2 = PropertyValuesHolder.ofFloat("translationY",0.0f, 200.0f);
		PropertyValuesHolder p3 = PropertyValuesHolder.ofFloat("rotation",0.0f, 360.0f);
		ObjectAnimator.ofPropertyValuesHolder(mImageView, p1, p2, p3).setDuration(2000).start();
}

AnimatorSet

跟普通的View動畫一樣,執行多種動畫效果時,屬性動畫也提供了動畫集方便我們執行多種動畫。
protected void playAnim() {
		Animator animator1 = ObjectAnimator.ofFloat(mImageView, "translationX",0.0f, 200.0f);
		Animator animator2 = ObjectAnimator.ofFloat(mImageView, "translationY",0.0f, 200.0f);
		Animator animator3 = ObjectAnimator.ofFloat(mImageView, "rotation",0.0f, 360.0f);
		AnimatorSet set = new AnimatorSet();
		set.playTogether(animator1, animator2, animator3);
		set.setDuration(2000);
		set.start();
}

AnimationSet提供了一個把多個動畫組合成一個組合的機制,並可設定組中動畫的時序關係,如同時播放,順序播放等。

以下例子同時應用5個動畫:

  1. 播放anim1;
  2. 同時播放anim2,anim3,anim4;
  3. 播放anim5。
AnimatorSet bouncer = new AnimatorSet();
bouncer.play(anim1).before(anim2);
bouncer.play(anim2).with(anim3);
bouncer.play(anim2).with(anim4)
bouncer.play(anim5).after(amin2);
animatorSet.start();

動畫監聽事件

Android屬性動畫也為我們提供了對動畫播放過程的監聽器,我們只需要呼叫Animator.addListener()方法,將AnimatorListener物件傳遞進去就可以了:
anim.addListener(new Animator.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) {
		//動畫被取消時執行
	}
});
Animator.AnimatorListener物件下,有4個未實現的方法,我們可以分別實現一下其中的方法,就可以方便的去監聽動畫執行整個過程了。但是Animator.AnimatorListener物件不夠簡潔,因為大部分時候我們只需要監聽動畫結束時的事件即可,那麼Android也為我們提供好了一個簡化的監聽物件AnimatorListenerAdapter,AnimatorListenerAdapter
是個抽象類,其下面共有onAnimationCancel(),onAnimationEnd(),onAnimationPause(),onAnimationRepeat(),onAnimationResume(),onAnimationStart()幾個方法供我們呼叫,既然我們一般情況下僅僅是需要動畫結束時監聽,那麼我們就按照如下方式使用:
anim.addListener(new AnimatorListenerAdapter() {

	@Override
	public void onAnimationEnd(Animator animation) {
		// TODO Auto-generated method stub
		super.onAnimationEnd(animation);
	}

});

ValueAnimator

概念

ValueAnimator包含Property Animation動畫的所有核心功能,如動畫時間,開始、結束屬性值,相應時間屬性值計算方法等。應用Property Animation有兩個步聚:

  1. 計算屬性值
  2. 根據屬性值執行相應的動作,如改變物件的某一屬性。

ValuAnimiator只完成了第一步工作,如果要完成第二步,需要實現ValueAnimator.onUpdateListener介面,這個介面只有一個函式onAnimationUpdate(),在這個函式中會傳入ValueAnimator物件做為引數,通過這個ValueAnimator物件的getAnimatedValue()函式可以得到當前的屬性值如:

protected void playAnim() {
		ValueAnimator animator = ValueAnimator.ofInt(0, 10);
		animator.setDuration(100);
		animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

			@Override
			public void onAnimationUpdate(ValueAnimator animation) {
				Log.i("TAG", "AnimatedValue : " + animation.getAnimatedValue().toString());
			}
		});
		animator.start();
}

從上面的例子可以看到ValueAnimator類實現的是動畫的插值因子的計算,大部分情況下我們使用ObjectAnimator就可以輕鬆實現很多種動畫效果了,然後使用ObjectAnimator的View必須滿足有getter和setter方法,若沒有這些方法,使用ObjectAnimator的動畫是無法實現的,我們只好考慮使用ObjectAnimator的父類ValueAnimator了,ValueAnimator實現動畫不需要View含有getter和setter方法,它是通過計算動畫的插值因子,我們根據這個插值自定義動畫效果就可以了。

TypeEvaluator

TypeEvaluator是一個介面,通過實現該介面下的evaluate方法,可以實現我們自定義的各種複雜效果的動畫:

ValueAnimator animator =ValueAnimator.ofObject(new TypeEvaluator<Number>() {

	@Override
	public Number evaluate(float fraction, Number startValue,Number endValue) {
		// TODO Auto-generated method stub
		return null;
	}
});
上面就是實現的TypeEvaluator介面,下面有個未實現的方法,這個回撥函式中提供如下三個引數: fraction:插值因子,取值範圍0~1 startValue:動畫的起始值 endValue:動畫的結束值 我們可以根據這3個引數來編寫計算自己需要的動畫效果,樣式很多樣化的,不僅僅是ObjectAnimator裡幾種動畫型別了。由此可以看出,ValueAnimator比ObjectAnimator更加靈活,方式更加繁多,我們自定義動畫效果時,可以使用ValueAnimator實現TypeEvaluator介面來寫自己的動畫演算法,實現比較複雜的動畫。 關於屬性動畫的詳細介紹,可以參考Google提供的官方文件,裡面有非常詳細的講述: