到目前为止,在本系列中已经讲述了一些帧频的方法和尽可能保持我们帧频的断言。这篇文章我们将一起研究如何计量帧频,并且探索布局和视图的改变会对帧频产生哪些影响。
我们以精确的定义帧频来开始我们这篇文章。当运行任何类型的动画时,帧频是很多不同的帧,这些帧每秒显示一个。帧频越高,动画显示的越流畅。大部分人能接受的单个帧大约是10-12fps,但是为了使动画让人感觉不到断续,我们需要更大的值。电影史24帧每秒,而电视是24-30帧每秒(但是300fps是标准的变量);许多视频游戏能超过30fps。
然而我们不需要达到电视广播或者游戏的动画标准;为了使动画表现更完美,我们应该让我们动画尽可能的流畅,我们应该指定最佳的帧频。
当在Android中使用动画时,不考虑我们用的动画框架,基本的流程是:
1.通过计量和定位所以的视图对象来初始化布局文件。
2.调用onDraw()方法绘制所以视图。
3.执行动画的对象将改变值,这些值将影响基于运行时间的onDraw()绘制。
4.回到第二步。
这个循环直到动画完成。视图层次销毁,或者布局改变。当我们需要布局时,上面的流程将重新进行。
帧频越低,onDraw()执行越快。所以我们应该最希望的是保持onDraw()尽可能的高效。
到底我们该如何计量帧频呢?最简单的办法是计算onDraw()被调用的次数除以运行时间能绘制的帧的个数,那样我们就能够得到帧频。然而,我们将介绍下面的例子TimingLogger来使计算更加简单,将我们想要计算量比较大的操作放到代码外面。
我们创建一个BlurredTextView,继承自TextView,在它里面计算onDraw()调用次数。
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,并且添加一个按钮来开启和停止动画。
<?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中计算的原因)。
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; } . . . }
如果我们运行上面的代码,可以发现动画时流畅的(尽管的视频看上去在这个设备上不是流畅的),但是模糊的背景是移动的在定制的视图上。这种情况是理想的,因为我们还没有做任何的模糊处理。
但是所有重要的帧频输出结果是这样的:
Elapsed time 6.0 sec, local frames 1044, frames 1, 0.2 fps
现在这是不正确的。视频中的值要大于0.2fps。这个本地帧表明onPreDraw()方法被调用的次数。帧表明onDraw()调用的次数。
我怀疑的是有一个优化发生了。因为我们的TextView并没有改变。一个ViewOverlay被使用了。导致onDraw()没有被调用。因此她的调用数没有增加。
然而,我们看到onPreDraw()被调用的次数了。这是基于我们手动计算的值。我们得到了174fps,这是相当理想的值。但是硬件要相当的好的时候,这种优化和提升才能体现真正的价值。
下篇文章我们将介绍动态模糊和帧频的影响。