绘图(四,view之绘图双缓冲)
来源:互联网 发布:怎么写单片机flash 编辑:程序博客网 时间:2024/04/30 22:42
前言
以下双缓冲的一些定义均是引用其他作者,不好意思,因为自己还没想出比较好的定义去描述双缓冲,同时也会引用一下其他作者的代码。关键最重要的是,我不认为,写别人已经写过的技术博客,是没有用的,也许对别人已经掌握了的,确实没有太大作用,但是对于我本人来说,我也是刚刚吸收,也有自己的想法,感觉其他作者写得不够详细,于是决定写一篇双缓冲的博客,不喜勿喷,谢谢支持。
双缓冲即在内存中创建一个与屏幕绘图区域一致的对象,先将图形绘制到内存中的这个对象上,再一次性将这个对象上的图形拷贝到屏幕上,这样能大大加快绘图的速度。
具体一点,双缓冲的核心技术就是先通过setBitmap方法将要绘制的所有的图形会知道一个Bitmap上,然后再来调用drawBitmap方法绘制出这个Bitmap,显示在屏幕上。
这一篇文章我会接在上一篇文章《绘图(三,进阶之绘制表盘)》继续深入讲解关于双缓冲的好处,当然如果没看《绘图(三,进阶之绘制表盘)》这篇文章的不要紧,我也会单独抽出关于双缓冲的技术使用,以及注意点。
1.双缓冲的使用场景
先看看《绘图(三,进阶之绘制表盘)》这篇文章的效果图。
为了做一个文字跟随表盘移动的动画,所以设计成了上述动画效果。但在很多实际应用外面红色弧长和表盘刻度是静止不变的。
效果如下:
好了,那么先看一下上一节的源码,由于以下的代码对上一节源码,稍微重构了一下,不过这次写得比上一节更加详细了。
先看构造函数
public DoubleCacaheView(Context context, AttributeSet attrs) { super(context, attrs); WindowManager wm = (WindowManager) context .getSystemService(Context.WINDOW_SERVICE); //获取屏幕宽度 width = getScreemWidth(wm); //获取圆弧半径,用于计算刻度使用 r = getRadius(width,mMargin,mMarginZhiZheng); //获取表盘最外圈红色弧长绘画范围 mRectFPanBiaoArc = getBiaoPanArcRectF(width,mMargin); //获取表盘刻度绘画范围 mRectFPanBiaoKeDu = getBiaoPanKeDu(width,mMargin,strokeWidth); //初始化画笔 initPaint(); //红色弧长路径 mPathPanBiaoArc = new Path(); //表盘刻度路径 mPathBiaoPanKeDu = new Path();}
如下几个方法的具体实现,其实就是在上一节基础上对其进行一下重构
//绘制在Path上的文本,也是就红色弧长的绘画drawTextOnPath(canvas,mPaint,mPathPanBiaoArc,mRectFPanBiaoArc,mSweepAnlge);//绘制在圆弧中心的小圆点drawCircleInCenter(canvas,mPaint);//绘制表盘上的刻度drawBianPanKeDu(canvas,mPaint,mPathBiaoPanKeDu,mRectFPanBiaoKeDu);//绘制指针drawZhiZheng(canvas,mPaint,width,r,mSweepAnlge);
onDraw具体实现
protected void onDraw(Canvas canvas) { // TODO Auto-generated method stub super.onDraw(canvas); canvas.drawColor(Color.WHITE); //绘制在Path上的文本 drawTextOnPath(canvas,mPaint,mPathPanBiaoArc,mRectFPanBiaoArc,mSweepAnlge); //绘制在圆弧中心的小圆点 drawCircleInCenter(canvas,mPaint); //绘制表盘上的刻度 drawBianPanKeDu(canvas,mPaint,mPathBiaoPanKeDu,mRectFPanBiaoKeDu); //绘制指针 drawZhiZheng(canvas,mPaint,width,r,mSweepAnlge); mPaint.setTextSize(50); mPaint.setTextAlign(Align.RIGHT); mPaint.setStyle(Paint.Style.FILL); if (mFlag) { // 如果扫描角度小于180度,将会发生重绘 if (mSweepAnlge <= 180) { canvas.drawTextOnPath("文件" + (int) mSweepAnlge + " ", mPathPanBiaoArc,60, -60, mPaint); mSweepAnlge += 2; invalidate(); } else { // 否则绘画完成,停止绘画 mFlag = false; canvas.drawTextOnPath("扫面完成 " , mPathPanBiaoArc, 60, -60, mPaint); mSweepAnlge = 0; } } else { mPaint.setTextSize(70); mPaint.setStrokeWidth(1); mPaint.setTextAlign(Align.CENTER); mPaint.setStyle(Paint.Style.STROKE); canvas.drawText("当前测度:" + (int) mSweepAnlge, width / 2,width / 2 + 100, mPaint); }}
不知大家看出来了没有,要想做到第二种效果,只有指针转动,而表盘和表盘刻度是不随用户动作而发生改变的,那么这段代码的运行效率并不是蛮高,对于静止不变的绘画能不能仅绘制一次呢?大家看这段代码
if (mSweepAnlge <= 180) { canvas.drawTextOnPath("文件" + (int) mSweepAnlge + " ", mPathPanBiaoArc,60, -60, mPaint); mSweepAnlge += 2; invalidate();}
当mSweepAnlge <= 180的时候都会调用invalidate()通知组件重绘,也就是重新执行onDraw方法,也就是说,我们每次改变的仅仅是指针的转动,但是绘画每次都重新绘制了表盘最外圈的红色弧长和表盘刻度,当试想一下如果,如果刻度比较复杂,计算比较耗时时,那么就会出现屏幕闪烁,非常不美观,当然此时的效果并没有出现屏幕闪烁,等一下我会举例说明的,于是就引出了双缓冲技术。
闪烁的原因
注:以下解释基于MFC的绘画原理,我们知道绘画底层引擎使用的都是OpenGL,所以关于不管是在哪个平台,绘画原理应该是差不多的。
因为窗体在刷新时,总要有一个擦除原来图象的过程,它利用背景色填充窗体绘图区,然后在调用新的绘图代码进行重绘,这样一擦一写造成了图象颜色的反差。当WM_PAINT的响应很频繁的时候,这种反差也就越发明显。于是我们就看到了闪烁现象。(当窗口由于任何原因需要重绘时,总是先用背景色将显示区清除,然后才调用OnPaint,而背景色往往与绘图内容反差很大,这样在短时间内背景色与显示图形的交替出现,使得显示窗口看起来在闪。如果将背景刷设置成NULL,这样无论怎样重绘图形都不会闪了。当然,这样做会使得窗口的显示乱成一团,因为重绘时没有背景色对原来绘制的图形进行清除,而又叠加上了新的图形。)
重绘的原理
重绘的原理:程序根据时间来刷新屏幕,这个时间由机器性能决定。
双缓冲技术
如果有一帧图形还没有完全绘制结束,程序就开始刷新屏幕这样就会造成瞬间屏幕闪烁画面很不美观,所以双缓冲的技术就诞生了。
那么在Android中怎么使用双缓冲技术呢?其实在最开头就已经说明了。
先通过setBitmap方法将要绘制的所有的图形会知道一个Bitmap上,然后再来调用drawBitmap方法绘制出这个Bitmap,显示在屏幕上。
我还是先举一个小例子来怎么使用双缓冲技术,然后再看如何把它运用到我们的表盘应用项目里来。
双缓冲举例
个人觉得下面一个例子非常好,我自己也有看《Android疯狂讲义》,这是上面的一个例子。
代码不多直接上整个源码了。
public class DrawView extends View { /** * 记录手触碰屏幕的X坐标 */ private float preX; /** * 记录手触碰屏幕的Y坐标 */ private float preY; /** * 绘制路径 */ private Path mPath; /** * 画笔 */ private Paint mPaint; /** * 新创建的画布 */ private Canvas cacheCanvas; /** * 和cacheCanvas一起使用,将新创建画布上的绘画保存在cacheBitmap对象中 */ private Bitmap cacheBitmap; public DrawView(Context context, AttributeSet attrs) { super(context, attrs); WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); DisplayMetrics outMetrics = new DisplayMetrics(); wm.getDefaultDisplay().getMetrics(outMetrics); //创建和屏幕一样大小的绘画区域 cacheBitmap = Bitmap.createBitmap(outMetrics.widthPixels, outMetrics.heightPixels, Config.ARGB_8888); cacheCanvas = new Canvas(); //将绘画对象和新创建的画布关联起来,于是在屏幕上的绘画将全部会知道cacaheBitmap对象中 cacheCanvas.setBitmap(cacheBitmap); mPath = new Path(); mPaint = new Paint(Paint.DITHER_FLAG); //防止抖动 mPaint.setAntiAlias(true); mPaint.setColor(Color.BLACK); mPaint.setStrokeWidth(1); mPaint.setStyle(Style.STROKE); mPaint.setDither(true); } @Override public boolean onTouchEvent(MotionEvent event) { // TODO Auto-generated method stub float x = event.getX(); float y = event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: mPath.moveTo(x, y); preX = x; preY = y; break; case MotionEvent.ACTION_MOVE: mPath.quadTo(preX, preY, x, y); //使线条更加平滑,内部运用“贝塞尔曲线”// mPath.lineTo( x, y); preX = x; preY = y; cacheCanvas.drawPath(mPath, mPaint); break; case MotionEvent.ACTION_UP: cacheCanvas.drawPath(mPath, mPaint); mPath.reset(); break; default: break; } invalidate(); return true; } @Override protected void onDraw(Canvas canvas) { // TODO Auto-generated method stub super.onDraw(canvas); Paint bmpPaint = new Paint(); //将cacaheBitmap绘制到该View的组件上 canvas.drawBitmap(cacheBitmap, 0, 0, bmpPaint); }}
代码其实很简单就是一个绘图的小demo,执行流程如下onTouchEvent->invalidate()(通知UI发生重绘)->onDraw。
我们所有的绘画操作内容都保存到了cacheBitmap对象中了,而onDraw要做的只是将bitmap对象显示出来即可。
试想一下如果要是不使用双缓冲的情况下,那么每次会话的路径都要使用path把他保存下来,然后调用invalidate通知UI重绘,将path里面的内容都绘制出来,当绘画路径越来越多的时候就会发现绘制速度越来越慢了,说不定会出现闪烁情况,时间再久一点而且容易造成内存溢出,因为path在不断add,要保存每一条绘制路,所以当出现这种情况时首要考虑双缓冲技术。
**最后总结一下双缓冲实现步骤:
1、在内存中创建与画布一致的缓冲区
2、在缓冲区画图
3、将缓冲区位图拷贝到当前画布上
4、释放内存缓冲区**
好吧,还是看看效果吧
表盘绘制优化
ok,终于可以对上一节的代码进行效率优化了,那么看看优化的代码部分吧
新增变量
/** * 指针到表盘的距离 */private float mMarginZhiZheng = 100.0f;/** * 保存绘画对象 */private Bitmap mBitmap ;/** * 先创建的画布 */private Canvas cacheCanvas ;/** * 屏幕高度 */private float height;
构造函数
public UseDoubleCacaheView(Context context, AttributeSet attrs) { super(context, attrs); WindowManager wm = (WindowManager) context .getSystemService(Context.WINDOW_SERVICE); //获取屏幕宽度 width = getScreemWidth(wm); //获取屏幕高度 height = getScreemHeght(wm); //获取圆弧半径,用于计算刻度使用 r = getRadius(width,mMargin,mMarginZhiZheng); //获取表盘最外圈红色弧长绘画范围 mRectFPanBiaoArc = getBiaoPanArcRectF(width,mMargin); //获取表盘刻度绘画范围 mRectFPanBiaoKeDu = getBiaoPanKeDu(width,mMargin,strokeWidth); //初始化画笔 initPaint(); //红色弧长路径 mPathPanBiaoArc = new Path(); //表盘刻度路径 mPathBiaoPanKeDu = new Path(); /** * 保存绘画对象 */ mBitmap = Bitmap.createBitmap((int)width, (int)height, Bitmap.Config.ARGB_8888); cacheCanvas = new Canvas(); cacheCanvas.setBitmap(mBitmap); //讲一下不变的部分一次性绘到mBitmap对象中 //绘制表盘 drawBiaoPan(cacheCanvas,mPaint,mPathPanBiaoArc,mRectFPanBiaoArc); //绘制在圆弧中心的小圆点 drawCircleInCenter(cacheCanvas,mPaint); //绘制表盘上的刻度 drawBianPanKeDu(cacheCanvas,mPaint,mPathBiaoPanKeDu,mRectFPanBiaoKeDu);}
onDraw
@Overrideprotected void onDraw(Canvas canvas) { // TODO Auto-generated method stub super.onDraw(canvas); canvas.drawColor(Color.WHITE); canvas.drawBitmap(mBitmap,0,0,mPaint); //绘制指针 drawZhiZheng(canvas,mPaint,width,r,mSweepAnlge); mPaint.setTextSize(50); mPaint.setTextAlign(Align.RIGHT); mPaint.setStyle(Paint.Style.FILL); if (mFlag) { // 如果扫描角度小于180度,将会发生重绘 if (mSweepAnlge <= 180) { canvas.drawTextOnPath("文件" + (int) mSweepAnlge + " ", mPathPanBiaoArc, 60, -60, mPaint); mSweepAnlge += 2; invalidate(); } else { // 否则绘画完成,停止绘画 mFlag = false; canvas.drawTextOnPath("扫面完成 ", mPathPanBiaoArc, 60, -60, mPaint); mSweepAnlge = 0; } } else { mPaint.setTextSize(70); mPaint.setStrokeWidth(1); mPaint.setTextAlign(Align.CENTER); mPaint.setStyle(Paint.Style.STROKE); canvas.drawText("当前测度:" + (int) mSweepAnlge, width / 2, width / 2 + 100, mPaint); }}
说明,如果要看到两种效果的不同,仅在MainActivity中任意一行代码即可
//setContentView(R.layout.activity_no_use); //没有使用双缓冲setContentView(R.layout.activity_use); //使用了双缓冲技术
源码:双缓冲.zip
- 绘图(四,view之绘图双缓冲)
- android 绘图之双缓冲绘图
- qt之双缓冲绘图
- 内存绘图、双缓冲绘图
- 内存绘图、双缓冲绘图
- MFC之缓冲绘图
- 自定义View之绘图篇(四):baseLine和FontMetrics
- 自定义View之绘图篇(四):baseLine和FontMetrics
- 关于双缓冲绘图之二
- 图形编辑器之双缓冲实现绘图
- 关于双缓冲绘图之二
- 双缓冲绘图
- GDI双缓冲绘图
- vc双缓冲绘图?
- 双缓冲绘图
- GDI双缓冲绘图
- vc++ 双缓冲绘图
- MFC绘图,双缓冲
- ggplot2-一页多图(不同来源, 灵活绘制)
- JavaScript 对象深入学习总结
- ListBox控件实现上移、下移、循环上移、循环下移操作
- 浅谈高斯过程回归
- 机器学习基础(四十五)—— 模拟退火(Simulated Annealing)
- 绘图(四,view之绘图双缓冲)
- Spring的FactoryBean详解
- VS 2015 Update 2 构建 Android 程序遇到的一些问题
- iOS中 超简单抽屉效果(MMDrawerController)的实现
- Java数组之基础(一)
- 第5周项目3-时间类 (2)
- HDOJ 5645 DZY Loves Balls
- win7+QT creator+openCV配置和使用
- POJ 1470 Tarjan算法