Android仿微信清理内存图表动画(解决surfaceView屏幕闪烁问题)demo实例详解

概述最近接了一个项目其中有功能要实现一个清理内存,要求和微信的效果一样。于是想到用surfaceView而不是继承view。下面小编给大家解析下实现思路。

最近接了一个项目其中有功能要实现一个清理内存,要求和微信的效果一样。于是想到用surfaceVIEw而不是继承vIEw。下面小编给大家解析下实现思路。

surfaceVIEw是为了解决频繁绘制动画产生了闪烁,而采用了双缓冲机制,即A、B两个缓冲轮流显示在画布上,同时,使用不当,同样容易产生闪烁,这是由于A、B中有一个缓冲没有改变。

在我写这个vIEw的时候就遇到了这个问题,研究了好久终于解决。

首先说一下思路:

微信清理缓存的动画是:

一个圆环不停的转动,同时中间有文字显示-->加载完成后,出现一个慢慢展开的图标,同时第一块区域要突出一点。

这就是微信的动画效果。但是具体实现是怎么样的呢?

下面说一下我实现的方法:

1、旋转圆环:

这个圆环由两部分组成,一个圆和一个深灰的弧线,而弧线一直在转动,产生了圆环在旋转的效果。

因此,这个就很好解决了。我们画两个图形,一个圆形,一个弧线,而弧线的角度不停的变化就产了旋转的效果。为了让它不断变化,就要用到一个动画类ValueAnimator,通过这个类不停的给出一个角度,然后我们不停的绘制,就可以完成这个效果。

2、文字:

文字是和圆环是一部分的,当然他们其实应该同时绘制。但是每次绘制,为了避免文字的重叠,我们需要将canvas清除。

我们通过计算出总的旋转动画时间和一个由绘制动画开始,到具体当前绘制时的时间差来模拟出百分比。

3、会展开的图表

这个是这个效果的难点部分,这里遇到的问题也比较多。

这是一个慢慢展开的动画,看似是一个圆在慢慢显现,其实不是。只不过是一个扇形再不停的旋转,但是没有清除之前的画布,这样产生了平铺效果。而第一块区域的扇形较大只不过是半径大一点而已。因此这部分我们同样利用ValueAnimator,也可以画出。

通过对第一个旋转动画进行监听,当第一个效果结束的时候,第二个图表的动画开始进行。

4、具体的内存大小信息:

这个比较简单,只需要确定坐标即可,但是在写的时候也遇到了闪烁情况。

下面是具体实现

最初版本:

package xiaoqi.expandablechartvIEw; import androID.animation.Animator; import androID.animation.PropertyValuesHolder; import androID.animation.ValueAnimator; import androID.content.Context; import androID.graphics.Canvas; import androID.graphics.color; import androID.graphics.Paint; import androID.graphics.PixelFormat; import androID.graphics.PorterDuff; import androID.graphics.RectF; import androID.util.AttributeSet; import androID.vIEw.SurfaceHolder; import androID.vIEw.SurfaceVIEw; import androID.vIEw.animation.linearInterpolator; public class ChartVIEw extends SurfaceVIEw implements SurfaceHolder.Callback { private Context context; private SurfaceHolder holder; private ValueAnimator chartAnimator; private ValueAnimator circleAnimator; //中间内存信息方块的坐标 private float centerDetailleft; private float centerDetailtop; private float centerDetailRight; private float centerDetailBottom; //chart外接正方形坐标 private float chartleft; private float charttop; private float chartRight; private float chartBottom; //起始角度 private float startAngle = 270; //半径 private float radius; //各区域角度 private float area1Angle; private float area2Angle; //区域的量 private float total; private float area1; private float area2; private long time; private int repeatCount = 2; //是否为第一次显示,用于防止surface闪烁 private boolean area1IsFirstShow = true; private boolean area2IsFirstShow = true; //大扇形外接正方形 private RectF rectF; //小扇形外接正方形 private RectF rectF2; private Paint area1Paint; private Paint area2Paint; private Paint area3Paint; private Paint circlePaint; private Paint arcPaint; private Paint loadingPaint; private Paint textPaint; private static final int CIRCLE_DURATION = 1000; public ChartVIEw(Context context) { super(context); this.context = context; init(); } public ChartVIEw(Context context,AttributeSet attrs) { super(context,attrs); this.context = context; init(); } public ChartVIEw(Context context,AttributeSet attrs,int defStyleAttr) { super(context,attrs,defStyleAttr); this.context = context; init(); } private voID init() { radius = Utility.dip2px(context,100); holder = getHolder(); holder.addCallback(this); setZOrderOntop(true); holder.setFormat(PixelFormat.TRANSLUCENT); initPaint(); initAnimator(); } private voID initAnimator() { PropertyValuesHolder angleValues = PropertyValuesHolder.offloat("angle",0f,360f); chartAnimator = ValueAnimator.ofPropertyValuesHolder(angleValues); chartAnimator.setDuration(2000); chartAnimator.setInterpolator(new linearInterpolator()); chartAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @OverrIDe public voID onAnimationUpdate(ValueAnimator animation) { float angle = obj2float(animation.getAnimatedValue("angle")); Canvas canvas = holder.lockCanvas(null); if(canvas != null){ // canvas.drawcolor(color.transparent,PorterDuff.Mode.CLEAR); drawDetail(canvas); // canvas.setDrawFilter(new PaintFlagsDrawFilter(0,Paint.ANTI_AliAS_FLAG | Paint.FILTER_BITMAP_FLAG)); // if (!area1IsFirstShow) { // canvas.drawArc(rectF,startAngle,area1Angle,true,area1Paint); // } // if (!area2IsFirstShow) { // canvas.drawArc(rectF2,area1Angle + startAngle,area2Angle,area2Paint); // } if (angle < area1Angle) { canvas.drawArc(rectF,angle,area1Paint); } else if (angle <= area2Angle + area1Angle) { // if (area1IsFirstShow) { // area1IsFirstShow = false; // canvas.drawArc(rectF,area1Paint); // } else { canvas.drawArc(rectF2,startAngle+area1Angle,angle - area1Angle,area2Paint); // } } else { // if (area2IsFirstShow) { // area2IsFirstShow = false; // canvas.drawArc(rectF2,area2Paint); // } else { canvas.drawArc(rectF2,startAngle + area1Angle + area2Angle,angle - area2Angle - area1Angle,area3Paint); // } } holder.unlockCanvasAndPost(canvas); } } }); circleAnimator = ValueAnimator.ofPropertyValuesHolder(angleValues); circleAnimator.setInterpolator(new linearInterpolator()); circleAnimator.setDuration(CIRCLE_DURATION); circleAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @OverrIDe public voID onAnimationUpdate(ValueAnimator animation) { float angle = obj2float(animation.getAnimatedValue("angle")); Canvas canvas = holder.lockCanvas(null); if(canvas != null){ long NowTime = System.currentTimeMillis(); int rate = (int) (NowTime - time) / (CIRCLE_DURATION * (repeatCount + 1) / 100); if (rate <= 100) { canvas.drawcolor(color.transparent,PorterDuff.Mode.CLEAR); canvas.drawText("正在加载" + rate + "%",getMeasureDWIDth() / 2 - radius / 2,getMeasuredHeight() / 2,loadingPaint); } canvas.drawCircle(getMeasureDWIDth() / 2,getMeasuredHeight() / 2 - Utility.dip2px(context,10),radius,circlePaint); canvas.drawArc(rectF2,180 + angle,30,false,arcPaint); holder.unlockCanvasAndPost(canvas); } } }); circleAnimator.addListener(new Animator.AnimatorListener() { @OverrIDe public voID onAnimationStart(Animator animation) { time = System.currentTimeMillis(); } @OverrIDe public voID onAnimationEnd(Animator animation) { chartAnimator.start(); } @OverrIDe public voID onAnimationCancel(Animator animation) { } @OverrIDe public voID onAnimationRepeat(Animator animation) { } }); } private voID initPaint() { area1Paint = new Paint(); area1Paint.setAntiAlias(true); area1Paint.setStyle(Paint.Style.FILL); area1Paint.setTextSize((Utility.dip2px(context,15))); area1Paint.setcolor(context.getResources().getcolor(R.color.background_blue)); area2Paint = new Paint(); area2Paint.setAntiAlias(true); area2Paint.setStyle(Paint.Style.FILL); area2Paint.setTextSize((Utility.dip2px(context,15))); area2Paint.setcolor(context.getResources().getcolor(R.color.chart_blue)); area3Paint = new Paint(); area3Paint.setAntiAlias(true); area3Paint.setStyle(Paint.Style.FILL); area3Paint.setTextSize((Utility.dip2px(context,15))); area3Paint.setcolor(context.getResources().getcolor(R.color.light_gary)); circlePaint = new Paint(); circlePaint.setAntiAlias(true); circlePaint.setstrokeWIDth(Utility.dip2px(context,5)); circlePaint.setStyle(Paint.Style.stroke); circlePaint.setcolor(context.getResources().getcolor(R.color.background_gray)); arcPaint = new Paint(); arcPaint.setAntiAlias(true); arcPaint.setstrokeWIDth(Utility.dip2px(context,5)); arcPaint.setStyle(Paint.Style.stroke); arcPaint.setcolor(context.getResources().getcolor(R.color.textcolor_gray)); loadingPaint = new Paint(); loadingPaint.setTextSize((Utility.dip2px(context,15))); loadingPaint.setcolor(context.getResources().getcolor(R.color.textcolor_gray)); textPaint = new Paint(); textPaint.setTextSize((Utility.dip2px(context,15))); textPaint.setcolor(context.getResources().getcolor(R.color.black)); } private float obj2float(Object o) { return ((Number) o).floatValue(); } @OverrIDe protected voID onMeasure(int wIDthMeasureSpec,int heightmeasureSpec) { super.onMeasure(wIDthMeasureSpec,heightmeasureSpec); chartleft = getMeasureDWIDth() / 2 - radius; charttop = getMeasuredHeight() / 2 - radius - Utility.dip2px(context,10); chartRight = getMeasureDWIDth() / 2 + radius; chartBottom = getMeasuredHeight() / 2 + radius - Utility.dip2px(context,10); centerDetailleft = getMeasureDWIDth() / 2 - Utility.dip2px(context,20); centerDetailtop = getMeasuredHeight() / 2 + radius + Utility.dip2px(context,15); centerDetailRight = getMeasureDWIDth() / 2; centerDetailBottom = getMeasuredHeight() / 2 + radius + Utility.dip2px(context,35); } @OverrIDe protected voID onDraw(Canvas canvas) { super.onDraw(canvas); } @OverrIDe public voID surfaceCreated(SurfaceHolder holder) { rectF = new RectF(chartleft - Utility.dip2px(context,5),charttop - Utility.dip2px(context,chartRight + Utility.dip2px(context,chartBottom + Utility.dip2px(context,5)); rectF2 = new RectF(chartleft,charttop,chartRight,chartBottom); // valueAnimator.start(); } @OverrIDe public voID surfaceChanged(SurfaceHolder holder,int format,int wIDth,int height) { } @OverrIDe public voID surfaceDestroyed(SurfaceHolder holder) { circleAnimator.cancel(); chartAnimator.cancel(); } private voID drawDetail(Canvas canvas) { canvas.drawRect(centerDetailleft - Utility.dip2px(context,150),centerDetailtop,centerDetailRight - Utility.dip2px(context,centerDetailBottom,area1Paint); canvas.drawRect(centerDetailleft,centerDetailRight,area2Paint); canvas.drawRect(centerDetailleft + Utility.dip2px(context,centerDetailRight + Utility.dip2px(context,area3Paint); drawText(canvas); } private voID drawText(Canvas canvas) { canvas.drawText("本软件",150) + Utility.dip2px(context,centerDetailtop + Utility.dip2px(context,area1Paint); canvas.drawText("200MB",25),textPaint); canvas.drawText("其他",area2Paint); canvas.drawText("24.1GB",textPaint); canvas.drawText("可用",area3Paint); canvas.drawText("30GB",textPaint); } public voID show() { circleAnimator.setRepeatCount(repeatCount); circleAnimator.start(); } public voID setArea1color(int color) { area1Paint.setcolor(color); } public voID setArea2color(int color) { area2Paint.setcolor(color); } public voID setArea3color(int color) { area3Paint.setcolor(color); } public voID seTradius(float radius) { this.radius = radius; } public voID setScale(float total,float area1,float area2){ area1Angle = area1/total * 360; area2Angle = area2/total * 360; } public voID setRepeatCount(int repeatCount){ this.repeatCount = repeatCount; } }

效果:

模仿微信的效果基本显示出来了,但是当区域改变的时候,会不停闪烁,其实下面标注信息的小正方形也在闪烁,只不过我已经修改好了。

查了网上许多方法都没有给出一个很直接的答案,大部分都是说要对surfaceVIEw前后缓存都进行绘制,这样就不产生闪烁问题。还有一种方法就是通过背景覆盖,让A缓冲在该区域的背景与B缓冲相同,这样自然而然切换的时候,就不会看到缓存交替而产生的闪烁问题了。

关于第一种,我并不是很理解,说是每次要改变前后两个缓冲,不能只变一个。。。。。。(网上都是这么说,但是我到底怎么改才算改!!?)

第二种方法,我经过了多次尝试实现了,通过切换画笔之后,每次画图都覆盖上一层,这样保持了之前闪烁部分的缓存一致。

该部分为找到的一些资料:

双缓存(Double-buffer)与黑屏闪烁

每个SurfaceVIEw 对象有两个独立的graphic buffer,官方SDK将它们称作"front buffer"和"back buffer"。

常规的"double-buffer"会这么做:每一帧的数据都被绘制到back buffer,然后back buffer的内容被持续翻转(flip)到front buffer;屏幕一直显示front buffer。但AndroID SurfaceVIEw的"double-buffer"却是这么做的:在buffer A里绘制内容,然后让屏幕显示buffer A; 下一个循环,在buffer B里绘制内容,然后让屏幕显示buffer B; 如此往复。于是,屏幕上显示的内容依次来自buffer A,B,A,....这样看来,两个buffer其实没有主从的分别,与其称之为"front buffer""back buffer",毋宁称之为"buffer A""buffer B"。

AndroID中"double-buffer"的实现机制,可以很好地解释闪屏现象。在第一个"lockCanvas-drawCanvas-unlockCanvasAndPost"循环中,更新的是buffer A的内容;到下一个"lockCanvas-drawCanvas-unlockCanvasAndPost"循环中,更新的是buffer B的内容。如果buffer A与buffer B中某个buffer内容为空,当屏幕轮流显示它们时,就会出现画面黑屏闪烁现象。

解决方法

出现黑屏是因为buffer A与buffer B中一者内容为空,而且为空的一方还被post到了屏幕。于是有两种解决思路:

不让空buffer出现:每次向一个buffer写完内容并post之后,顺便用这个buffer的内容填充另一个buffer。这样能保证两个buffer的内容是同步的,缺点是做了无用功,耗费性能。

不post空buffer到屏幕:当准备更新内容时,先判断内容是否为空,只有非空时才启动"lockCanvas-drawCanvas-unlockCanvasAndPost"这个流程。

就好比,A缓存是白色,B缓冲是黑色(也就是前后surfaceVIEw的缓存)。而黑色是surfaceVIEw的默认色。比如下面的伪代码,通过线程不停的绘制:

canvas = holder.lockCanvas(); if(flag) { canvas.drawcolor(color.WHITE); } holder.unlockCanvasAndPost(canvas); flag = false;

看似没有什么问题,但是在实际过程却一直在黑白闪烁,这就是因为,虽然A我们每次都绘制了,但是B一直没变还是黑色。这时,我们通过覆盖,讲背景变为白色,就解决了这个问题,而我的解决方法也类似于这种。

下面贴出代码:

package xiaoqi.expandablechartvIEw; import androID.animation.Animator; import androID.animation.PropertyValuesHolder; import androID.animation.ValueAnimator; import androID.content.Context; import androID.graphics.Canvas; import androID.graphics.color; import androID.graphics.Paint; import androID.graphics.PixelFormat; import androID.graphics.PorterDuff; import androID.graphics.RectF; import androID.util.AttributeSet; import androID.vIEw.SurfaceHolder; import androID.vIEw.SurfaceVIEw; import androID.vIEw.animation.linearInterpolator; public class ChartVIEw extends SurfaceVIEw implements SurfaceHolder.Callback { private Context context; private SurfaceHolder holder; private ValueAnimator chartAnimator; private ValueAnimator circleAnimator; //中间内存信息方块的坐标 private float centerDetailleft; private float centerDetailtop; private float centerDetailRight; private float centerDetailBottom; //chart外接正方形坐标 private float chartleft; private float charttop; private float chartRight; private float chartBottom; //起始角度 private float startAngle = 270; //半径 private float radius; //各区域角度 private float area1Angle; private float area2Angle; //区域的量 private float total; private float area1; private float area2; private long time; private int repeatCount = 2; //是否为第一次显示,用于防止surface闪烁 private boolean area1IsFirstShow = true; private boolean area2IsFirstShow = true; //大扇形外接正方形 private RectF rectF; //小扇形外接正方形 private RectF rectF2; private Paint area1Paint; private Paint area2Paint; private Paint area3Paint; private Paint circlePaint; private Paint arcPaint; private Paint loadingPaint; private Paint textPaint; private static final int CIRCLE_DURATION = 1000; public ChartVIEw(Context context) { super(context); this.context = context; init(); } public ChartVIEw(Context context,360f); chartAnimator = ValueAnimator.ofPropertyValuesHolder(angleValues); chartAnimator.setDuration(2000); chartAnimator.setInterpolator(new linearInterpolator()); chartAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @OverrIDe public voID onAnimationUpdate(ValueAnimator animation) { float angle = obj2float(animation.getAnimatedValue("angle")); Canvas canvas = holder.lockCanvas(null); if(canvas != null){ canvas.drawcolor(color.transparent,Paint.ANTI_AliAS_FLAG | Paint.FILTER_BITMAP_FLAG)); if (!area1IsFirstShow) { canvas.drawArc(rectF,area1Paint); } if (!area2IsFirstShow) { canvas.drawArc(rectF2,area2Paint); } if (angle < area1Angle) { canvas.drawArc(rectF,area1Paint); } else if (angle <= area2Angle + area1Angle) { if (area1IsFirstShow) { area1IsFirstShow = false; canvas.drawArc(rectF,area1Paint); } else { canvas.drawArc(rectF2,area2Paint); } } else { if (area2IsFirstShow) { area2IsFirstShow = false; canvas.drawArc(rectF2,area2Paint); } else { canvas.drawArc(rectF2,area3Paint); } } holder.unlockCanvasAndPost(canvas); } } }); circleAnimator = ValueAnimator.ofPropertyValuesHolder(angleValues); circleAnimator.setInterpolator(new linearInterpolator()); circleAnimator.setDuration(CIRCLE_DURATION); circleAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @OverrIDe public voID onAnimationUpdate(ValueAnimator animation) { float angle = obj2float(animation.getAnimatedValue("angle")); Canvas canvas = holder.lockCanvas(null); if(canvas != null){ long NowTime = System.currentTimeMillis(); int rate = (int) (NowTime - time) / (CIRCLE_DURATION * (repeatCount + 1) / 100); if (rate <= 100) { canvas.drawcolor(color.transparent,float area2){ area1Angle = area1/total * 360; area2Angle = area2/total * 360; } public voID setRepeatCount(int repeatCount){ this.repeatCount = repeatCount; } }

效果:

同时建议每个图形都用自己的paint,而不是通过重新set不同设置来调用paint,因为在使用时,我发现,因为很多地方用的是同一个paint也导致了闪烁,而为每个图形都创建了自己的paint之后就好了。

以上所述是小编给大家介绍的AndroID仿微信清理内存图表动画(解决surfaceVIEw屏幕闪烁问题)demo实例详解,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对编程小技巧网站的支持!

总结

以上是内存溢出为你收集整理的Android仿微信清理内存图表动画(解决surfaceView屏幕闪烁问题)demo实例详解全部内容,希望文章能够帮你解决Android仿微信清理内存图表动画(解决surfaceView屏幕闪烁问题)demo实例详解所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/1148327.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-31
下一篇 2022-05-31

发表评论

登录后才能评论

评论列表(0条)

保存