github传送门
看到这样的一个效果,该如何去实现呢?下面我就一步一步的把她撸出来!
先总结下自定义View的步骤:
1、自定义View的属性
2、在View的构造方法中获得我们自定义的属性
3、重写onMeasure
4、重写onDraw
思路
1.自定义属性:文字的颜色和字体大小,圆弧的颜色和宽度,一开始加载进度的位置,等等;
2.画出需要的效果:画圆弧,画字体,使用画笔paint在canvas上绘制;
3.设置进度,重新绘制;
4.接口回调。
自定义我们需要的属性:
在values文件夹下新建文件attrs.xml
接下就从最基本的代码开始
在自定义View中获取并设置这些属性:
private int mCurrent;//当前进度
private Paint mPaintOut;
private Paint mPaintCurrent;
private Paint mPaintText;
private float mPaintWidth;//画笔宽度
private int mPaintColor = Color.RED;//画笔颜色
private int mTextColor = Color.BLACK;//字体颜色
private float mTextSize;//字体大小
private int location;//从哪个位置开始
private float startAngle;//开始角度
初始化构造函数 并获取attr里面的值
public CircleProgressView(Context context) {
this(context, null);
}
public CircleProgressView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public CircleProgressView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
//获取attr里面的值
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.CircleProgressView);
location = array.getInt(R.styleable.CircleProgressView_location, 1);
mPaintWidth = array.getDimension(R.styleable.CircleProgressView_progress_paint_width, dip2px(context, 4));//默认4dp
mPaintColor = array.getColor(R.styleable.CircleProgressView_progress_paint_color, mPaintColor);
mTextSize = array.getDimension(R.styleable.CircleProgressView_progress_text_size, dip2px(context, 18));//默认18sp
mTextColor = array.getColor(R.styleable.CircleProgressView_progress_text_color, mTextColor);
array.recycle();
//画笔->背景圆弧
mPaintOut = new Paint();
mPaintOut.setAntiAlias(true);
mPaintOut.setStrokeWidth(mPaintWidth);
mPaintOut.setStyle(Paint.Style.STROKE);
mPaintOut.setColor(Color.GRAY);
mPaintOut.setStrokeCap(Paint.Cap.ROUND);
//画笔->进度圆弧
mPaintCurrent = new Paint();
mPaintCurrent.setAntiAlias(true);
mPaintCurrent.setStrokeWidth(mPaintWidth);
mPaintCurrent.setStyle(Paint.Style.STROKE);
mPaintCurrent.setColor(mPaintColor);
mPaintCurrent.setStrokeCap(Paint.Cap.ROUND);
//画笔->绘制字体
mPaintText = new Paint();
mPaintText.setAntiAlias(true);
mPaintText.setStyle(Paint.Style.FILL);
mPaintText.setColor(mTextColor);
mPaintText.setTextSize(mTextSize);
if (location == 1) {//默认从左侧开始
startAngle = -180;
} else if (location == 2) {
startAngle = -90;
} else if (location == 3) {
startAngle = 0;
} else if (location == 4) {
startAngle = 90;
}
}
画出需要的效果:画圆弧,画字体,使用画笔paint在canvas上绘制:
注意:绘制操作是在onDraw(Canvas canvas)方法中。
第一步:绘制背景灰色圆弧:
我们使用cancas的drawArc()方法,来了解一下这个方法是什么意思
假设是这样:canvas.drawArc(rectF, 45, 90, false, mPaintOut);
代表:从45°开始,扫过90°范围,方向是顺时针方向绘制,至于那个false是什么意思,暂时先不管,传true一般画扇形图才能用到。来看看草图:
好的,一目了然,如果我们要画一个圆圈怎么办呢,很简单,扫过的范围是360°就OK了。起点在哪个位置就无所谓了。看到这里有些人就要问了,画圆为什么不使用canvas.drawCircle()方法,这是因为后面要画的圆弧使用的也是drawArc()方法,所以为了易懂我们都用这个吧(手动大笑)。
//绘制背景圆弧,因为画笔有一定的宽度,所有画圆弧的范围要比View本身的大小稍微小一些,不然画笔画出来的东西会显示不完整
RectF rectF = new RectF(mPaintWidth / 2, mPaintWidth / 2, getWidth() - mPaintWidth / 2, getHeight() - mPaintWidth / 2);
canvas.drawArc(rectF, 0, 360, false, mPaintOut);
第二步:绘制当前进度的圆弧
这里有人就发现了,我们还继续按照上面的方式只要传入不同的sweepAngle扫过角度的值不就好了吗?yes,没错。我们只要计算出当前百分比所对应的角度值是多少度就OK了。很简单的一个公式:画个草图吧!
那么代码就很容易写了。
//绘制当前进度
float sweepAngle = 360 * mCurrent / 100;
canvas.drawArc(rectF, startAngle, sweepAngle, false, mPaintCurrent);
第三步:绘制文字
// 参数分别为 (文本 基线x 基线y 画笔)
canvas.drawText(String text, float x, float y,Paint paint);
说白了就是文字左下角的那个点的坐标。
而我们要把文字画在View的中心点位置,所以开始撸吧,
来,先求x点坐标=View宽度的一半减去文字宽度的一半,思考一下是不是?
y点的坐标=View高度的一半+文字高度的一半。
OK,上代码:
//绘制进度数字
String text = mCurrent + "%";
//获取文字宽度
float textWidth = mPaintText.measureText(text, 0, text.length());
float dx = getWidth() / 2 - textWidth / 2;
Paint.FontMetricsInt fontMetricsInt = mPaintText.getFontMetricsInt();
float dy = (fontMetricsInt.bottom - fontMetricsInt.top) / 2 - fontMetricsInt.bottom;
float baseLine = getHeight() / 2 + dy;
canvas.drawText(text, dx, baseLine, mPaintText);
3.设置进度,重新绘制;
为了让当前进度mCurrent从0~100的增加,我们需要暴露一个方法可以实时的设置mCurrent值然后不断的进行绘制界面。
/**
- 设置当前进度并重新绘制界面
- @param mCurrent
*/
public void setmCurrent(int mCurrent) {
this.mCurrent = mCurrent;
invalidate();
}
相信很所人都知道只要调用了 invalidate()方法,正常情况下系统就会调用onDraw方法,然后就可以不断的绘制界面了。
这里写个属性动画来达到进度条加载的效果:
//进度条从0到100
ValueAnimator animator = ValueAnimator.ofFloat(0, 100);
animator.setDuration(4000);
animator.setInterpolator(new LinearInterpolator());
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float current = (float) animation.getAnimatedValue();
mCircleProgressView.setmCurrent((int) current);
}
});
animator.start();
4.接口回调。
这一步就很简单了,只要监听进度条达到100就是完成了加载,所以我们先来写一个接口。
//声明接口
public interface OnLoadingCompleteListener {
void complete();
}
//暴露回调方法
public void setOnLoadingCompleteListener(OnLoadingCompleteListener loadingCompleteListener) {
this.mLoadingCompleteListener = loadingCompleteListener;
}
//监听
if (mLoadingCompleteListener != null && mCurrent == 100) {
mLoadingCompleteListener.complete();
}
在对应的Activity中回调接口就可以了。
完整代码
package demo.dianping.com.demo.widget;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;
import demo.dianping.com.demo.R;
/**
* Created by wangwei on 2018/9/27.
*/
public class CircleProgressView extends View {
private int mCurrent;//当前进度
private Paint mPaintOut;
private Paint mPaintCurrent;
private Paint mPaintText;
private float mPaintWidth;//画笔宽度
private int mPaintColor = Color.RED;//画笔颜色
private int mTextColor = Color.BLACK;//字体颜色
private float mTextSize;//字体大小
private int location;//从哪个位置开始
private float startAngle;//开始角度
private OnLoadingCompleteListener mLoadingCompleteListener;
public CircleProgressView(Context context) {
this(context, null);
}
public CircleProgressView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public CircleProgressView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.CircleProgressView);
location = array.getInt(R.styleable.CircleProgressView_location, 1);
mPaintWidth = array.getDimension(R.styleable.CircleProgressView_progress_paint_width, dip2px(context, 4));//默认4dp
mPaintColor = array.getColor(R.styleable.CircleProgressView_progress_paint_color, mPaintColor);
mTextSize = array.getDimension(R.styleable.CircleProgressView_progress_text_size, dip2px(context, 18));//默认18sp
mTextColor = array.getColor(R.styleable.CircleProgressView_progress_text_color, mTextColor);
array.recycle();
//画笔->背景圆弧
mPaintOut = new Paint();
mPaintOut.setAntiAlias(true);
mPaintOut.setStrokeWidth(mPaintWidth);
mPaintOut.setStyle(Paint.Style.STROKE);
mPaintOut.setColor(Color.GRAY);
mPaintOut.setStrokeCap(Paint.Cap.ROUND);
//画笔->进度圆弧
mPaintCurrent = new Paint();
mPaintCurrent.setAntiAlias(true);
mPaintCurrent.setStrokeWidth(mPaintWidth);
mPaintCurrent.setStyle(Paint.Style.STROKE);
mPaintCurrent.setColor(mPaintColor);
mPaintCurrent.setStrokeCap(Paint.Cap.ROUND);
//画笔->绘制字体
mPaintText = new Paint();
mPaintText.setAntiAlias(true);
mPaintText.setStyle(Paint.Style.FILL);
mPaintText.setColor(mTextColor);
mPaintText.setTextSize(mTextSize);
if (location == 1) {//默认从左侧开始
startAngle = -180;
} else if (location == 2) {
startAngle = -90;
} else if (location == 3) {
startAngle = 0;
} else if (location == 4) {
startAngle = 90;
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
int size = width > height ? height : width;
setMeasuredDimension(size, size);
}
// 然后调用onDraw 进行绘制 看看效果:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//绘制背景圆弧,因为画笔有一定的宽度,所有画圆弧的范围要比View本身的大小稍微小一些,不然画笔画出来的东西会显示不完整
RectF rectF = new RectF(mPaintWidth / 2, mPaintWidth / 2, getWidth() - mPaintWidth / 2, getHeight() - mPaintWidth / 2);
canvas.drawArc(rectF, 0, 360, false, mPaintOut);
//绘制当前进度
float sweepAngle = 360 * mCurrent / 100;
canvas.drawArc(rectF, startAngle, sweepAngle, false, mPaintCurrent);
//绘制进度数字
String text = mCurrent + "%";
//获取文字宽度
float textWidth = mPaintText.measureText(text, 0, text.length());
float dx = getWidth() / 2 - textWidth / 2;
Paint.FontMetricsInt fontMetricsInt = mPaintText.getFontMetricsInt();
float dy = (fontMetricsInt.bottom - fontMetricsInt.top) / 2 - fontMetricsInt.bottom;
float baseLine = getHeight() / 2 + dy;
canvas.drawText(text, dx, baseLine, mPaintText);
if (mLoadingCompleteListener != null && mCurrent == 100) {
mLoadingCompleteListener.complete();
}
}
/**
* 获取当前进度值
*
* @return
*/
public int getmCurrent() {
return mCurrent;
}
/**
* 设置当前进度并重新绘制界面
*
* @param mCurrent
*/
public void setmCurrent(int mCurrent) {
this.mCurrent = mCurrent;
invalidate();
}
public void setOnLoadingCompleteListener(OnLoadingCompleteListener loadingCompleteListener) {
this.mLoadingCompleteListener = loadingCompleteListener;
}
public interface OnLoadingCompleteListener {
void complete();
}
/**
* 根据手机的分辨率从 dp 的单位 转成为 px(像素)
*/
public static int dip2px(Context context, float dpValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
}