Android動畫深入原理分析
動畫分類
Android動畫可以分3種:View動畫,幀動畫和屬性動畫;屬性動畫爲API11的新特性,在低版本是無法直接使用屬性動畫的,但可以用nineoldAndroids來實現(但是本質還是viiew動畫)。學習本篇內容主要掌握以下知識:
1,View動畫以及自定義View動畫。
2,View動畫的一些特殊使用場景。
3,對屬性動畫做了一個全面的介紹。
4,使用動畫的一些注意事項。
View動畫的四種變換效果對應着Animation的四個子類:TranslateAnimation(平移動畫)、ScaleAnimation(縮放動畫)、RotateAnimation(旋轉動畫)和AlphaAnimation(透明度動畫),他們即可以用代碼來動態創建也可以用XML來定義,推薦使用可讀性更好的XML來定義。 <set>標籤表示動畫集合,對應AnimationSet類,它可以包含若干個動畫,並且他的內部也可以嵌套其他動畫集合。android:interpolator 表示動畫集合所採用的插值器,插值器影響動畫速度,比如非勻速動畫就需要通過插值器來控制動畫的播放過程。
android:shareInterpolator表示集合中的動畫是否和集合共享同一個插值器,如果集合不指定插值器,那麼子動畫就需要單獨指定所需的插值器或默認值。 Animation通過setAnimationListener方法可以給View動畫添加過程監聽。
自定義View動畫只需要繼承Animation這個抽象類,並重寫initialize和applyTransformation方法,在initialize方法中做一些初始化工作,在applyTransformation中進行相應的矩形變換,很多時候需要採用Camera來簡化矩形變換過程。
幀動畫是順序播放一組預先定義好的圖片,類似電影播放;使用簡單但容易引發OOM,儘量避免使用過多尺寸較大的圖片。
view動畫應用場景
LayoutAnimation作用於ViewGroup,爲ViewGroup指定一個動畫,當他的子元素出場的時候都會具有這種動畫,ListView上用的多,LayoutAnimation也是一個View動畫。
代碼實現:
-
<?xml version="1.0" encoding="utf-8"?>
-
<layoutAnimation xmlns:android="http://schemas.android.com/apk/res/android"
-
android:animationOrder="normal"
-
android:delay="0.3" android:animation="@anim/anim_item"/>
-
-
//--- animationOrder 表示子元素的動畫的順序,有三種選項:
-
//normal(順序顯示)、reverse(逆序顯示)和random(隨機顯示)。
-
-
<?xml version="1.0" encoding="utf-8"?>
-
<set xmlns:android="http://schemas.android.com/apk/res/android"
-
android:duration="300"
-
android:shareInterpolator="true">
-
<alpha
-
android:fromAlpha="0.0"
-
android:toAlpha="1.0" />
-
<translate
-
android:fromXDelta="300"
-
android:toXDelta="0" />
-
</set>
第一種,在佈局中引用LayoutAnimation
-
<ListView
-
android:id="@+id/lv"
-
android:layout_width="match_parent"
-
android:layout_height="0dp"
-
android:layout_weight="1"
-
android:layoutAnimation="@anim/anim_layout"/>
第二種,代碼種使用
-
Animation animation = AnimationUtils.loadAnimation(this, R.anim.anim_item);
-
LayoutAnimationController controller = new LayoutAnimationController(animation);
-
controller.setDelay(0.5f);
-
controller.setOrder(LayoutAnimationController.ORDER_NORMAL);
-
listview.setLayoutAnimation(controller);
幀動畫
逐幀動畫(Frame-by-frame Animations)從字面上理解就是一幀挨着一幀的播放圖片,類似於播放電影的效果。不同於View動畫,Android系統提供了一個類AnimationDrawable來實現幀動畫,幀動畫比較簡單,我們看一個例子就行了。
-
<?xml version="1.0" encoding="utf-8"?>
-
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
-
android:oneshot="false">
-
-
<item
-
android:drawable="@mipmap/lottery_1"
-
android:duration="200" />
-
// ...省略很多
-
<item
-
android:drawable="@mipmap/lottery_6"
-
android:duration="200" />
-
-
</animation-list>
然後
-
imageView.setImageResource(R.drawable.frame_anim);
-
AnimationDrawable animationDrawable = (AnimationDrawable) imageView.getDrawable();
-
animationDrawable.start();//啓動start,關閉stop
屬性動畫
屬性動畫是Android 3.0新加入(api 11)的功能,不同於之前的view動畫(看過的都知道,view動畫比如實現的位移其實不是真正的位置移動,只是實現了一些簡單的視覺效果)。屬性動畫對之前的動畫做了很大的拓展,毫不誇張的說,屬性動畫可以實現任何動畫效果,因爲在作用的對象是屬性(對象),屬性動畫中有幾個概念需要我們注意下,
ValueAnimator、ObjectAnimator、AnimatorSet等。
屬性動畫作用屬性
1,屬性動畫可以對任意對象的屬性進行動畫而不僅僅是View,屬性動畫默認間隔300ms,默認幀率10ms/幀。
2,看一段代碼
-
<set
-
android:ordering=["together" | "sequentially"]>
-
-
<objectAnimator
-
android:propertyName="string"
-
android:duration="int"
-
android:valueFrom="float | int | color"
-
android:valueTo="float | int | color"
-
android:startOffset="int"
-
android:repeatCount="int"
-
android:repeatMode=["repeat" | "reverse"]
-
android:valueType=["intType" | "floatType"]/>
-
-
<animator
-
android:duration="int"
-
android:valueFrom="float | int | color"
-
android:valueTo="float | int | color"
-
android:startOffset="int"
-
android:repeatCount="int"
-
android:repeatMode=["repeat" | "reverse"]
-
android:valueType=["intType" | "floatType"]/>
-
-
<set>
-
...
-
</set>
-
</set>
<set>
它代表的就是一個AnimatorSet對象。裏面有一個 ordering屬性,主要是指定動畫的播放順序。
<objectAnimator>
它表示一個ObjectAnimator對象。它裏面有很多屬性,我們重點需要了解的也是它。
android:propertyName -------屬性名稱,例如一個view對象的”alpha”和”backgroundColor”。
android:valueFrom --------變化開始值
android:valueTo ------------變化結束值
android:valueType -------變化值類型 ,它有兩種值:intType和floatType,默認值floatType。
android:duration ---------持續時間
android:startOffset ---------動畫開始延遲時間
android:repeatCount --------重複次數,-1表示無限重複,默認爲-1
android:repeatMode 重複模式,前提是android:repeatCount爲-1 ,它有兩種值:”reverse”和”repeat”,分別表示反向和順序方向。
<animator>
它對應的就是ValueAnimator對象。它主要有以下屬性。
android:valueFrom
android:valueTo
android:duration
android:startOffset
android:repeatCount
android:repeatMode
android:valueType
定義了一組動畫之後,我們怎麼讓它運行起來呢?
-
AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(myContext,
-
R.anim.property_animator);
-
set.setTarget(myObject);//myObject表示作用的對象
-
set.start();
插值器和估值器
時間插值器(TimeInterpolator)的作用是根據時間流逝的百分比來計算出當前屬性值改變的百分比,系統預置的有LinearInterpolator(線性插值器:勻速動畫),AccelerateDecelerateInterpolator(加速減速插值器:動畫兩頭慢中間快),DecelerateInterpolator(減速插值器:動畫越來越慢)。
注:這裏的插值器很多,可以翻看我之前關於插值器的講解。
估值器(TypeEvaluator)的作用是根據當前屬性改變的百分比來計算改變後的屬性值。系統預置有IntEvaluator 、FloatEvaluator 、ArgbEvaluator。
舉個簡單的例子吧
-
public class IntEvaluator implements TypeEvaluator<Integer> {
-
public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
-
int startInt = startValue;
-
return (int)(startInt + fraction * (endValue - startInt));
-
}
-
}
上述代碼就是計算當前屬性所佔總共的百分百。
插值器和估值器除了系統提供之外,我們還可以自定義實現,自定義插值器需要實現Interpolator或者TimeInterpolator;自定義估值器算法需要實現TypeEvaluator。
屬性動畫監聽器
屬性動畫監聽器用於監聽動畫的播放過程,主要有兩個接口:AnimatorUpdateListener和AnimatorListener 。
AnimatorListener
-
public static interface AnimatorListener {
-
void onAnimationStart(Animator animation); //動畫開始
-
void onAnimationEnd(Animator animation); //動畫結束
-
void onAnimationCancel(Animator animation); //動畫取消
-
void onAnimationRepeat(Animator animation); //動畫重複播放
-
}
AnimatorUpdateListener
-
public static interface AnimatorUpdateListener {
-
void onAnimationUpdate(ValueAnimator animator);
-
}
應用場景
這裏我們先提一個問題:給Button加一個動畫,讓Button在2秒內將寬帶從當前寬度增加到500dp,也行你會說,很簡單啊,直接用view動畫就可以實現,view動畫不是有個縮放動畫,但是你可以試試,view動畫是不支持對寬度和高度進行改變的。Button繼承自TextView,setWidth是對TextView的,所以直接對Button做setWidth是不行的。那麼要怎麼做呢?
針對上面的問題,官網api給出瞭如下的方案:
- 給你的對象加上get和set方法,如果你有權限的話
- 用一個類來包裝原始對象,間接提高get和set方法
- 採用ValueAnimator,監聽動畫執行過程,實現屬性的改變
有了上面的說明,我們大致明白了,要實現開始說的這個問題的效果,我們需要用一個間接的類來實現get和set方法或者自己實現一個ValueAnimator。
第一種,自己封裝一個類實現get和set方法,這也是我們常用的,拓展性強
-
public class ViewWrapper {
-
private View target;
-
public ViewWrapper(View target) {
-
this.target = target;
-
}
-
public int getWidth() {
-
return target.getLayoutParams().width;
-
}
-
public void setWidth(int width) {
-
target.getLayoutParams().width = width;
-
target.requestLayout();
-
}
-
}
第二種,採用ValueAnimator,監聽動畫過程。
-
private void startValueAnimator(final View target, final int start, final int end) {
-
ValueAnimator valueAnimator = ValueAnimator.ofInt(1, 100);
-
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
-
private IntEvaluator mEvaluation = new IntEvaluator();//新建一個整形估值器作爲臨時變量
-
-
@Override
-
public void onAnimationUpdate(ValueAnimator animation) {
-
//獲得當前動畫的進度值 1~100之間
-
int currentValue = (int) animation.getAnimatedValue();
-
//獲得當前進度佔整個動畫過程的比例,浮點型,0~1之間
-
float fraction = animation.getAnimatedFraction();
-
//調用估值器,通過比例計算出寬度
-
int targetWidth = mEvaluation.evaluate(fraction, start, end);
-
target.getLayoutParams().width = targetWidth;
-
//設置給作用的對象,刷新頁面
-
target.requestLayout();
-
}
-
});
-
}
屬性動畫的工作原理
屬性動畫的工作原理,主要是對作用的對象不斷的調用get/set方法來改變初始值和最終值,然後set到動畫屬性上即可。然後通過消息機制(Handler(不過這裏的Handler不是我們常用的handler,而是AnimationHandler,它其實本質就是一個Runable)和Looper去將動畫執行出來),通過代碼我們發現它調了JNI的代碼,不過這個我們不用關心,我們直接看ObjectAnimator.start()
-
private void start(boolean playBackwards) {
-
if(Looper.myLooper() == null) {
-
throw new AndroidRuntimeException("Animators may only be run on Looper threads");
-
} else {
-
this.mPlayingBackwards = playBackwards;
-
this.mCurrentIteration = 0;
-
this.mPlayingState = 0;
-
this.mStarted = true;
-
this.mStartedDelay = false;
-
((ArrayList)sPendingAnimations.get()).add(this);
-
if(this.mStartDelay == 0L) {
-
this.setCurrentPlayTime(this.getCurrentPlayTime());
-
this.mPlayingState = 0;
-
this.mRunning = true;
-
if(this.mListeners != null) {
-
ArrayList animationHandler = (ArrayList)this.mListeners.clone();
-
int numListeners = animationHandler.size();
-
-
for(int i = 0; i < numListeners; ++i) {
-
((AnimatorListener)animationHandler.get(i)).onAnimationStart(this);
-
}
-
}
-
}
-
-
ValueAnimator.AnimationHandler var5 = (ValueAnimator.AnimationHandler)sAnimationHandler.get();
-
if(var5 == null) {
-
var5 = new ValueAnimator.AnimationHandler(null);
-
sAnimationHandler.set(var5);
-
}
-
-
var5.sendEmptyMessage(0);
-
}
-
}
-
private static final ThreadLocal<ArrayList<ValueAnimator>> sAnimations = new ThreadLocal() {
-
protected ArrayList<ValueAnimator> initialValue() {
-
return new ArrayList();
-
}
-
};
-
private static final ThreadLocal<ArrayList<ValueAnimator>> sPendingAnimations = new ThreadLocal() {
-
protected ArrayList<ValueAnimator> initialValue() {
-
return new ArrayList();
-
}
-
};
-
private static final ThreadLocal<ArrayList<ValueAnimator>> sDelayedAnims = new ThreadLocal() {
-
protected ArrayList<ValueAnimator> initialValue() {
-
return new ArrayList();
-
}
-
};
-
private static final ThreadLocal<ArrayList<ValueAnimator>> sEndingAnims = new ThreadLocal() {
-
protected ArrayList<ValueAnimator> initialValue() {
-
return new ArrayList();
-
}
-
};
-
private static final ThreadLocal<ArrayList<ValueAnimator>> sReadyAnims = new ThreadLocal() {
-
protected ArrayList<ValueAnimator> initialValue() {
-
return new ArrayList();
-
}
-
};
這裏系統怎麼計算每一幀的動畫的呢,看看下面的代碼
-
void animateValue(float fraction) {
-
fraction = this.mInterpolator.getInterpolation(fraction);
-
this.mCurrentFraction = fraction;
-
int numValues = this.mValues.length;
-
-
int numListeners;
-
for(numListeners = 0; numListeners < numValues; ++numListeners) {
-
this.mValues[numListeners].calculateValue(fraction);
-
}
-
-
if(this.mUpdateListeners != null) {
-
numListeners = this.mUpdateListeners.size();
-
-
for(int i = 0; i < numListeners; ++i) {
-
((ValueAnimator.AnimatorUpdateListener)this.mUpdateListeners.get(i)).onAnimationUpdate(this);
-
}
-
}
-
-
}
不過我們知道要改變動畫,一定調用了get/set方法,那我們重點看下這相關的代碼。這段代碼在setProperty方法裏面
-
public void setProperty(Property property) {
-
if(this.mValues != null) {
-
PropertyValuesHolder valuesHolder = this.mValues[0];
-
String oldName = valuesHolder.getPropertyName();
-
valuesHolder.setProperty(property);
-
this.mValuesMap.remove(oldName);
-
this.mValuesMap.put(this.mPropertyName, valuesHolder);
-
}
-
-
if(this.mProperty != null) {
-
this.mPropertyName = property.getName();
-
}
-
-
this.mProperty = property;
-
this.mInitialized = false;
-
}
這裏有一個PropertyValuesHolder,顧名思義這是一個操作數據的類,和我們的adapter的Holder差不多,該方法的get方法主要用到了反射。
-
private void setupValue(Object target, Keyframe kf) {
-
if(this.mProperty != null) {
-
kf.setValue(this.mProperty.get(target));
-
}
-
-
try {
-
if(this.mGetter == null) {
-
Class e = target.getClass();
-
this.setupGetter(e);
-
}
-
-
kf.setValue(this.mGetter.invoke(target, new Object[0]));
-
} catch (InvocationTargetException var4) {
-
Log.e("PropertyValuesHolder", var4.toString());
-
} catch (IllegalAccessException var5) {
-
Log.e("PropertyValuesHolder", var5.toString());
-
}
-
-
}
代碼就看到這,有興趣的可以去看下源碼
使用屬性動畫需要注意的事項
使用幀動畫時,當圖片數量較多且圖片分辨率較大的時候容易出現OOM,需注意,儘量避免使用幀動畫。 使用無限循環的屬性動畫時,在Activity退出時即使停止,否則將導致Activity無法釋放從而造成內存泄露。 View動畫是對View的影像做動畫,並不是真正的改變了View的狀態,因此有時候會出現動畫完成後View無法隱藏(setVisibility(View.GONE)失效),這時候調用view.clearAnimation()清理View動畫即可解決。
不要使用px,使用px會導致不同設備上有不同的效果。 View動畫是對View的影像做動畫,View的真實位置沒有變動,也就導致點擊View動畫後的位置觸摸事件不會響應,屬性動畫不存在這個問題。 使用動畫的過程中,使用硬件加速可以提高動畫的流暢度。 動畫在3.0以下的系統存在兼容性問題,特殊場景可能無法正常工作,需做好適配工作。