一件开心的事:
哈哈哈,先容我高兴一会。就在昨天,当我打开csdn博客的时候,发现了一件让我激动了半天的事,就是那个男人竟然关注了我,关注了我,关注了我。有图有真相:
这感觉无法言表,不说了,我去敲几行代码掩饰一下内心的喜悦。
对于FlickerProgressBar请看:
1.问题描述
1.1提出问题
在FlickerProgressBar实现过程中对于进度的相关绘制有同学提出了疑问@王少星,就是绘制进度的时候都需要调用Bitmap.createBitmap()来重新创建进度bitmap,是否有更好的方法去处理?首先感谢该同学的提问。这个问题在开发过程中我也意识到了,不过当时没有想到其他更好的方法,只想着先实现效果。
1.2问题所在
最开始进度绘制是这样的
private void drawProgress() {
bgPaint.setStyle(Paint.Style.FILL);
bgPaint.setStrokeWidth(0);
bgPaint.setColor(progressColor);
float right = (progress / MAX_PROGRESS) * getMeasuredWidth();
pgBitmap = Bitmap.createBitmap((int) Math.max(right, 1), getMeasuredHeight(), Bitmap.Config.ARGB_8888);
pgCanvas = new Canvas(pgBitmap);
pgCanvas.drawColor(progressColor);
if(!isStop){
bgPaint.setXfermode(xfermode);
pgCanvas.drawBitmap(flikerBitmap, flickerLeft, 0, bgPaint);
bgPaint.setXfermode(null);
}
}
可以看到每次都根据当前进度创建了一个新的bitmap,pgBitmap = Bitmap.createBitmap((int) Math.max(right, 1), getMeasuredHeight(), Bitmap.Config.ARGB_8888);
,先不说这样做有什么不好,来看看下面这幅图
可以看到在下载过程中,也就是进度条绘制过程中,内存分配图成锯齿形。然后再看数据统计
Free[1.28 MB]
Allocated[31.03 MB]
这里出现了明显的内存占用过大和内存抖动问题。
2.问题解决
2.1方法一
首先需要明白一点是这里需要创建一个bitmap,和滑块bitmap进行SRC_ATOP模式绘制。
然后,这里的绘制进度需要一个根据当前进度计算的宽度创建的一个bitmap,那么突破口就是bitmap和宽度。
查看Bitmap的方法,找到了两个可以修改宽度的方法,一个是
setWidth (int width)
,另一个是
reconfigure (int width, int height, Bitmap.Config config)
。
setWidth()方法在内部调用的也是reconfigure()方法。
修改:
pgBitmap只用在init()方法中初始化一次
pgBitmap = Bitmap.createBitmap(getMeasuredWidth(), getMeasuredHeight(), Bitmap.Config.ARGB_8888);
注意:
- setWidth和reconfigure方法中的宽度参数不能大于创建时bitmap的宽度,不然会抛异常。
宽高均为控件宽高,而不是光满足大于0的某一个值,不然调用reconfigure 和 setWidth抛异常Bitmap not large enough to support new configuration
- 这两个方法在api19才加进来的,所以对于低版本的支持不是很好
方法一修改进度绘制方法:
@TargetApi(Build.VERSION_CODES.KITKAT)
private void drawProgress() {
bgPaint.setStyle(Paint.Style.FILL);
bgPaint.setStrokeWidth(0);
bgPaint.setColor(progressColor);
float right = (progress / MAX_PROGRESS) * getMeasuredWidth();
pgBitmap.setWidth((int) Math.max(1, right));
//pgBitmap.reconfigure((int) Math.max(right, 1), getMeasuredHeight(), Bitmap.Config.ARGB_8888);
pgCanvas = new Canvas(pgBitmap);
pgCanvas.drawColor(progressColor);
if(!isStop){
bgPaint.setXfermode(xfermode);
pgCanvas.drawBitmap(flikerBitmap, flickerLeft, 0, bgPaint);
bgPaint.setXfermode(null);
}
}
2.2 方法二
方法一能很好的解决上面提到的问题,但是对于低版本支持不是很好。所以需要寻找更好的方法。
最后,问题的解决根本再一次落到了canvas的clipRect方法。让我深深的感受到这个方法的强大。
思路:在初始化方法中就创建pgBitmap,并且宽高为控件宽高,并初始化pgCanvas。
在绘制进度的时候在调用pgCanvas的clipRect方法,然后在截取的宽度中进行相关绘制。这样可以很好的解决问题了,也不用考虑bitmap的create方法中宽高必须大于0和抛异常了。
初始化方法
private void init() {
bgPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
textPaint.setTextSize(textSize);
textBouds = new Rect();
progressColor = loadingColor;
flikerBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.flicker);
flickerLeft = -flikerBitmap.getWidth();
//这里就进行初始化,只用创建一次就可以了
pgBitmap = Bitmap.createBitmap(getMeasuredWidth(), getMeasuredHeight(), Bitmap.Config.ARGB_8888);
pgCanvas = new Canvas(pgBitmap);
thread = new Thread(this);
thread.start();
}
方法二修改进度绘制方法:
private void drawProgress() {
bgPaint.setStyle(Paint.Style.FILL);
bgPaint.setStrokeWidth(0);
bgPaint.setColor(progressColor);
float right = (progress / MAX_PROGRESS) * getMeasuredWidth();
pgCanvas.save(Canvas.CLIP_SAVE_FLAG);
pgCanvas.clipRect(0, 0, right, getMeasuredHeight());
pgCanvas.drawColor(progressColor);
pgCanvas.restore();
if(!isStop){
bgPaint.setXfermode(xfermode);
pgCanvas.drawBitmap(flikerBitmap, flickerLeft, 0, bgPaint);
bgPaint.setXfermode(null);
}
}
3.修改后性能
对比修改前的,效果对比很明显。
这里内存分配非常平稳,没有丝毫抖动。再看看数据:
Free[8.37 MB]
Allocated[19.52 MB]
分配内存减少约10M,空闲内存提高约6.5M。cpu占用区别不是很明显。