到目前為止,在本系列中已經講述了一些幀頻的方法和儘可能保持我們幀頻的斷言。這篇文章我們將一起研究如何計量幀頻,並且探索佈局和檢視的改變會對幀頻產生哪些影響。
我們以精確的定義幀頻來開始我們這篇文章。當執行任何型別的動畫時,幀頻是很多不同的幀,這些幀每秒顯示一個。幀頻越高,動畫顯示的越流暢。大部分人能接受的單個幀大約是10-12fps,但是為了使動畫讓人感覺不到斷續,我們需要更大的值。電影史24幀每秒,而電視是24-30幀每秒(但是300fps是標準的變數);許多視訊遊戲能超過30fps。
然而我們不需要達到電視廣播或者遊戲的動畫標準;為了使動畫表現更完美,我們應該讓我們動畫儘可能的流暢,我們應該指定最佳的幀頻。
當在Android中使用動畫時,不考慮我們用的動畫框架,基本的流程是:
1.通過計量和定位所以的檢視物件來初始化佈局檔案。
2.呼叫onDraw()方法繪製所以檢視。
3.執行動畫的物件將改變值,這些值將影響基於執行時間的onDraw()繪製。
4.回到第二步。
這個迴圈直到動畫完成。檢視層次銷燬,或者佈局改變。當我們需要佈局時,上面的流程將重新進行。
幀頻越低,onDraw()執行越快。所以我們應該最希望的是保持onDraw()儘可能的高效。
到底我們該如何計量幀頻呢?最簡單的辦法是計算onDraw()被呼叫的次數除以執行時間能繪製的幀的個數,那樣我們就能夠得到幀頻。然而,我們將介紹下面的例子TimingLogger來使計算更加簡單,將我們想要計算量比較大的操作放到程式碼外面。
我們建立一個BlurredTextView,繼承自TextView,在它裡面計算onDraw()呼叫次數。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
; html-script: false ] public class BlurredTextView extends TextView{ private long mFrameCount = 0; public BlurredTextView(Context context) { this(context, null, -1); } public BlurredTextView(Context context, AttributeSet attrs) { super(context, attrs, -1); } public BlurredTextView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); mFrameCount++; } public long getFrameCount() { return mFrameCount; } public void setFrameCount(long frameCount) { this.mFrameCount = frameCount; } } |
現在我們使用我們之前的控制佈局來代替標準的TextView,並且新增一個按鈕來開啟和停止動畫。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
; html-script: false ] <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:gravity="center" android:orientation="vertical" > <ImageView android:id="@+id/image" android:src="@drawable/broadstairs" android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="matrix" android:layout_centerInParent="true"/> <com.stylingandroid.blurring.BlurredTextView android:id="@+id/text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/hello" android:layout_centerInParent="true" android:textColor="@android:color/white" android:textStyle="bold" android:textSize="36sp"/> <Button android:id="@+id/animate" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:layout_alignParentRight="true" android:text="@string/start"/> </RelativeLayout> |
最後我們需要更新MainActivity類來處理開啟動畫和停止動畫和計算幀數(這是為什麼要在MainActivity和定製的TextView中計算的原因)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 |
; html-script: false ] public class MainActivity extends Activity { private static final String TAG = "Blurring"; private ImageView mImage; private BlurredTextView mText; private ObjectAnimator mAnimator = null; private long mStart = 0; private long mFrameCount = 0; private OnPreDrawListener mPreDrawListener = new OnPreDrawListener() { @Override public boolean onPreDraw() { if(mFrameCount == 0) { Drawable drawable = mImage.getDrawable(); if (drawable != null && drawable instanceof BitmapDrawable) { Bitmap bitmap = ((BitmapDrawable) drawable) .getBitmap(); if (bitmap != null) { blur(bitmap, mText, 25); //blurJava(bitmap, mText, 25); } } } mFrameCount++; return true; } }; . . . /** * Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); . . . mText.setText("Animated"); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (mAnimator == null) { button.setText(R.string.stop); startAnimation(); } else { button.setText(R.string.start); stopAnimation(); } } }); } private void startAnimation() { mStart = System.currentTimeMillis(); mText.setFrameCount(0); mFrameCount = 0; mAnimator = ObjectAnimator.ofFloat(mText, "translationY", 0, -200); mAnimator.setDuration(1000); mAnimator.setRepeatMode(ValueAnimator.REVERSE); mAnimator.setRepeatCount(ValueAnimator.INFINITE); mAnimator.start(); } private void stopAnimation() { mAnimator.cancel(); float elapsed = (float) (System.currentTimeMillis() - mStart) / 1000.0f; long framecount = mText.getFrameCount(); float fps = framecount / elapsed; Log.d(TAG, getString(R.string.framerate, elapsed, mFrameCount, framecount, fps)); mAnimator = null; } . . . } |
如果我們執行上面的程式碼,可以發現動畫時流暢的(儘管的視訊看上去在這個裝置上不是流暢的),但是模糊的背景是移動的在定製的檢視上。這種情況是理想的,因為我們還沒有做任何的模糊處理。
但是所有重要的幀頻輸出結果是這樣的:
1 2 |
; html-script: false ] Elapsed time 6.0 sec, local frames 1044, frames 1, 0.2 fps |
現在這是不正確的。視訊中的值要大於0.2fps。這個本地幀表明onPreDraw()方法被呼叫的次數。幀表明onDraw()呼叫的次數。
我懷疑的是有一個優化發生了。因為我們的TextView並沒有改變。一個ViewOverlay被使用了。導致onDraw()沒有被呼叫。因此她的呼叫數沒有增加。
然而,我們看到onPreDraw()被呼叫的次數了。這是基於我們手動計算的值。我們得到了174fps,這是相當理想的值。但是硬體要相當的好的時候,這種優化和提升才能體現真正的價值。
下篇文章我們將介紹動態模糊和幀頻的影響。