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

最近接了一个项目其中有功能要实现一个清理内存,要求和微信的效果一样。于是想到用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, true, area2Paint);
// }
if (angle < area1Angle) {
canvas.drawArc(rectF, startAngle, angle, true, area1Paint);
} else if (angle <= area2Angle + area1Angle) {
// if (area1IsFirstShow) {
// area1IsFirstShow = false;
// canvas.drawArc(rectF, startAngle, area1Angle, true, area1Paint);
// } else {
canvas.drawArc(rectF2, startAngle+area1Angle, angle - area1Angle, true, area2Paint);
// }
} else {
// if (area2IsFirstShow) {
// area2IsFirstShow = false;
// canvas.drawArc(rectF2, area1Angle + startAngle, area2Angle, true, area2Paint);
// } else {
canvas.drawArc(rectF2, startAngle + area1Angle + area2Angle, angle - area2Angle - area1Angle,
true, 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, 5), chartRight +
Utility.dip2px(context, 5), 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, 150), centerDetailBottom, area1Paint);
canvas.drawRect(centerDetailLeft, centerDetailTop, centerDetailRight, centerDetailBottom, area2Paint);
canvas.drawRect(centerDetailLeft + Utility.dip2px(context, 150), centerDetailTop,
centerDetailRight + Utility.dip2px(context, 150), centerDetailBottom, area3Paint);
drawText(canvas);
}
private void drawText(Canvas canvas) {
canvas.drawText("本软件", centerDetailRight - Utility.dip2px(context, 150) + Utility.dip2px(context, 5),
centerDetailTop + Utility.dip2px(context, 10), area1Paint);
canvas.drawText("200MB", centerDetailRight - Utility.dip2px(context, 150) + Utility.dip2px(context, 5),
centerDetailTop + Utility.dip2px(context, 25), textPaint);
canvas.drawText("其他", centerDetailRight + Utility.dip2px(context, 5),
centerDetailTop + Utility.dip2px(context, 10), area2Paint);
canvas.drawText("24.1GB", centerDetailRight + Utility.dip2px(context, 5),
centerDetailTop + Utility.dip2px(context, 25), textPaint);
canvas.drawText("可用", centerDetailRight + Utility.dip2px(context, 150) + Utility.dip2px(context, 5),
centerDetailTop + Utility.dip2px(context, 10), area3Paint);
canvas.drawText("30GB", centerDetailRight + Utility.dip2px(context, 150) + Utility.dip2px(context, 5),
centerDetailTop + Utility.dip2px(context, 25), 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, B,....这样看来,两个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, 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, true, area2Paint);
}
if (angle < area1Angle) {
canvas.drawArc(rectF, startAngle, angle, true, area1Paint);
} else if (angle <= area2Angle + area1Angle) {
if (area1IsFirstShow) {
area1IsFirstShow = false;
canvas.drawArc(rectF, startAngle, area1Angle, true, area1Paint);
} else {
canvas.drawArc(rectF2, startAngle+area1Angle, angle - area1Angle, true, area2Paint);
}
} else {
if (area2IsFirstShow) {
area2IsFirstShow = false;
canvas.drawArc(rectF2, area1Angle + startAngle, area2Angle, true, area2Paint);
} else {
canvas.drawArc(rectF2, startAngle + area1Angle + area2Angle, angle - area2Angle - area1Angle,
true, 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, 5), chartRight +
Utility.dip2px(context, 5), 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, 150), centerDetailBottom, area1Paint);
canvas.drawRect(centerDetailLeft, centerDetailTop, centerDetailRight, centerDetailBottom, area2Paint);
canvas.drawRect(centerDetailLeft + Utility.dip2px(context, 150), centerDetailTop,
centerDetailRight + Utility.dip2px(context, 150), centerDetailBottom, area3Paint);
drawText(canvas);
}
private void drawText(Canvas canvas) {
canvas.drawText("本软件", centerDetailRight - Utility.dip2px(context, 150) + Utility.dip2px(context, 5),
centerDetailTop + Utility.dip2px(context, 10), area1Paint);
canvas.drawText("200MB", centerDetailRight - Utility.dip2px(context, 150) + Utility.dip2px(context, 5),
centerDetailTop + Utility.dip2px(context, 25), textPaint);
canvas.drawText("其他", centerDetailRight + Utility.dip2px(context, 5),
centerDetailTop + Utility.dip2px(context, 10), area2Paint);
canvas.drawText("24.1GB", centerDetailRight + Utility.dip2px(context, 5),
centerDetailTop + Utility.dip2px(context, 25), textPaint);
canvas.drawText("可用", centerDetailRight + Utility.dip2px(context, 150) + Utility.dip2px(context, 5),
centerDetailTop + Utility.dip2px(context, 10), area3Paint);
canvas.drawText("30GB", centerDetailRight + Utility.dip2px(context, 150) + Utility.dip2px(context, 5),
centerDetailTop + Utility.dip2px(context, 25), 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;
}
}

效果:

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

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

(0)

相关推荐

  • Android截屏保存png图片的实例代码

    复制代码 代码如下: import java.io.FileNotFoundException;import java.io.FileOutputStream;import java.io.IOException; import android.app.Activity;import android.graphics.Bitmap;import android.graphics.Rect;import android.util.Log;import android.view.View; publ

  • Android截屏SurfaceView黑屏问题的解决办法

    最近项目中有截屏的需求,普通的view截屏方法网上一搜一大把,但是SurfaceView截屏黑屏问题很多文章说的并不清楚,自己参考了一些别的博客,再加上自己的思考,算是找到了一种解决方案. 1.首先看我们一般是怎么用SurfaceView的 public class SuperSurfaceView extends SurfaceView implements SurfaceHolder.Callback { SurfaceHolder surfaceHolder; public SuperSu

  • Android 屏幕截屏方法汇总

    1.直接使用getWindow().getDecorView().getRootView() 直接使用getWindow().getDecorView().getRootView()是获取当前屏幕的activity.然而对于系统状态栏的信息是截不了,出现一条空白的.如下图: 主要到没,有一条白色边就是系统状态栏.看一下代码,很简单都加了注释了. //这种方法状态栏是空白,显示不了状态栏的信息 private void saveCurrentImage() { //获取当前屏幕的大小 int wi

  • 使用python编写android截屏脚本双击运行即可

    测试的过程中经常需要截取屏幕,通常的做法是使用手机自带的截屏功能,然后将截屏文件复制出来,这种方法的优点是不需要连接数据线就可截屏,缺点则是生成的截屏文件命名是随机命名的,复制出来也比较麻烦.另一种方法是使用PC端的手机助手类软件. 这里使用python编写一个截屏的脚本,双击运行脚本就OK,截屏成功后会将截屏文件已当前时间命名,并保存在存放脚本的当前路径的screenshot文件夹下: #!/usr/bin/env python import os import time PATH = lam

  • Android切换至SurfaceView时闪屏(黑屏闪一下)以及黑屏移动问题的解决方法

    1.最近的项目中,有一个Activity用到Fragment+ViewPager,其中一个fragment中实现了视频播放的功能,包含有SurfaceView.结果,每次打开程序第一次进入到该Activity时都会闪屏黑一下.原因就出在SurfaceView. 详解: I think I found the reason for the black flash. In my case I'm using a SurfaceView inside a Fragment and dynamicall

  • Android强制设定横屏时,SurfaceView一直黑屏

    接着上一个问题,解决了SurfaceView闪屏问题之后(http://www.jb51.net/article/101909.htm),又有了一个新的问题.现在我想设置含有fragment+viewpager的activity横屏.其中一个fragment有视频播放功能,含SurfaceView. 当我横屏拿着平板时,打开程序进入到该activity,是正常的.当竖屏拿着打开程序进入到该activity时,就会一直处于黑屏的状态.原因应该还是SurfaceView.难道程序转入后台或者黑屏以后

  • 解析android截屏问题

    我是基于android2.3.3系统之上的,想必大家应该知道在android源码下面有个文件叫做screencap吧,位于frameworks\base\services\surfaceflinger\tests\screencap\screencap.cpp,你直接在linux下编译(保存在 /system/bin/test-screencap),然后push到手机上再通过电脑去敲命令test-screencap /mnt/sdcard/scapxx.png就可以实现截屏. 复制代码 代码如下

  • Android Fragment中使用SurfaceView切换时闪一下黑屏的解决办法

    重构了下之前自己的一个新闻客户端,全部使用了Fragment来进行页面切换,只有一个入口Activity作为程序的启动Activity,其中有一个界面需要调用摄像头识别二维码,于是就会用到SurfaceView进行预览,那么问题来了,当切换到对应的Fragment时,屏幕会黑一下,黑了1秒左右就显示出正常的界面,而且这种现象只有第一次进入该Fragment才会出现,之后进入都不会出现,解决方法是无意在github上看到了,试了一下,可以行的通,下面贴出解决方法. 方法一.在Activity的on

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

    最近接了一个项目其中有功能要实现一个清理内存,要求和微信的效果一样.于是想到用surfaceView而不是继承view.下面小编给大家解析下实现思路. surfaceView是为了解决频繁绘制动画产生了闪烁,而采用了双缓冲机制,即A.B两个缓冲轮流显示在画布上,同时,使用不当,同样容易产生闪烁,这是由于A.B中有一个缓冲没有改变. 在我写这个view的时候就遇到了这个问题,研究了好久终于解决. 首先说一下思路: 微信清理缓存的动画是: 一个圆环不停的转动,同时中间有文字显示-->加载完成后,出现

  • Android 仿微信小程序入口动画

    目录 效果对比 流程分析 自定义ViewGroup 小程序缩放比例值计算 动画遮罩 MainActivity 效果对比 微信原版 仿照效果 流程分析 自定义ViewGroup 整个布局是通过自定义ViewGroup来管理的,在自定义ViewGroup中,子布局一共有两个,一个是小程序布局,一个是会话列表布局,然后按照上下分别摆放就可以了. package com.example.kotlindemo.widget.weixin import android.content.Context imp

  • Android隐藏标题栏及解决启动闪过标题的实例详解

    Android隐藏标题栏及解决启动闪过标题的实例详解 方法一: 在代码中设置 this.requestWindowFeature(Window.FEATURE_NO_TITLE);//去掉标题栏 方法二: 在AndroidManifest.xml 里面设置 <application android:icon="@drawable/icon" android:label="@string/app_name" Android:theme="@androi

  • 微信小程序中使用Promise进行异步流程处理的实例详解

    微信小程序中使用Promise进行异步流程处理的实例详解 我们知道,JavaScript是单进程执行的,同步操作会对程序的执行进行阻塞处理.比如在浏览器页面程序中,如果一段同步的代码需要执行很长时间(比如一个很大的循环操作),则页面会产生卡死的现象. 所以,在JavaScript中,提供了一些异步特性,为程序提供了性能和体验上的益处,比如可以将代码放到setTimeout()中执行:或者在网页中,我们使用Ajax的方式向服务器端做异步数据请求.这些异步的代码不会阻塞当前的界面主进程,界面还是可以

  • 微信小程序开发数据缓存基础知识辨析及运用实例详解

    提示:这里可以添加本文要记录的大概内容: 例如:随着人工智能的不断发展,机器学习这门技术也越来越重要,很多人都开启了学习机器学习,本文就介绍了机器学习的基础内容. 提示:以下是本篇文章正文内容,下面案例可供参考 一.微信数据缓存是什么? 在实际开发中,在用到一个数据时,我们需要调用api接口去得到,然后渲染在页面中,但是对于一些数据,是经常需要使用的,如果每次使用时都需要调用api接口,会十分麻烦.数据缓存就解决了这个问题,我们可以在初次调用某api得到数据的同时将数据缓存,那么在之后的使用过程

  • 微信小程序 点击控件后选中其它反选实例详解

    微信小程序 点击控件后选中其它反选实例详解 前言: 如果需要实现进来进行给按钮加上买一送一的样式,或者单击就选中单个按钮,只能靠css结合js进行控制了,小程序暂时没有这样的控件. 实现效果图: 微信小程序进来的时候自动进行按钮样式的初始化,这个需要一个字段做判断,加上正则表达式wxml文件: <block wx:for="{{liuliangItems}}"> <block wx:if="{{item.one2one == 1}}"> &l

  • 微信小程序 增、删、改、查操作实例详解

    微信小程序 增.删.改.查操作实例详解 1.以收货地址的增删改查为例 2.文件目录 js文件是逻辑控制,主要是它发送请求和接收数据, json 用于此页面局部 配置并且覆盖全局app.json配置, wxss用于页面的样式设置, wxml就是页面,相当于html <form bindsubmit="addSubmit"> <view class="consignee"> <text class="consignee-tit&q

  • 微信小程序中form 表单提交和取值实例详解

    微信小程序中form 表单提交和取值实例详解 我们知道,如果我们直接给 input 添加 bindinput,比如:<input bindinput="onUsernameInput" />,那么可以在 onUsernameInput 中直接使用 e.detail.value,即: onUsernameInput : function(e) { e.detail.value; } 但是,如果有多个输入控件,我们不可能为每个控件添加 bindinput.bindchange

  • 微信小程序 选择器(时间,日期,地区)实例详解

    微信小程序 选择器(时间,日期,地区) 微信小程序 开发由于本人最近学习微信小程序的开发,根据自己的实践结果整理了下结果,对日期选择器,时间选择器,地区选择器做的实例,有不对的地方,希望大家指正. 用微信封装好的控件感觉很好,为我们开发人员省去了很多麻烦.弊端就是不能做大量的自定义.今天试用了选择器. 上gif: 上代码: 1.index.js //index.js //获取应用实例 var app = getApp() Page({ data: { date: '2016-11-08', ti

  • 微信小程序 图片加载(本地,网路)实例详解

    在微信小程序中,要显示一张图片,有两种图片加载方式: 加载本地图片 加载网络图片 加载本地图片 <image class="widget__arrow" src="/image/arrowright.png" mode="aspectFill"> </image> src="/image/arrowright.png" 这句就是加载本地图片资源的.想想iOS中的加载本地图片,imageName:,类似.

随机推荐