仿华为手机管家“一键优化”Loading加载框
最近公司项目版本通过了没事做,闲来无聊学习下自定义view知识。偶尔看到华为手机上面的手机管家应用上面的loading图,于是想模仿一下,练练手~
废话不多说,先看下效果图


左边是华为自带应用的效果图,感觉挺漂亮的。右边是自己模仿的山寨版~
首先我们要看下华为这个加载框的静态图。
分析一下,首先我们可以将这个图形拆成几个部分。
1.最外面的圆圈和带渐变的内圆圈
2.刻度指针跟渐变的刻度指针
3.内部的进度值
化繁为简,首先我们先画出最外面的圆,代码就不用解释了
canvas.drawCircle(mWidth/2,mHeight/2,radius,mPaint);
mWidth和mHeight是该View的宽和高,我们可以在onMeasure里面获取得到
private int mHeight;
private int mWidth;
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
if(widthMode == MeasureSpec.EXACTLY){
mWidth = widthSize;
}else{
//没定义具体大小的话,具体给200px宽高
mWidth = 200;
}
if(heightMode == MeasureSpec.EXACTLY){
mHeight = heightSize;
}else{
mHeight = 200;
}
setMeasuredDimension(mWidth,mHeight);
}
画出来效果是这样的
怎么感觉怪怪的?? 仔细看了下效果图,发现它是有个渐变的过程,圆圈的颜色从上到下由深至浅~ 这个得怎么实现呢?查了下资料,发现有一个类叫 LinearGradient。这个类是干嘛用的呢?简单来说就是实现图形线性渐变的工具类,看了下构造函数,需要传以下参数
具体用法自行百度,直接贴代码
LinearGradient linearGradient = new LinearGradient(mWidth/2,mHeight - dip2px(getContext(),150),mWidth/2,mHeight + dip2px(getContext(),150),
Color.parseColor("#F9F9F9"),Color.parseColor("#E9E9E9"), Shader.TileMode.MIRROR);
mPaint.setShader(linearGradient);
恩,看起来差不多了(下面的颜色可能有点浅)。
接下来我们画内圆圈的部分,红色框 的内容
它也有一个渐变的过程,但是它的渐变的由外向里渐变的过程,怎么实现呢? 既然有线性渐变,肯定有其他类型的渐变类,看了下源码LinearGradient 继承于 Shader基类,看下Shader的实现类,
发现有个RadialGradient的类,可以用于环形渐变渲染。分析下,我们这个渐变颜色是由灰到白,构造函数为
RadialGradient(float centerX, float centerY, float radius, int[] colors, float[] stops, Shader.TileMode tileMode)
重点关注两个参数colors和stops
- int[] colors:表示所需要的渐变颜色数组
- float[] stops:表示每个渐变颜色所在的位置百分点,取值0-1,数量必须与colors数组保持一致,不然直接crash,一般第一个数值取0,最后一个数值取1;如果第一个数值和最后一个数值并没有取0和1,比如我们这里取一个位置数组:{0.2,0.5,0.8},起始点是0.2百分比位置,结束点是0.8百分比位置,而0-0.2百分比位置和0.8-1.0百分比的位置都是没有指定颜色的。而这些位置的颜色就是根据我们指定的TileMode空白区域填充模式来自行填充!!!有时效果我们是不可控的。所以为了方便起见,建议大家stop数组的起始和终止数值设为0和1.
//画内圈的圆弧
RadialGradient radialGradient = new RadialGradient(mWidth/2,mHeight/2,radius - dip2px(getContext(),10),new int[]{Color.WHITE,Color.parseColor("#FFEBEBEB")}, new float[]{0.85f,1f},Shader.TileMode.CLAMP);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setShader(radialGradient);
canvas.drawCircle(mWidth/2,mHeight/2,radius - dip2px(getContext(),10),mPaint);
由于不是平均区域渐变,所以将第二段position设在0.85f,运行后
接下来,画灰色指针,先画一根指针,然后再通过画笔旋转将所有指针画出,贴代码
canvas.save();
canvas.rotate(index * 3,mWidth/2,mHeight/2);
for(int i=1; i<=120; i++){
canvas.rotate(3,mWidth/2,mHeight/2);
mLinePaint.setColor(Color.parseColor("#DBDBDB"));
mLinePaint.setAlpha(255);
canvas.drawLine(mWidth/2,mHeight/2 - dip2px(getContext(),120),mWidth/2,mHeight/2 - dip2px(getContext(),105),mLinePaint);
}
canvas.restore()
然后再画蓝色指针,顺便逐渐改变画笔的透明度。效果如下
//画蓝色线条
if(i <= 30){
mColorPaint.setAlpha((int) (i * 3 / 100f * 255) + 15);
canvas.drawLine(mWidth/2,mHeight/2 - dip2px(getContext(),120),mWidth/2,mHeight/2 - dip2px(getContext(),105),mColorPaint);
}
是不是感觉有点像了,接下来,我们怎么让它旋转起来呢?
这里设置了一个Index进度值,我们每次重绘都让它的值改变,一担大于某个值,就让缩小,一旦小于某个值,就让它增大,每次根据该值旋转特定的角度, 如此循环,实现动画。
if(index < 120){
index ++;
}else if(index == 120){
index = 0;
}
canvas.save();
canvas.rotate(index * 3,mWidth/2,mHeight/2);
for(int i=1; i<=120; i++){
canvas.rotate(3,mWidth/2,mHeight/2);
mLinePaint.setColor(Color.parseColor("#DBDBDB"));
mLinePaint.setAlpha(255);
canvas.drawLine(mWidth/2,mHeight/2 - dip2px(getContext(),120),mWidth/2,mHeight/2 - dip2px(getContext(),105),mLinePaint);
if(i <= 30){
mColorPaint.setAlpha((int) (i * 3 / 100f * 255) + 15);
canvas.drawLine(mWidth/2,mHeight/2 - dip2px(getContext(),120),mWidth/2,mHeight/2 - dip2px(getContext(),105),mColorPaint);
}
}
canvas.restore();
postInvalidate();
这样就实现了圆圈转动了~
接下来我们加上进度值,进度,用了一个percent变量、这个比较简单,就直接贴代码
private int percent = 0;
public void setPercent(int per){
this.percent = per;
}
.....
mTxtPaint.setColor(Color.BLACK);
mTxtPaint.setTextSize(dip2px(getContext(),70));
float textWidth = mTxtPaint.measureText(String.valueOf(percent));
canvas.drawText(String.valueOf(percent),mWidth/2 - textWidth/2,mHeight/2 + 50 ,mTxtPaint);
mTxtPaint.setTextSize(dip2px(getContext(),20));
mTxtPaint.setColor(Color.GRAY);
float Width = mTxtPaint.measureText(String.valueOf("%"));
canvas.drawText("%",mWidth/2 + textWidth/2 - Width/2 + 25,mHeight/2 + 50 ,mTxtPaint);
为了能够让Loading停止,并且绘制对应的进度刻度,可以设置一个boolean变量isStop,在isStop为false的时候
绘制出对应的进度刻度并且停止重绘,onDraw完整的代码为
@Override
protected void onDraw(Canvas canvas) {
LinearGradient linearGradient = new LinearGradient(mWidth/2,mHeight - dip2px(getContext(),150),mWidth/2,mHeight + dip2px(getContext(),150),
Color.parseColor("#F9F9F9"),Color.parseColor("#E9E9E9"), Shader.TileMode.MIRROR);
mPaint.setShader(linearGradient);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(Color.parseColor("#E1E1E1"));
mPaint.setStrokeWidth(3);
//画最外面的圆圈
canvas.drawCircle(mWidth/2,mHeight/2,radius,mPaint);
canvas.save();
//画内圈的圆弧
RadialGradient radialGradient = new RadialGradient(mWidth/2,mHeight/2,radius - dip2px(getContext(),10),new int[]{Color.WHITE,Color.parseColor("#FFEBEBEB")}, new float[]{0.85f,1f},Shader.TileMode.CLAMP);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setShader(radialGradient);
canvas.drawCircle(mWidth/2,mHeight/2,radius - dip2px(getContext(),10),mPaint);
if(index < 120){
index ++;
}else if(index == 120){
index = 0;
}
canvas.save();
canvas.rotate(index * 3,mWidth/2,mHeight/2);
for(int i=1; i<=120; i++){
canvas.rotate(3,mWidth/2,mHeight/2);
mLinePaint.setColor(Color.parseColor("#DBDBDB"));
mLinePaint.setAlpha(255);
canvas.drawLine(mWidth/2,mHeight/2 - dip2px(getContext(),120),mWidth/2,mHeight/2 - dip2px(getContext(),105),mLinePaint);
if(!isStop){
//画蓝色线条
if(i <= 30){
mColorPaint.setAlpha((int) (i * 3 / 100f * 255) + 15);
canvas.drawLine(mWidth/2,mHeight/2 - dip2px(getContext(),120),mWidth/2,mHeight/2 - dip2px(getContext(),105),mColorPaint);
}
}
}
canvas.restore();
mTxtPaint.setColor(Color.BLACK);
mTxtPaint.setTextSize(dip2px(getContext(),70));
float textWidth = mTxtPaint.measureText(String.valueOf(percent));
canvas.drawText(String.valueOf(percent),mWidth/2 - textWidth/2,mHeight/2 + 50 ,mTxtPaint);
mTxtPaint.setTextSize(dip2px(getContext(),20));
mTxtPaint.setColor(Color.GRAY);
float Width = mTxtPaint.measureText(String.valueOf("%"));
canvas.drawText("%",mWidth/2 + textWidth/2 - Width/2 + 25,mHeight/2 + 50 ,mTxtPaint);
if(!isStop){
postInvalidate();
}else{
//画进度实线
int index = (int) (percent / 100f * 120);
for(int j = 0; j < index; j++){
mColorPaint.setAlpha(255);
canvas.rotate(3,mWidth/2,mHeight/2);
canvas.drawLine(mWidth/2,mHeight/2 - dip2px(getContext(),120),mWidth/2,mHeight/2 - dip2px(getContext(),105),mColorPaint);
}
}
}
好了,这样就基本完成了。 基本不难,主要是一些细节的实现,比如渐变样式,还有画笔的旋转重绘使得刻度看起来处于旋转状态。