Android動畫機制與使用技巧
導語
Android動畫效果一直是人機互動中十分重要的一部分,從早期的Android版本中,由於動畫機制和繪圖機制的不健全,Android的人機互動備受詬病,Android從4.X開始,特別是5.X,動畫越來越完善了,Google也開始重視這一方面了,當然我們也必須重視這一方面,看例項戳我。
主要內容
- Android View動畫框架
- Android屬性動畫分析
- Android佈局動畫
- Interpolators(插值器)
- 自定義動畫
具體內容
Android View動畫框架
Animation動畫框架定義了透明度,旋轉,縮放個移動等幾種動畫,而且控制了整個的View,實現原理是每次繪製檢視的時候View所在的ViewGroup中drawChild函式獲取該View的Animation的Transformation值,然後呼叫了canvas.concat(transformToApply.getMatrix()),通過矩陣運算完成動畫幀,如果動畫沒有完成則繼續呼叫invalidate()方法啟動下回繪製來驅動動畫,從而完成整個動畫的繪製。
檢視動畫使用簡單,效果豐富,它提供了AlphaAnimation,RotateAnimatio,TranslateAnimation,ScaleAnimation四種動畫方式,並提供了Animationset動畫集合,混合使用多種動畫,在Android3.0之前,檢視動畫一家獨大,但隨著Android3.0之後屬性動畫框架的推出它的風光就大不如前了。相比屬性動畫,檢視動畫的一個非常大的缺陷就是不具備互動性,當某個元件發生檢視動畫後,其響應事件的位置還依然在動畫前的地方,所以檢視動畫只能做普通的顯示效果,避免互動的發生,但是它的優點也非常明顯,即效率比較高且使用方便。檢視動畫使用非常簡單, 不僅可以通過XML檔案來描述一個動畫過程,同樣也可以使用程式碼來控制整個動畫過程。
下面這個例項就列舉了一些簡單的檢視動畫使用方法。
透明度動畫
AlphaAnimation al = new AlphaAnimation(0,1);
al.setDuration(2000);
alpha.startAnimation(al);
旋轉動畫
RotateAnimation ro = new RotateAnimation(0,300,100,100); ro.setDuration(2000);
rotate.setAnimation(ro);
平移動畫
TranslateAnimation tr = new TranslateAnimation(0,200,0,300);
tr.setDuration(2000 );
translate.setAnimation(tr);
縮放動畫
ScaleAnimation sc = new ScaleAnimation(0,2,0,2); sc.setDuration(2000);
scale.setAnimation(sc);
動畫集合
AnimationSet setAnimation = new AnimationSet(true);
setAnimation.setDuration(2000);
AlphaAnimation als = new AlphaAnimation(0,1);
als.setDuration(2000);
setAnimation.addAnimation(als);
RotateAnimation ros = new RotateAnimation(0,300,100,100);
ros.setDuration(2000);
setAnimation.addAnimation(ros);
set.startAnimation(setAnimation);
我們一起來執行一下看效果:
當然,有動畫,就有監聽,我們來監聽一下動畫,以透明動畫為例。
AlphaAnimation al = new AlphaAnimation(0,1);
al.setDuration(2000);
alpha.startAnimation(al);
al.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
Log.i("Animation","開始");
}
@Override
public void onAnimationEnd(Animation animation) {
Log.i("Animation","結束");
Toast.makeText(MainActivity.this,"動畫結束",Toast.LENGTH_LONG).show();
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
執行之後列印log:
當然,我們眼見為實:
通過監聽,我們就可以瞭解動畫的動向了。
Android屬性動畫分析
屬性動畫在Animator框架裡的,用的最多的也就是AnimatorSet和ObjectAnimator配合,使用ObjectAnimator進行更精細化的控制,只控制一個物件的屬性,而使用多個ObjectAnimator組合到AnimatorSet形成一個動畫,而且ObjectAnimator能夠自動驅動,單證各種好處,我們來看一下。
ObjectAnimator
ObjectAnimator是屬性動畫框架中最重要的實行類,建立一個ObjectAnimator只需通過他的靜態工廠類直接返回一個ObjectAnimator物件,引數包括一個物件和物件的屬性名字,但這個屬性必須有get和對函式,內部會通過Java反射機制來呼叫set函式修改物件屬性值.同樣你也可以呼叫setIn設定相應的差值器。 下面這個小例子就完成了一個非常簡單的平移動畫。
在前面的講解中說到,以前的動畫框架所產生的動畫,並不能改變事件響應的位置,它只是單純地修改了顯示。如果使用舊的檢視動畫產生上面的效果,那麼按鈕的實際點選有效區依然在原來的地方,點選移動後的地方是不會有點選事件發生的。而屬性動圓則不同,由於它真實地改變了一個View的屬性,所以事件響應的區域也同樣發生了改變,這時候點選移動後的按鈕, 就會響應點選事件了。
讓我們來看看這個簡單的平移動畫是如何實現的,麻雀雖小五臟俱全,這個簡單的例子基本上就涵蓋了ObjectAnimator的所有知識。
ObjectAnimator ob = ObjectAnimator.ofFloat(
view,
"translationX",
300);
ob.setDuration(2000);
ob.start();
通過ObjectAnimator的靜態工廠方法,建立個ObjectAnimator物件,第一個引數自然是要操縱的view,第二個引數則是要操縱的屬性而最後個引數是一個可變陣列引數,需要傳遞進去該屬性變化的一個取值過程。
不過,在使用ObjectAnimator的時候,有一點是非常重要的,就是操縱的set,get方法,不然ObjectAnimator是無效的,下面我們具體舉一些值。
- translationX和 translationY:這兩個屬性作為一種增量來控制著View物件從它佈局容器的左上角座標偏移的位置。
- rotation、rotationX和rotationY:這三個屬性控制View物件圍繞支點進行2D和3D旋轉。
- scaleX和scaleY:這兩個屬性控制著View物件圍繞它的支點進行2D縮放。
- pivotX和pivotY:這兩個屬性控制著view物件的支點位置,圍繞這個支點進行旋轉和縮放變換處理,預設情況下,該支點的位置就是View物件的中心點。
- x和y:這是兩個簡單實用的屬性,它描述了View物件在它的容器中的最終位置,它是最初的左上角座標和 translationX和 translationY值的累計和。
- alpha:它表示View物件的alpha透明度,預設值是1(不透明),0代表完全透明(不可見)。
由以上可知,檢視動畫所實現的動畫效果,在這裡基本都已經包含了,那麼如果一個屬性沒有get、set方法,屬性動畫是不是就束手無策了答案當然是否定的,google在應用層提供了兩種方案來解決這個問題, 一個是通過自定義一個屬性類或者包裝類,來間接地給這個屬性增加get、set方法。或者通過ValueAnimator來實現,ValueAnimator在後面的內容中會講到,這裡先來看看如何使用包裝類的方法給一個屬性增加set、get方法:
private static class WrapperView {
private View mTarget;
public WrapperView(View target) {
mTarget = target;
}
public int getWidth() {
return mTarget.getLayoutParams().width;
}
public void setWidth(int width) {
mTarget.getLayoutParams().width = width;
mTarget.requestLayout();
}
}
通過上面的程式碼就給一個屬性包裝上了一層,並且提供了set、get方法,使用時只需要操作包裝類就可以間接呼叫到get、set方法了。
WrapperView vi = new WrapperView(mButton);
ObjectAnimator.ofInt(vi, "width", 500).setDuration(2000).start();
PropertyValuesHolder
這個類類似檢視動畫中的AnimationSet,就是把動畫給組合起來,在屬性動畫中,如果針對一個物件的多個屬性,就同時需要多個動畫了,可以使用PropertyValuesHolder,來實現,比如需要在平移的過程中,同時改變x、y的縮放,程式碼如下所示。
PropertyValuesHolder pvh1 = PropertyValuesHolder.ofFloat("translationX",300f);
PropertyValuesHolder pvh2 = PropertyValuesHolder.ofFloat("scaleX",1f,0,1f);
PropertyValuesHolder pvh3 = PropertyValuesHolder.ofFloat("scaleY",1f,0,1f);
ObjectAnimator.ofPropertyValuesHolder(alpha,pvh1,pvh2,pvh3).setDuration(2000).start();
ValueAnimator
ValueAnimator這個屬性在動畫當中有很大的地位,雖然不想ObjectAnimator那樣,耀眼,但是他確實屬性動畫的核心所在,ObjectAnimator也是繼承自他。
public final class ObjectAnimator extends ValueAnimator
ValueAnimator本身不提供任何動畫,他更像是一個數值發生器,用來產生一定具有規律的數字,從而讓呼叫者控制動畫的整個過程,我們舉個例子來說明。
ValueAnimator va = ValueAnimator.ofFloat(0,100);
va.setTarget(view);
va.setDuration(2000).start();
va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float values = (float) animation.getAnimatedValue();
Log.i("數值",values+"");
}
});
我們執行一下:
動畫事件的監聽
一個完整的動畫是具有:start、repeat、end、cancel四個過程的,通過Android的介面,我們很容易監聽到這幾個事件。
ob.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
當然,大部分的場景嗎,我們只關心動畫結束,所以,Android也提供了一個AnimatorLisistenerAdapter來讓你自己選擇監聽事件。
va.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
}
});
AnimatorSet
對於一個屬性同時作用在一個view上,前面已經有一個PropertyValuesHolder了,而AnimatorSet不僅能實現,而且能更精準的控制順序,同樣是實現PropertyValuesHolder的動畫,AnimatorSet是這樣實現的:
ObjectAnimator animator1 = ObjectAnimator.ofFloat(alpha, "translationX", 300f);
ObjectAnimator animator2 = ObjectAnimator.ofFloat(alpha, "scaleX", 1f, 0, 1f);
ObjectAnimator animator3 = ObjectAnimator.ofFloat(alpha, "scaleY", 1f, 0, 1f);
AnimatorSet set = new AnimatorSet();
set.setDuration(2000);
set.playTogether(animator1,animator2,animator3);
set.start();
在屬性動畫中,AnimatorSet正是通過playTogether等方法控制多個動畫協同工作,從而控制播放順序的。
在XML中定義動畫
屬性動畫同樣的可以定義在xml中,我們也去玩玩:
<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="2000"
android:propertyName="scaleX"
android:valueFrom="1.0"
android:valueTo="2.0"
android:valueType="floatType">
</objectAnimator>
在程式碼中引用:
/**
* 引用xml動畫
* @param v
*/
private void scaleX(View v){
Animator anim = AnimatorInflater
.loadAnimator(this,R.animator.animator);
anim.setTarget(v);
anim.start();
}
View的animate方法
在Android3.0,Google給view增加了animate方法直接來驅動屬性動畫,程式碼如下,我們可以發現,其實animate就是屬性動畫的一種縮寫。
animate.animate().alpha(0).y(300).setDuration(2000).withStartAction(new Runnable() {
@Override
public void run() {
}
}).withEndAction(new Runnable() {
@Override
public void run() {
runOnUiThread(new Runnable() {
@Override
public void run() {
}
});
}
}).start();
Android佈局動畫
所謂的佈局動畫,就是作用在ViewGruop中給View新增的過渡效果,最簡單的方法是在xml中開啟。
android:animateLayoutChanges="true"
不過這都是Android自帶的效果,渣渣。
我們還可以通過LayoutAnimationController來定義。
ll = (LinearLayout) findViewById(R.id.ll);
//設定過渡動畫
ScaleAnimation sa = new ScaleAnimation(0, 1, 0, 1);
sa.setDuration(2000);
LayoutAnimationController lc = new LayoutAnimationController(sa, 0.5f);
lc.setOrder(LayoutAnimationController.ORDER_NORMAL);
//設定佈局動畫
ll.setLayoutAnimation(lc);
通過上面。就可以給佈局正加一個檢視動畫,讓子View出現的時候有一個縮放動畫,在apiDemo中這個例子還是很經典的。
LayoutAnimationController的第一個引數是需要作用的動畫,而第二個引數,剛是每個子View顯示的delay的時間。當delay時間不為0時,可以設定子View顯示的順序:
- LayoutAnimationController.ORDER_NORMAL——順序。
- LayoutAnimationController.ORDER_RANDOM——隨機。
- LayoutAnimationController.ORDER_REVEESE——反序。
Interpolators(插值器)
插值器在動畫中是一個非常重要的概念,我們通過插值器可以定義動畫變換速率,這一點非常類似物理中的加速度,其作用主要是目標變化對應的變化,同樣的一個動畫變換起始值,在不同的插值器的作用下,每個單位時間內所達到的變換值都是不一樣的,例如一個平移動畫,如果使用線性插值器,那麼在持續時間內單位時間所移動的距離都是一樣的,如果使用加速度插值器,那麼單位時間內所移動的速度越來越快,大家如果把插值器的概念理解為一個人進行萬米長跑,規定一個小時到達,有的人怕時間來不及一開始就加速跑但是到後面速度越來越慢,而有的人開始節省體力,所以開始跑的比較慢,後來越跑越快直到終點,不管怎麼跑,最終他們的都是在規定的時間到達終點,唯一不同的是他們的跑的速度不同,通過這個例子,我們可以很好的理解插值器的概念。
自定義動畫
建立自定義動畫很簡單,只需要實現applyTransformation的邏輯就可以,不過通常情況下,我們還要覆蓋父類的initialize方法來完成一些初始化工作。
@Overrideprotected void applyTransformation(float interpolatedTime, Transformation t) {
super.applyTransformation(interpolatedTime, t);
}
第一個引數interpolatedTime是前面說的插值器的時間因子,這個因子是由動畫當前完成的百分比和當前時間對應的差值計算的,取值範圍在0-1.0,第二個引數就非常簡單了,她是矩陣的封裝類,一般使用這個類獲取當前的矩陣物件,程式碼如下:
Matrix matrix = t.getMatrix();
通過改變獲得的matrix 物件,可以將動畫效果實現,而對於matrix 的變換操作,基本上可以實現任何效果,我們實現一個電視機關閉的效果。
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
Matrix matrix = t.getMatrix();
matrix.preScale(1, 1 - interpolatedTime, width,height);
super.applyTransformation(interpolatedTime, t);
}
當然我們可以設定更加精準的插值器,從而對不同的過程採用不同的動畫效果,模擬的更加逼真。
接下來我們結合矩陣,並且使用Canmera來實現一個3D的效果,要注意的是,這裡所指的Camera不是相機,而是這個類,他封裝了openGl的3D動畫,我們繼續用程式碼來實現。
@Override
public void initialize(int width, int height, int parentWidth, int parentHeight) {
super.initialize(width, height, parentWidth, parentHeight);
setDuration(2000);
setFillAfter(true);
setInterpolator(new BounceInterpolator());
w = width / 2;
h = height / 2;
}
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
Matrix matrix = t.getMatrix();
// matrix.preScale(1, 1 - interpolatedTime, w,h);
mCamera.save();
//設定旋轉角度
mCamera.rotate(0, 180, 360);
mCamera.restore();
//通過pre方法設定矩形作用前的偏移量來改變旋轉中心
matrix.preTranslate(w, h);
matrix.postTranslate(-w, -h);
super.applyTransformation(interpolatedTime, t);
}
通過以上的方法,就可以實現了。
總結
- 瞭解Android動畫框架,會使用Android簡單檢視動畫。
- 學會使用Android屬性動畫開發出更加豐富的動畫效果。
- 簡單使用佈局動畫。
- 使用Interpolators(插值器)自定義動畫。