動畫類型
- 幀動畫:通過序列幀實現,間隔一段時間播放一張圖片。實現簡單,但是性能最差
- 補間動畫:輸入動畫類型(透明度,大小,移動,旋轉),開始參數和結束參數。通過插值器控制變化速度。實現相對簡單,但是並沒有真正改變view的所在位置,只是顯示變化。
- 屬性動畫:ValueAnimator,ObjectAnimator和animatorSet。動畫優化的核心,見後續詳情
幀動畫優化
實現
動畫xml文件:
<?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/ic_launcher"
android:duration="200" />
<item
android:drawable="@mipmap/ic_launcher"
android:duration="300" />
<item
android:drawable="@mipmap/ic_launcher"
android:duration="400" />
<item
android:drawable="@mipmap/ic_launcher"
android:duration="600" />
<item
android:drawable="@mipmap/ic_launcher"
android:duration="700" />
<item
android:drawable="@mipmap/ic_launcher"
android:duration="800" />
</animation-list>
調用
imageView.setImageResource(R.drawable.anim_test);
AnimationDrawable animationDrawable = (AnimationDrawable) imageView.getDrawable();
animationDrawable.start();
關注點:
- oneshot控制是否循環,建議不用試hanlder控制循環
- 每幀的時間都可以不一樣。可以用於控制停頓
- 代碼動態添加幀:AnimationDrawable.addFrame(Drawable,time)
- 所有的view的繪製,都需要獲取焦點才能成功(詳見window機制)。所以需要調用
animationDrawable.start();才能執行動畫
優化
幀動畫實際上是很難優化的,因爲固定要讀取bitmap。主要有以下兩種優化方式:
- 內存優化:控制bitmap的總數,不使用的bitmap主動回收了,但是這樣會導致性能的下降。適合與低內存的手機;另一方面,動畫人員給的序列幀是通過工具生成的,有可能生成相同的圖片。我們可以複用他們,比如上下左右對稱的圖片,旋轉一週,肯定有存在相同的4張圖片。
- 讀取優化:提前讀取一些圖片。
補間動畫
類型:直接引用他人總結的
實現:
- 在xml中配置,然後通過 AnimationUtils.loadAnimation(this, R.anim.view_animation)加載
- 代碼中設置起始參數,結束參數和插值器
詳情參考:https://www.jianshu.com/p/733532041f46
個人認爲關於補間動畫的實現已經寫的很詳細了。
關注點
- 補間動畫適合於效果單一的動畫,比如單純的平移,旋轉等
- 如果效果複雜。建議修改爲屬性動畫再優化,因爲每個類型,每個view就需要一個相應動畫。這樣就會出現很多的動畫實例
屬性動畫
- ValueAnimator:值動畫,實際上只是根據時間規律生成一系列數值(int,float,alpha,argb等),然後我們就可以把這些值設置給相應的view操作。讓view動起來
- ObjectAnimator:對象動畫。是ValueAnimator的實現類之一,需要綁定view對象,通過set/get方法自動把生成的一系列數值設置給相應的參數。set/get方法支持原有的方法,也支持自定義。
- animatorSet:動畫組合。用戶組合多個屬性動畫,讓他們同時進行或者排序進行。比如多個動畫同時進行,通過animatorSet就可以一時間產生多種數值,提高效率。原來的方式爲各自的動畫各自產生序列。
適用場景:
ObjectAnimator適用於只有一個view。如果多個view,爲了更好的性能,則建議適用ValueAnimator
ValueAnimator使用相對麻煩一些,適合於多view
優化
原則:儘量減少動畫對象
居於這個原則有以下優化方法:
- 使用animatorSet:通過set統一控制的動畫的執行和順序,但是animatorSet並不支持循環。如果非要循環,建議外部加一層handle,因爲動畫有可能被中斷。
- 對於單view,多效果使用PropertyValuesHolder結合ObjectAnimator。可以只用一個動畫對象實現多個效果。以下實現消失動畫(逐漸變小,逐漸透明直至完全消失)
PropertyValuesHolder scaleValuesHolder = PropertyValuesHolder.ofFloat("scale", 1,0); PropertyValuesHolder alphaValuesHolder = PropertyValuesHolder.ofFloat("alpha", 1,0); ObjectAnimator.ofPropertyValuesHolder(this, scaleValuesHolder, alphaValuesHolder).start();
- 對於多view,多效果使用PropertyValuesHolder結合ValueAnimator。可以只用一個動畫對象實現多個view,多個效果。以下實現兩個view的消失動畫(逐漸變小,逐漸透明直至完全消失)
final TextView textView1 = findViewById(R.id.tv_test); final TextView textView2 = findViewById(R.id.tv_test2); PropertyValuesHolder scaleValuesHolder = PropertyValuesHolder.ofFloat("scale", 1,0); PropertyValuesHolder alphaValuesHolder = PropertyValuesHolder.ofFloat("alpha", 1,0); ValueAnimator valueAnimator = ValueAnimator.ofPropertyValuesHolder(scaleValuesHolder,alphaValuesHolder); valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { textView1.setAlpha((float)animation.getAnimatedValue("alpha")); textView1.setScaleX((float)animation.getAnimatedValue("scale")); textView1.setScaleY((float)animation.getAnimatedValue("scale")); textView2.setAlpha((float)animation.getAnimatedValue("alpha")); textView2.setScaleX((float)animation.getAnimatedValue("scale")); textView2.setScaleY((float)animation.getAnimatedValue("scale")); } });
- 如果動畫更復雜,比如先執行scale(500ms),動畫開始後100ms執行透明度動畫(400ms),然後停頓1.5s再循環。這樣的效果以上的方式都無法通過一個動畫實現。可以使用keyframe。
final TextView textView1 = findViewById(R.id.tv_test); Keyframe scaleKf0 = Keyframe.ofFloat(0f, 0); Keyframe scaleKf1 = Keyframe.ofFloat(0.25f, 1); Keyframe scaleKf2 = Keyframe.ofFloat(1f, 1); PropertyValuesHolder scaleValuesHolder = PropertyValuesHolder.ofKeyframe("scale", scaleKf0, scaleKf1, scaleKf2); Keyframe alphaKf0 = Keyframe.ofFloat(0f, 0); Keyframe alphaKf1 = Keyframe.ofFloat(0.05f, 0); Keyframe alphaKf2 = Keyframe.ofFloat(0.25f, 1); Keyframe alphaKf3 = Keyframe.ofFloat(1f, 1); PropertyValuesHolder alphaValuesHolder = PropertyValuesHolder.ofKeyframe("alpha",alphaKf0, alphaKf1, alphaKf2, alphaKf3); ObjectAnimator.ofPropertyValuesHolder(textView1,scaleValuesHolder,alphaValuesHolder);
keyframe第一個參數爲進度百分比(受插值器控制,如果不是LinearInterpolator,可能很快就到達一半了),第二參數爲value。上一幀到下一幀如果不一樣可以實現動畫,如果一樣不處理。還可以獨立設置每一個關鍵幀的插值器。同樣,單view使用ObjectAnimator,多view使用valueAnimator。再複雜的動畫都可以通過keyframe用最少的動畫實現效果。
最終篇-渲染優化
以上的優化思路還停留在不改變view渲染的。有幾個動畫元素就用多少個view。只是減少了動畫對象。但是根據view的渲染機制,並沒有優化多少,就是生成插值的內存和性能優化了。效果並不是很好。
以下實踐總結的優化:
- 通過canvas,Paint畫圖/畫圓/畫矩形等等,結合valueAnimator,改變值以改變位置,大小或者形狀。調用invalidate刷新view。
- 通過canvas,Paint畫圖/畫圓/畫矩形等等,結合ObjectAnimator,提供相應的set/get方法。
該方式使用較爲複雜,可以結合以上的所以情況,無論多麼複雜,都可以用一個view,最少的動畫對象實現。需要深刻了解自定義view,view渲染和動畫機制。將三者結合,實現最佳的性能。
還有很多細節,set/get方法不能混淆。使用keyframe時,如果效果沒有發現變化,就不用調用invalidate
以下是實現兩個圓,同時平移和縮放。
package com.example.admin.myapplication;
import android.animation.PropertyValuesHolder;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;
public class MyAnimView extends View {
private Paint mPaint;
private float mX;
private float mY;
private float mRadius;
public MyAnimView(Context context) {
super(context);
init();
}
public MyAnimView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public MyAnimView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mPaint = new Paint();
}
public void startAnim() {
PropertyValuesHolder scaleValuesHolder = PropertyValuesHolder.ofFloat("scale", 0, 100);
PropertyValuesHolder moveValuesHolder = PropertyValuesHolder.ofFloat("move", 0, 200);
ValueAnimator valueAnimator = ValueAnimator.ofPropertyValuesHolder(scaleValuesHolder, moveValuesHolder);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mRadius = (float) animation.getAnimatedValue("scale");
mX = (float) animation.getAnimatedValue("move");
mY = (float) animation.getAnimatedValue("move");
invalidate();
}
});
valueAnimator.setDuration(1000);
valueAnimator.setRepeatCount(ValueAnimator.INFINITE);
valueAnimator.start();
}
@Override
public void onDraw(Canvas canvas) {
canvas.drawCircle(mX, mY, mRadius, mPaint);
canvas.drawCircle(mX + 100, mY + 100, mRadius, mPaint);
}
}
總結
場景 | 實現方式 | 說明 |
時間緊張,沒追求 | 幀動畫 | 實現簡單,性能極差 |
一個view,簡單的動效 |
補間動畫 |
實現簡單,性能一半,不能改變點擊響應區域 |
一個view,簡單的動效,需要改變點擊響應區域 | ObjectAnimator | 實現較簡單,性能一般 |
一個view,多個動效 | ObjectAnimator結合PropertyValuesHolder | 實現難度一般,性能優秀 |
多個view,多個動效 | valueAnimator結合PropertyValuesHolder | 實現難度一般,性能優秀 |
一個view,多個動效,但是啓動時間,結束時間不一樣 | ObjectAnimator,PropertyValuesHolder結合keyframe | 實現難度較難,性能很優秀 |
多個view,多個動效,但是啓動時間,結束時間不一樣 | valueAnimator,PropertyValuesHolder結合keyframe | 實現難度較難,性能很優秀 |
複雜的動畫的最佳性能優化 | canvas,paint結合valueAnimator或者PropertyValuesHolder | 實現最難,性能最佳 |
如果能用簡單的動畫實現就簡單實現,追求性能的時候再考慮複雜的實現的。