android酷炫下载进度条
拿到项目需求时,不要立马就去写代码,先想下实现的思路,大致涉及到哪些技术点,哪种实现方式比较好,就拿上面的效果说,系统的ProgressBar肯定实现不了该效果,那就要自定义view,自定义view大致包含以下几步:
1、自定义属性的声明与获取
2、测量onMeasure
3、布局onLayout(ViewGroup布局容器)
4、绘制onDraw
5、onTouchEvent(涉及用户手势交互)
6、onInterceptTouchEvent(处理父view和子view之间的事件拦截)
7、状态的恢复与保存
TIM截图20190120142614.png
对于该效果:
横向进度条包含:左边一个进度、中间一个文字、右边一个进度
圆形进度条:最外层一个圆、加载进度的圆弧、显示进度的中间文字
并没有涉及到view摆放、用户交互行为,大致就只需要第一、第二、第四、第七步;实现的流程大致确定后,考虑是继承自view还是ProgressBar,上面的效果只是对系统ProgressBar的效果进行改动,继承自ProgressBar就可以了,这样可以使用系统ProgressBar的一些属性和方法,通过getProgress()和setProgress()方法就可以获取和设置加载进度,不用像继承自view,还有给view添加属性动画来实现加载进度,考虑清楚了那就开始撸代码吧。
按照流程第一步自定义属性的定义和获取:
<declare-styleable name="ProgressBarView">
<attr name="progress_unreach_color" format="color"></attr>
<attr name="progress_height" format="dimension"></attr>
<attr name="progress_reach_color" format="color"></attr>
<attr name="progress_text_color" format="color"></attr>
<attr name="progress_text_size" format="dimension"></attr>
<attr name="progress_text_offset" format="dimension"></attr>
</declare-styleable>
<declare-styleable name="RoundProgressBar">
<attr name="radius" format="dimension"></attr>
</declare-styleable>
/**
* 获取自定义属性
*
* @param attrs
*/
private void obtainStyledAttrs(AttributeSet attrs) {
TypedArray ta = getContext().obtainStyledAttributes(attrs, R.styleable.ProgressBarView);
mTextSize = ta.getDimensionPixelSize(R.styleable.ProgressBarView_progress_text_size, sp2px(mTextSize));
mTextColor = ta.getColor(R.styleable.ProgressBarView_progress_text_color, mTextColor);
mUnReachColor = ta.getColor(R.styleable.ProgressBarView_progress_unreach_color, mUnReachColor);
mProgressHeight = (int) ta.getDimension(R.styleable.ProgressBarView_progress_height, dp2px(mProgressHeight));
mReachColor = ta.getColor(R.styleable.ProgressBarView_progress_reach_color, mReachColor);
mTextOffset = (int) ta.getDimension(R.styleable.ProgressBarView_progress_text_offset, dp2px(mTextOffset));
ta.recycle();
}
第一步弄好了,就是第二步测量,测量的时候需要注意控件的宽高模式,根据设置的宽高模式来测量和指定控件的宽高;
@Override
protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthVal = MeasureSpec.getSize(widthMeasureSpec);
int height = measureHeight(heightMeasureSpec);
setMeasuredDimension(widthVal, height);
//计算progressbar真正宽度=控件的宽度-paddingleft-paddingright
mRealWith = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
}
private int measureHeight(int heightMeasureSpec) {
int result = 0;
//获取高度模式
int mode = MeasureSpec.getMode(heightMeasureSpec);
//获取宽度模式
int size = MeasureSpec.getSize(heightMeasureSpec);
if (mode == MeasureSpec.EXACTLY) {
//精准模式 用户设置为 比如80dp match_parent fill_parent
result = size;
} else {
//计算中间文字的高度
int textHeight = (int) (mPaint.descent() - mPaint.ascent());
//paddingTop+paddingBottom+ progressbar高度和文字高度的最大值
result = getPaddingTop() + getPaddingBottom() + Math.max(mProgressHeight, Math.abs(textHeight));
if (mode == MeasureSpec.AT_MOST) {
result = Math.min(result, size);
}
}
return result;
}
测量完成后,就可以进行onDraw绘制了,因为要改变系统ProgressBar效果就不需要super.onDraw(canvas);
@Override
protected synchronized void onDraw(Canvas canvas) {
//保存画布
canvas.save();
//移动画布
canvas.translate(getPaddingLeft(), getHeight() / 2);
//定义变量用来控制是否要绘制右边progressbar 如果宽度不够的时候就不进行绘制
boolean noNeedUnRech = false;
//计算左边进度在整个控件宽度的占比
float radio = getProgress() * 1.0f / getMax();
//获取左边进度的宽度
float progressX = radio * mRealWith;
//中间文字
String text = getProgress() + "%";
//获取文字的宽度
int textWidth = (int) mPaint.measureText(text);
if (progressX + textWidth > mRealWith) {
//左边进度+文字的宽度超过progressbar的宽度 重新计算左边进度的宽度 这个时候也就意味着不需要绘制右边进度
progressX = mRealWith - textWidth;
noNeedUnRech = true;
}
//计算左边进度结束的位置 如果结束的位置小于0就不需要绘制左边的进度
float endX = progressX - mTextOffset / 2;
if (endX > 0) {
//绘制左边进度
mPaint.setColor(mReachColor);
mPaint.setStrokeWidth(mProgressHeight);
canvas.drawLine(0, 0, endX, 0, mPaint);
}
mPaint.setColor(mTextColor);
if (getProgress() != 0) {
//计算文字基线
int y = (int) (-(mPaint.descent() + mPaint.ascent()) / 2);
//绘制文字
canvas.drawText(text, progressX, y, mPaint);
}
if (!noNeedUnRech) {
//右边进度的开始位置=左边进度+文字间距的一半+文字宽度
float start;
if (getProgress() == 0) {
start = progressX;
} else {
start = progressX + mTextOffset / 2 + textWidth;
}
mPaint.setColor(mUnReachColor);
mPaint.setStrokeWidth(mProgressHeight);
//绘制右边进度
canvas.drawLine(start, 0, mRealWith, 0, mPaint);
}
//重置画布
canvas.restore();
}
完整代码:
/**
* 长方形进度条
*/
public class ProgressBarView extends ProgressBar {
//字体大小
protected int mTextSize = 12;
//字体颜色
protected int mTextColor = Color.BLACK;
//没有到达(右边progressbar的颜色)
protected int mUnReachColor = Color.GREEN;
//progressbar的高度
protected int mProgressHeight = 6;
//progressbar进度的颜色
protected int mReachColor = mTextColor;
//字体间距
protected int mTextOffset = 10;
protected Paint mPaint;
//progressbar真正的宽度
protected int mRealWith;
public ProgressBarView(Context context) {
this(context, null);
}
public ProgressBarView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ProgressBarView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
//获取自定义属性
obtainStyledAttrs(attrs);
mPaint = new Paint();
mPaint.setTextSize(mTextSize);
}
/**
* 获取自定义属性
*
* @param attrs
*/
private void obtainStyledAttrs(AttributeSet attrs) {
TypedArray ta = getContext().obtainStyledAttributes(attrs, R.styleable.ProgressBarView);
mTextSize = ta.getDimensionPixelSize(R.styleable.ProgressBarView_progress_text_size, sp2px(mTextSize));
mTextColor = ta.getColor(R.styleable.ProgressBarView_progress_text_color, mTextColor);
mUnReachColor = ta.getColor(R.styleable.ProgressBarView_progress_unreach_color, mUnReachColor);
mProgressHeight = (int) ta.getDimension(R.styleable.ProgressBarView_progress_height, dp2px(mProgressHeight));
mReachColor = ta.getColor(R.styleable.ProgressBarView_progress_reach_color, mReachColor);
mTextOffset = (int) ta.getDimension(R.styleable.ProgressBarView_progress_text_offset, dp2px(mTextOffset));
ta.recycle();
}
@Override
protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthVal = MeasureSpec.getSize(widthMeasureSpec);
int height = measureHeight(heightMeasureSpec);
setMeasuredDimension(widthVal, height);
//计算progressbar真正宽度=控件的宽度-paddingleft-paddingright
mRealWith = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
}
@Override
protected synchronized void onDraw(Canvas canvas) {
//保存画布
canvas.save();
//移动画布
canvas.translate(getPaddingLeft(), getHeight() / 2);
//定义变量用来控制是否要绘制右边progressbar 如果宽度不够的时候就不进行绘制
boolean noNeedUnRech = false;
//计算左边进度在整个控件宽度的占比
float radio = getProgress() * 1.0f / getMax();
//获取左边进度的宽度
float progressX = radio * mRealWith;
//中间文字
String text = getProgress() + "%";
//获取文字的宽度
int textWidth = (int) mPaint.measureText(text);
if (progressX + textWidth > mRealWith) {
//左边进度+文字的宽度超过progressbar的宽度 重新计算左边进度的宽度 这个时候也就意味着不需要绘制右边进度
progressX = mRealWith - textWidth;
noNeedUnRech = true;
}
//计算左边进度结束的位置 如果结束的位置小于0就不需要绘制左边的进度
float endX = progressX - mTextOffset / 2;
if (endX > 0) {
//绘制左边进度
mPaint.setColor(mReachColor);
mPaint.setStrokeWidth(mProgressHeight);
canvas.drawLine(0, 0, endX, 0, mPaint);
}
mPaint.setColor(mTextColor);
if (getProgress() != 0) {
//计算文字基线
int y = (int) (-(mPaint.descent() + mPaint.ascent()) / 2);
//绘制文字
canvas.drawText(text, progressX, y, mPaint);
}
if (!noNeedUnRech) {
//右边进度的开始位置=左边进度+文字间距的一半+文字宽度
float start;
if (getProgress() == 0) {
start = progressX;
} else {
start = progressX + mTextOffset / 2 + textWidth;
}
mPaint.setColor(mUnReachColor);
mPaint.setStrokeWidth(mProgressHeight);
//绘制右边进度
canvas.drawLine(start, 0, mRealWith, 0, mPaint);
}
//重置画布
canvas.restore();
}
private int measureHeight(int heightMeasureSpec) {
int result = 0;
//获取高度模式
int mode = MeasureSpec.getMode(heightMeasureSpec);
//获取宽度模式
int size = MeasureSpec.getSize(heightMeasureSpec);
if (mode == MeasureSpec.EXACTLY) {
//精准模式 用户设置为 比如80dp match_parent fill_parent
result = size;
} else {
//计算中间文字的高度
int textHeight = (int) (mPaint.descent() - mPaint.ascent());
//paddingTop+paddingBottom+ progressbar高度和文字高度的最大值
result = getPaddingTop() + getPaddingBottom() + Math.max(mProgressHeight, Math.abs(textHeight));
if (mode == MeasureSpec.AT_MOST) {
result = Math.min(result, size);
}
}
return result;
}
protected int dp2px(int dip) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip, getResources().getDisplayMetrics());
}
protected int sp2px(int sp) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, getResources().getDisplayMetrics());
}
}
圆形加载进度条效果的话,文字和颜色可以共用横向进度条的,所有就让圆形进度条直接继承自横向进度条;
/**
* 原型进度条
*/
public class RoundProgressBar extends ProgressBarView {
//半径
private int mRadius = 30;
private int mMaxPaintWidth;
public RoundProgressBar(Context context) {
this(context, null);
}
public RoundProgressBar(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public RoundProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
//绘制圆形进度条的宽度 这里设置为长方形进度条高度的1.5倍
mRealWith = (int) (mProgressHeight * 1.5f);
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.RoundProgressBar);
mRadius = (int) typedArray.getDimension(R.styleable.RoundProgressBar_radius, dp2px(mRadius));
typedArray.recycle();
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setAntiAlias(true);
mPaint.setDither(true);
mPaint.setStrokeCap(Paint.Cap.ROUND);
}
@Override
protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
mMaxPaintWidth = mProgressHeight;
//计算控件的精准值
int expect = mRadius * 2 + mMaxPaintWidth + getPaddingLeft() + getPaddingRight();
int width = resolveSize(expect, widthMeasureSpec);
int height = resolveSize(expect, heightMeasureSpec);
int readWidth = Math.min(width, height);
mRadius = (readWidth - getPaddingRight() - getPaddingLeft() - mMaxPaintWidth) / 2;
setMeasuredDimension(readWidth, readWidth);
}
@Override
protected synchronized void onDraw(Canvas canvas) {
//获取当前进度
String text = getProgress() + "%";
//测量文字的宽度
float textWidth = mPaint.measureText(text);
//文字的高度
float textHeight = (mPaint.descent() + mPaint.ascent()) / 2;
//保存画布
canvas.save();
//平移画布位置
canvas.translate(getPaddingLeft() + mMaxPaintWidth / 2, getPaddingTop() + mMaxPaintWidth / 2);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(mUnReachColor);
mPaint.setStrokeWidth(mRealWith);
//绘制圆 要注意绘制圆的x y 因为上面对画布进行了平移所以这里就不需要计算了,如果画布没有进行平移需要计算 x y
canvas.drawCircle(mRadius, mRadius, mRadius, mPaint);
//绘制圆弧
mPaint.setColor(mReachColor);
mPaint.setStrokeWidth(mReachColor);
//计算圆弧的扫过的幅度
float sweepAngle = getProgress() * 1.0f / getMax() * 360;
RectF rectF = new RectF(0, 0, mRadius * 2, mRadius * 2);
canvas.drawArc(rectF, 0, sweepAngle, false, mPaint);
//绘制中间文字
mPaint.setColor(mTextColor);
mPaint.setStyle(Paint.Style.FILL);
canvas.drawText(text, mRadius - textWidth / 2, mRadius - textHeight, mPaint);
canvas.restore();
}
}
自定义的代码就基本完成了,在调用的时候也比较简单,不需要像继承自View那样给View设置属性动画又调用invalidate进行重绘,直接调用setProgress方法设置当前的进度就可以了;
public class MainActivity extends AppCompatActivity {
private ProgressBarView progressBarView,progressBarView1;
private RoundProgressBar roundProgressBar,roundProgressBar1;
private static final int MSG_UPDATA = 0X110;
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
int progress = progressBarView.getProgress();
progressBarView.setProgress(++progress);
progressBarView1.setProgress(++progress);
roundProgressBar.setProgress(++progress);
roundProgressBar1.setProgress(++progress);
if (progress >= 100) {
handler.removeMessages(MSG_UPDATA);
}
handler.sendEmptyMessageDelayed(MSG_UPDATA, 100);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
progressBarView = findViewById(R.id.progress_bar_view);
roundProgressBar = findViewById(R.id.round_progressbar);
progressBarView1=findViewById(R.id.progress_bar_view1);
roundProgressBar1=findViewById(R.id.round_progressbar1);
handler.sendEmptyMessageDelayed(MSG_UPDATA, 1000);
}
}