Android进度条升级篇
作者:互联网
先上效果图
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.RectF;
import android.graphics.SweepGradient;
import android.graphics.Typeface;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import com.littlejie.circleprogress.utils.Constant;
import com.littlejie.circleprogress.utils.MiscUtil;
/**
* 圆形进度条,类似 QQ 健康中运动步数的 UI 控件
* Created by littlejie on 2017/2/21.
*/
public class CircleProgress extends View {
private static final String TAG = CircleProgress.class.getSimpleName();
private Context mContext;
//默认大小
private int mDefaultSize;
//是否开启抗锯齿
private boolean antiAlias;
//绘制提示
private TextPaint mHintPaint;
private CharSequence mHint;
private int mHintColor;
private float mHintSize;
private float mHintOffset;
//绘制单位
private TextPaint mUnitPaint;
private CharSequence mUnit;
private int mUnitColor;
private float mUnitSize;
private float mUnitOffset;
//绘制数值
private TextPaint mValuePaint;
private float mValue;
private float mMaxValue;
private float mValueOffset;
private int mPrecision;
private String mPrecisionFormat;
private int mValueColor;
private float mValueSize;
//绘制圆弧
private Paint mArcPaint;
private float mArcWidth;
private float mStartAngle, mSweepAngle;
private RectF mRectF;
//渐变的颜色是360度,如果只显示270,那么则会缺失部分颜色
private SweepGradient mSweepGradient;
private int[] mGradientColors = {Color.GREEN, Color.YELLOW, Color.RED};
//当前进度,[0.0f,1.0f]
private float mPercent;
//动画时间
private long mAnimTime;
//属性动画
private ValueAnimator mAnimator;
//绘制背景圆弧
private Paint mBgArcPaint;
private int mBgArcColor;
private float mBgArcWidth;
//圆心坐标,半径
private Point mCenterPoint;
private float mRadius;
private float mTextOffsetPercentInRadius;
public CircleProgress(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
private void init(Context context, AttributeSet attrs) {
mContext = context;
mDefaultSize = MiscUtil.dipToPx(mContext, Constant.DEFAULT_SIZE);
mAnimator = new ValueAnimator();
mRectF = new RectF();
mCenterPoint = new Point();
initAttrs(attrs);
initPaint();
setValue(mValue);
}
private void initAttrs(AttributeSet attrs) {
TypedArray typedArray = mContext.obtainStyledAttributes(attrs, R.styleable.CircleProgressBar);
antiAlias = typedArray.getBoolean(R.styleable.CircleProgressBar_antiAlias, Constant.ANTI_ALIAS);
mHint = typedArray.getString(R.styleable.CircleProgressBar_hint);
mHintColor = typedArray.getColor(R.styleable.CircleProgressBar_hintColor, Color.BLACK);
mHintSize = typedArray.getDimension(R.styleable.CircleProgressBar_hintSize, Constant.DEFAULT_HINT_SIZE);
mValue = typedArray.getFloat(R.styleable.CircleProgressBar_value, Constant.DEFAULT_VALUE);
mMaxValue = typedArray.getFloat(R.styleable.CircleProgressBar_maxValue, Constant.DEFAULT_MAX_VALUE);
//内容数值精度格式
mPrecision = typedArray.getInt(R.styleable.CircleProgressBar_precision, 0);
mPrecisionFormat = MiscUtil.getPrecisionFormat(mPrecision);
mValueColor = typedArray.getColor(R.styleable.CircleProgressBar_valueColor, Color.BLACK);
mValueSize = typedArray.getDimension(R.styleable.CircleProgressBar_valueSize, Constant.DEFAULT_VALUE_SIZE);
mUnit = typedArray.getString(R.styleable.CircleProgressBar_unit);
mUnitColor = typedArray.getColor(R.styleable.CircleProgressBar_unitColor, Color.BLACK);
mUnitSize = typedArray.getDimension(R.styleable.CircleProgressBar_unitSize, Constant.DEFAULT_UNIT_SIZE);
mArcWidth = typedArray.getDimension(R.styleable.CircleProgressBar_arcWidth, Constant.DEFAULT_ARC_WIDTH);
mStartAngle = typedArray.getFloat(R.styleable.CircleProgressBar_startAngle, Constant.DEFAULT_START_ANGLE);
mSweepAngle = typedArray.getFloat(R.styleable.CircleProgressBar_sweepAngle, Constant.DEFAULT_SWEEP_ANGLE);
mBgArcColor = typedArray.getColor(R.styleable.CircleProgressBar_bgArcColor, Color.WHITE);
mBgArcWidth = typedArray.getDimension(R.styleable.CircleProgressBar_bgArcWidth, Constant.DEFAULT_ARC_WIDTH);
mTextOffsetPercentInRadius = typedArray.getFloat(R.styleable.CircleProgressBar_textOffsetPercentInRadius, 0.33f);
//mPercent = typedArray.getFloat(R.styleable.CircleProgressBar_percent, 0);
mAnimTime = typedArray.getInt(R.styleable.CircleProgressBar_animTime, Constant.DEFAULT_ANIM_TIME);
int gradientArcColors = typedArray.getResourceId(R.styleable.CircleProgressBar_arcColors, 0);
if (gradientArcColors != 0) {
try {
int[] gradientColors = getResources().getIntArray(gradientArcColors);
if (gradientColors.length == 0) {//如果渐变色为数组为0,则尝试以单色读取色值
int color = getResources().getColor(gradientArcColors);
mGradientColors = new int[2];
mGradientColors[0] = color;
mGradientColors[1] = color;
} else if (gradientColors.length == 1) {//如果渐变数组只有一种颜色,默认设为两种相同颜色
mGradientColors = new int[2];
mGradientColors[0] = gradientColors[0];
mGradientColors[1] = gradientColors[0];
} else {
mGradientColors = gradientColors;
}
} catch (Resources.NotFoundException e) {
throw new Resources.NotFoundException("the give resource not found.");
}
}
typedArray.recycle();
}
private void initPaint() {
mHintPaint = new TextPaint();
// 设置抗锯齿,会消耗较大资源,绘制图形速度会变慢。
mHintPaint.setAntiAlias(antiAlias);
// 设置绘制文字大小
mHintPaint.setTextSize(mHintSize);
// 设置画笔颜色
mHintPaint.setColor(mHintColor);
// 从中间向两边绘制,不需要再次计算文字
mHintPaint.setTextAlign(Paint.Align.CENTER);
mValuePaint = new TextPaint();
mValuePaint.setAntiAlias(antiAlias);
mValuePaint.setTextSize(mValueSize);
mValuePaint.setColor(mValueColor);
// 设置Typeface对象,即字体风格,包括粗体,斜体以及衬线体,非衬线体等
mValuePaint.setTypeface(Typeface.DEFAULT_BOLD);
mValuePaint.setTextAlign(Paint.Align.CENTER);
mUnitPaint = new TextPaint();
mUnitPaint.setAntiAlias(antiAlias);
mUnitPaint.setTextSize(mUnitSize);
mUnitPaint.setColor(mUnitColor);
mUnitPaint.setTextAlign(Paint.Align.CENTER);
mArcPaint = new Paint();
mArcPaint.setAntiAlias(antiAlias);
// 设置画笔的样式,为FILL,FILL_OR_STROKE,或STROKE
mArcPaint.setStyle(Paint.Style.STROKE);
// 设置画笔粗细
mArcPaint.setStrokeWidth(mArcWidth);
// 当画笔样式为STROKE或FILL_OR_STROKE时,设置笔刷的图形样式,如圆形样式
// Cap.ROUND,或方形样式 Cap.SQUARE
mArcPaint.setStrokeCap(Paint.Cap.ROUND);
mBgArcPaint = new Paint();
mBgArcPaint.setAntiAlias(antiAlias);
mBgArcPaint.setColor(mBgArcColor);
mBgArcPaint.setStyle(Paint.Style.STROKE);
mBgArcPaint.setStrokeWidth(mBgArcWidth);
mBgArcPaint.setStrokeCap(Paint.Cap.ROUND);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(MiscUtil.measure(widthMeasureSpec, mDefaultSize),
MiscUtil.measure(heightMeasureSpec, mDefaultSize));
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
Log.d(TAG, "onSizeChanged: w = " + w + "; h = " + h + "; oldw = " + oldw + "; oldh = " + oldh);
//求圆弧和背景圆弧的最大宽度
float maxArcWidth = Math.max(mArcWidth, mBgArcWidth);
//求最小值作为实际值
int minSize = Math.min(w - getPaddingLeft() - getPaddingRight() - 2 * (int) maxArcWidth,
h - getPaddingTop() - getPaddingBottom() - 2 * (int) maxArcWidth);
//减去圆弧的宽度,否则会造成部分圆弧绘制在外围
mRadius = minSize / 2;
//获取圆的相关参数
mCenterPoint.x = w / 2;
mCenterPoint.y = h / 2;
//绘制圆弧的边界
mRectF.left = mCenterPoint.x - mRadius - maxArcWidth / 2;
mRectF.top = mCenterPoint.y - mRadius - maxArcWidth / 2;
mRectF.right = mCenterPoint.x + mRadius + maxArcWidth / 2;
mRectF.bottom = mCenterPoint.y + mRadius + maxArcWidth / 2;
//计算文字绘制时的 baseline
//由于文字的baseline、descent、ascent等属性只与textSize和typeface有关,所以此时可以直接计算
//若value、hint、unit由同一个画笔绘制或者需要动态设置文字的大小,则需要在每次更新后再次计算
mValueOffset = mCenterPoint.y + getBaselineOffsetFromY(mValuePaint);
mHintOffset = mCenterPoint.y - mRadius * mTextOffsetPercentInRadius + getBaselineOffsetFromY(mHintPaint);
mUnitOffset = mCenterPoint.y + mRadius * mTextOffsetPercentInRadius + getBaselineOffsetFromY(mUnitPaint);
updateArcPaint();
Log.d(TAG, "onSizeChanged: 控件大小 = " + "(" + w + ", " + h + ")"
+ "圆心坐标 = " + mCenterPoint.toString()
+ ";圆半径 = " + mRadius
+ ";圆的外接矩形 = " + mRectF.toString());
}
private float getBaselineOffsetFromY(Paint paint) {
return MiscUtil.measureTextHeight(paint) / 2;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawText(canvas);
drawArc(canvas);
}
/**
* 绘制内容文字
*
* @param canvas
*/
private void drawText(Canvas canvas) {
// 计算文字宽度,由于Paint已设置为居中绘制,故此处不需要重新计算
// float textWidth = mValuePaint.measureText(mValue.toString());
// float x = mCenterPoint.x - textWidth / 2;
canvas.drawText(String.format(mPrecisionFormat, mValue), mCenterPoint.x, mValueOffset, mValuePaint);
if (mHint != null) {
canvas.drawText(mHint.toString(), mCenterPoint.x, mHintOffset, mHintPaint);
}
if (mUnit != null) {
canvas.drawText(mUnit.toString(), mCenterPoint.x, mUnitOffset, mUnitPaint);
}
}
private void drawArc(Canvas canvas) {
// 绘制背景圆弧
// 从进度圆弧结束的地方开始重新绘制,优化性能
canvas.save();
float currentAngle = mSweepAngle * mPercent;
canvas.rotate(mStartAngle, mCenterPoint.x, mCenterPoint.y);
canvas.drawArc(mRectF, currentAngle, mSweepAngle - currentAngle + 2, false, mBgArcPaint);
// 第一个参数 oval 为 RectF 类型,即圆弧显示区域
// startAngle 和 sweepAngle 均为 float 类型,分别表示圆弧起始角度和圆弧度数
// 3点钟方向为0度,顺时针递增
// 如果 startAngle < 0 或者 > 360,则相当于 startAngle % 360
// useCenter:如果为True时,在绘制圆弧时将圆心包括在内,通常用来绘制扇形
canvas.drawArc(mRectF, 2, currentAngle, false, mArcPaint);
canvas.restore();
}
/**
* 更新圆弧画笔
*/
private void updateArcPaint() {
// 设置渐变
mSweepGradient = new SweepGradient(mCenterPoint.x, mCenterPoint.y, mGradientColors, null);
mArcPaint.setShader(mSweepGradient);
}
public boolean isAntiAlias() {
return antiAlias;
}
public CharSequence getHint() {
return mHint;
}
public void setHint(CharSequence hint) {
mHint = hint;
}
public CharSequence getUnit() {
return mUnit;
}
public void setUnit(CharSequence unit) {
mUnit = unit;
}
public float getValue() {
return mValue;
}
/**
* 设置当前值
*
* @param value
*/
public void setValue(float value) {
if (value > mMaxValue) {
value = mMaxValue;
}
float start = mPercent;
float end = value / mMaxValue;
startAnimator(start, end, mAnimTime);
}
private void startAnimator(float start, float end, long animTime) {
mAnimator = ValueAnimator.ofFloat(start, end);
mAnimator.setDuration(animTime);
mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mPercent = (float) animation.getAnimatedValue();
mValue = mPercent * mMaxValue;
if (BuildConfig.DEBUG) {
Log.d(TAG, "onAnimationUpdate: percent = " + mPercent
+ ";currentAngle = " + (mSweepAngle * mPercent)
+ ";value = " + mValue);
}
invalidate();
}
});
mAnimator.start();
}
/**
* 获取最大值
*
* @return
*/
public float getMaxValue() {
return mMaxValue;
}
/**
* 设置最大值
*
* @param maxValue
*/
public void setMaxValue(float maxValue) {
mMaxValue = maxValue;
}
/**
* 获取精度
*
* @return
*/
public int getPrecision() {
return mPrecision;
}
public void setPrecision(int precision) {
mPrecision = precision;
mPrecisionFormat = MiscUtil.getPrecisionFormat(precision);
}
public int[] getGradientColors() {
return mGradientColors;
}
/**
* 设置渐变
*
* @param gradientColors
*/
public void setGradientColors(int[] gradientColors) {
mGradientColors = gradientColors;
updateArcPaint();
}
public long getAnimTime() {
return mAnimTime;
}
public void setAnimTime(long animTime) {
mAnimTime = animTime;
}
/**
* 重置
*/
public void reset() {
startAnimator(mPercent, 0.0f, 1000L);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
//释放资源
}
}
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.RectF;
import android.graphics.SweepGradient;
import android.graphics.Typeface;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import com.littlejie.circleprogress.utils.Constant;
import com.littlejie.circleprogress.utils.MiscUtil;
/**
* 带有刻度的圆形进度条
* Created by littlejie on 2017/2/26.
*/
public class DialProgress extends View {
private static final String TAG = DialProgress.class.getSimpleName();
private Context mContext;
//圆心坐标
private Point mCenterPoint;
private float mRadius;
private float mTextOffsetPercentInRadius;
private boolean antiAlias;
//绘制提示
private TextPaint mHintPaint;
private CharSequence mHint;
private int mHintColor;
private float mHintSize;
private float mHintOffset;
//绘制数值
private Paint mValuePaint;
private int mValueColor;
private float mMaxValue;
private float mValue;
private float mValueSize;
private float mValueOffset;
private String mPrecisionFormat;
//绘制单位
private Paint mUnitPaint;
private float mUnitSize;
private int mUnitColor;
private float mUnitOffset;
private CharSequence mUnit;
//前景圆弧
private Paint mArcPaint;
private float mArcWidth;
private int mDialIntervalDegree;
private float mStartAngle, mSweepAngle;
private RectF mRectF;
//渐变
private int[] mGradientColors = {Color.GREEN, Color.YELLOW, Color.RED};
//当前进度,[0.0f,1.0f]
private float mPercent;
//动画时间
private long mAnimTime;
//属性动画
private ValueAnimator mAnimator;
//背景圆弧
private Paint mBgArcPaint;
private int mBgArcColor;
//刻度线颜色
private Paint mDialPaint;
private float mDialWidth;
private int mDialColor;
private int mDefaultSize;
public DialProgress(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
private void init(Context context, AttributeSet attrs) {
mContext = context;
mDefaultSize = MiscUtil.dipToPx(context, Constant.DEFAULT_SIZE);
mRectF = new RectF();
mCenterPoint = new Point();
initConfig(context, attrs);
initPaint();
setValue(mValue);
}
private void initConfig(Context context, AttributeSet attrs) {
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.DialProgress);
antiAlias = typedArray.getBoolean(R.styleable.DialProgress_antiAlias, true);
mMaxValue = typedArray.getFloat(R.styleable.DialProgress_maxValue, Constant.DEFAULT_MAX_VALUE);
mValue = typedArray.getFloat(R.styleable.DialProgress_value, Constant.DEFAULT_VALUE);
mValueSize = typedArray.getDimension(R.styleable.DialProgress_valueSize, Constant.DEFAULT_VALUE_SIZE);
mValueColor = typedArray.getColor(R.styleable.DialProgress_valueColor, Color.BLACK);
mDialIntervalDegree = typedArray.getInt(R.styleable.DialProgress_dialIntervalDegree, 10);
int precision = typedArray.getInt(R.styleable.DialProgress_precision, 0);
mPrecisionFormat = MiscUtil.getPrecisionFormat(precision);
mUnit = typedArray.getString(R.styleable.DialProgress_unit);
mUnitColor = typedArray.getColor(R.styleable.DialProgress_unitColor, Color.BLACK);
mUnitSize = typedArray.getDimension(R.styleable.DialProgress_unitSize, Constant.DEFAULT_UNIT_SIZE);
mHint = typedArray.getString(R.styleable.DialProgress_hint);
mHintColor = typedArray.getColor(R.styleable.DialProgress_hintColor, Color.BLACK);
mHintSize = typedArray.getDimension(R.styleable.DialProgress_hintSize, Constant.DEFAULT_HINT_SIZE);
mArcWidth = typedArray.getDimension(R.styleable.DialProgress_arcWidth, Constant.DEFAULT_ARC_WIDTH);
mStartAngle = typedArray.getFloat(R.styleable.DialProgress_startAngle, Constant.DEFAULT_START_ANGLE);
mSweepAngle = typedArray.getFloat(R.styleable.DialProgress_sweepAngle, Constant.DEFAULT_SWEEP_ANGLE);
mAnimTime = typedArray.getInt(R.styleable.DialProgress_animTime, Constant.DEFAULT_ANIM_TIME);
mBgArcColor = typedArray.getColor(R.styleable.DialProgress_bgArcColor, Color.GRAY);
mDialWidth = typedArray.getDimension(R.styleable.DialProgress_dialWidth, 2);
mDialColor = typedArray.getColor(R.styleable.DialProgress_dialColor, Color.WHITE);
mTextOffsetPercentInRadius = typedArray.getFloat(R.styleable.DialProgress_textOffsetPercentInRadius, 0.33f);
int gradientArcColors = typedArray.getResourceId(R.styleable.DialProgress_arcColors, 0);
if (gradientArcColors != 0) {
try {
int[] gradientColors = getResources().getIntArray(gradientArcColors);
if (gradientColors.length == 0) {
int color = getResources().getColor(gradientArcColors);
mGradientColors = new int[2];
mGradientColors[0] = color;
mGradientColors[1] = color;
} else if (gradientColors.length == 1) {
mGradientColors = new int[2];
mGradientColors[0] = gradientColors[0];
mGradientColors[1] = gradientColors[0];
} else {
mGradientColors = gradientColors;
}
} catch (Resources.NotFoundException e) {
throw new Resources.NotFoundException("the give resource not found.");
}
}
typedArray.recycle();
}
private void initPaint() {
mHintPaint = new TextPaint();
// 设置抗锯齿,会消耗较大资源,绘制图形速度会变慢。
mHintPaint.setAntiAlias(antiAlias);
// 设置绘制文字大小
mHintPaint.setTextSize(mHintSize);
// 设置画笔颜色
mHintPaint.setColor(mHintColor);
// 从中间向两边绘制,不需要再次计算文字
mHintPaint.setTextAlign(Paint.Align.CENTER);
mValuePaint = new Paint();
mValuePaint.setAntiAlias(antiAlias);
mValuePaint.setTextSize(mValueSize);
mValuePaint.setColor(mValueColor);
mValuePaint.setTypeface(Typeface.DEFAULT_BOLD);
mValuePaint.setTextAlign(Paint.Align.CENTER);
mUnitPaint = new Paint();
mUnitPaint.setAntiAlias(antiAlias);
mUnitPaint.setTextSize(mUnitSize);
mUnitPaint.setColor(mUnitColor);
mUnitPaint.setTextAlign(Paint.Align.CENTER);
mArcPaint = new Paint();
mArcPaint.setAntiAlias(antiAlias);
mArcPaint.setStyle(Paint.Style.STROKE);
mArcPaint.setStrokeWidth(mArcWidth);
mArcPaint.setStrokeCap(Paint.Cap.BUTT);
mBgArcPaint = new Paint();
mBgArcPaint.setAntiAlias(antiAlias);
mBgArcPaint.setStyle(Paint.Style.STROKE);
mBgArcPaint.setStrokeWidth(mArcWidth);
mBgArcPaint.setStrokeCap(Paint.Cap.BUTT);
mBgArcPaint.setColor(mBgArcColor);
mDialPaint = new Paint();
mDialPaint.setAntiAlias(antiAlias);
mDialPaint.setColor(mDialColor);
mDialPaint.setStrokeWidth(mDialWidth);
}
/**
* 更新圆弧画笔
*/
private void updateArcPaint() {
// 设置渐变
// 渐变的颜色是360度,如果只显示270,那么则会缺失部分颜色
SweepGradient sweepGradient = new SweepGradient(mCenterPoint.x, mCenterPoint.y, mGradientColors, null);
mArcPaint.setShader(sweepGradient);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(MiscUtil.measure(widthMeasureSpec, mDefaultSize),
MiscUtil.measure(heightMeasureSpec, mDefaultSize));
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
Log.d(TAG, "onSizeChanged: w = " + w + "; h = " + h + "; oldw = " + oldw + "; oldh = " + oldh);
int minSize = Math.min(getMeasuredWidth() - getPaddingLeft() - getPaddingRight() - 2 * (int) mArcWidth,
getMeasuredHeight() - getPaddingTop() - getPaddingBottom() - 2 * (int) mArcWidth);
mRadius = minSize / 2;
mCenterPoint.x = getMeasuredWidth() / 2;
mCenterPoint.y = getMeasuredHeight() / 2;
//绘制圆弧的边界
mRectF.left = mCenterPoint.x - mRadius - mArcWidth / 2;
mRectF.top = mCenterPoint.y - mRadius - mArcWidth / 2;
mRectF.right = mCenterPoint.x + mRadius + mArcWidth / 2;
mRectF.bottom = mCenterPoint.y + mRadius + mArcWidth / 2;
mValueOffset = mCenterPoint.y + getBaselineOffsetFromY(mValuePaint);
mHintOffset = mCenterPoint.y - mRadius * mTextOffsetPercentInRadius + getBaselineOffsetFromY(mHintPaint);
mUnitOffset = mCenterPoint.y + mRadius * mTextOffsetPercentInRadius + getBaselineOffsetFromY(mUnitPaint);
updateArcPaint();
Log.d(TAG, "onMeasure: 控件大小 = " + "(" + getMeasuredWidth() + ", " + getMeasuredHeight() + ")"
+ ";圆心坐标 = " + mCenterPoint.toString()
+ ";圆半径 = " + mRadius
+ ";圆的外接矩形 = " + mRectF.toString());
}
private float getBaselineOffsetFromY(Paint paint) {
return MiscUtil.measureTextHeight(paint) / 2;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawArc(canvas);
drawDial(canvas);
drawText(canvas);
}
private void drawArc(Canvas canvas) {
// 绘制背景圆弧
// 从进度圆弧结束的地方开始重新绘制,优化性能
float currentAngle = mSweepAngle * mPercent;
canvas.save();
canvas.rotate(mStartAngle, mCenterPoint.x, mCenterPoint.y);
canvas.drawArc(mRectF, currentAngle, mSweepAngle - currentAngle, false, mBgArcPaint);
// 第一个参数 oval 为 RectF 类型,即圆弧显示区域
// startAngle 和 sweepAngle 均为 float 类型,分别表示圆弧起始角度和圆弧度数
// 3点钟方向为0度,顺时针递增
// 如果 startAngle < 0 或者 > 360,则相当于 startAngle % 360
// useCenter:如果为True时,在绘制圆弧时将圆心包括在内,通常用来绘制扇形
canvas.drawArc(mRectF, 0, currentAngle, false, mArcPaint);
canvas.restore();
}
private void drawDial(Canvas canvas) {
int total = (int) (mSweepAngle / mDialIntervalDegree);
canvas.save();
canvas.rotate(mStartAngle, mCenterPoint.x, mCenterPoint.y);
for (int i = 0; i <= total; i++) {
canvas.drawLine(mCenterPoint.x + mRadius, mCenterPoint.y, mCenterPoint.x + mRadius + mArcWidth, mCenterPoint.y, mDialPaint);
canvas.rotate(mDialIntervalDegree, mCenterPoint.x, mCenterPoint.y);
}
canvas.restore();
}
private void drawText(Canvas canvas) {
canvas.drawText(String.format(mPrecisionFormat, mValue), mCenterPoint.x, mValueOffset, mValuePaint);
if (mUnit != null) {
canvas.drawText(mUnit.toString(), mCenterPoint.x, mUnitOffset, mUnitPaint);
}
if (mHint != null) {
canvas.drawText(mHint.toString(), mCenterPoint.x, mHintOffset, mHintPaint);
}
}
public float getMaxValue() {
return mMaxValue;
}
public void setMaxValue(float maxValue) {
mMaxValue = maxValue;
}
/**
* 设置当前值
*
* @param value
*/
public void setValue(float value) {
if (value > mMaxValue) {
value = mMaxValue;
}
float start = mPercent;
float end = value / mMaxValue;
startAnimator(start, end, mAnimTime);
}
private void startAnimator(float start, float end, long animTime) {
mAnimator = ValueAnimator.ofFloat(start, end);
mAnimator.setDuration(animTime);
mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mPercent = (float) animation.getAnimatedValue();
mValue = mPercent * mMaxValue;
if (BuildConfig.DEBUG) {
Log.d(TAG, "onAnimationUpdate: percent = " + mPercent
+ ";currentAngle = " + (mSweepAngle * mPercent)
+ ";value = " + mValue);
}
invalidate();
}
});
mAnimator.start();
}
public int[] getGradientColors() {
return mGradientColors;
}
public void setGradientColors(int[] gradientColors) {
mGradientColors = gradientColors;
updateArcPaint();
}
public void reset() {
startAnimator(mPercent, 0.0f, 1000L);
}
}
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Point;
import android.graphics.RectF;
import android.os.Build;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.animation.LinearInterpolator;
import com.littlejie.circleprogress.utils.Constant;
import com.littlejie.circleprogress.utils.MiscUtil;
/**
* 水波进度条
* Created by littlejie on 2017/2/26.
*/
public class WaveProgress extends View {
private static final String TAG = WaveProgress.class.getSimpleName();
//浅色波浪方向
private static final int L2R = 0;
private static final int R2L = 1;
private int mDefaultSize;
//圆心
private Point mCenterPoint;
//半径
private float mRadius;
//圆的外接矩形
private RectF mRectF;
//深色波浪移动距离
private float mDarkWaveOffset;
//浅色波浪移动距离
private float mLightWaveOffset;
//浅色波浪方向
private boolean isR2L;
//是否锁定波浪不随进度移动
private boolean lockWave;
//是否开启抗锯齿
private boolean antiAlias;
//最大值
private float mMaxValue;
//当前值
private float mValue;
//当前进度
private float mPercent;
//绘制提示
private TextPaint mHintPaint;
private CharSequence mHint;
private int mHintColor;
private float mHintSize;
private Paint mPercentPaint;
private float mValueSize;
private int mValueColor;
//圆环宽度
private float mCircleWidth;
//圆环
private Paint mCirclePaint;
//圆环颜色
private int mCircleColor;
//背景圆环颜色
private int mBgCircleColor;
//水波路径
private Path mWaveLimitPath;
private Path mWavePath;
//水波高度
private float mWaveHeight;
//水波数量
private int mWaveNum;
//深色水波
private Paint mWavePaint;
//深色水波颜色
private int mDarkWaveColor;
//浅色水波颜色
private int mLightWaveColor;
//深色水波贝塞尔曲线上的起始点、控制点
private Point[] mDarkPoints;
//浅色水波贝塞尔曲线上的起始点、控制点
private Point[] mLightPoints;
//贝塞尔曲线点的总个数
private int mAllPointCount;
private int mHalfPointCount;
private ValueAnimator mProgressAnimator;
private long mDarkWaveAnimTime;
private ValueAnimator mDarkWaveAnimator;
private long mLightWaveAnimTime;
private ValueAnimator mLightWaveAnimator;
public WaveProgress(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
private void init(Context context, AttributeSet attrs) {
mDefaultSize = MiscUtil.dipToPx(context, Constant.DEFAULT_SIZE);
mRectF = new RectF();
mCenterPoint = new Point();
initAttrs(context, attrs);
initPaint();
initPath();
}
private void initAttrs(Context context, AttributeSet attrs) {
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.WaveProgress);
antiAlias = typedArray.getBoolean(R.styleable.WaveProgress_antiAlias, true);
mDarkWaveAnimTime = typedArray.getInt(R.styleable.WaveProgress_darkWaveAnimTime, Constant.DEFAULT_ANIM_TIME);
mLightWaveAnimTime = typedArray.getInt(R.styleable.WaveProgress_lightWaveAnimTime, Constant.DEFAULT_ANIM_TIME);
mMaxValue = typedArray.getFloat(R.styleable.WaveProgress_maxValue, Constant.DEFAULT_MAX_VALUE);
mValue = typedArray.getFloat(R.styleable.WaveProgress_value, Constant.DEFAULT_VALUE);
mValueSize = typedArray.getDimension(R.styleable.WaveProgress_valueSize, Constant.DEFAULT_VALUE_SIZE);
mValueColor = typedArray.getColor(R.styleable.WaveProgress_valueColor, Color.BLACK);
mHint = typedArray.getString(R.styleable.WaveProgress_hint);
mHintColor = typedArray.getColor(R.styleable.WaveProgress_hintColor, Color.BLACK);
mHintSize = typedArray.getDimension(R.styleable.WaveProgress_hintSize, Constant.DEFAULT_HINT_SIZE);
mCircleWidth = typedArray.getDimension(R.styleable.WaveProgress_circleWidth, Constant.DEFAULT_ARC_WIDTH);
mCircleColor = typedArray.getColor(R.styleable.WaveProgress_circleColor, Color.GREEN);
mBgCircleColor = typedArray.getColor(R.styleable.WaveProgress_bgCircleColor, Color.WHITE);
mWaveHeight = typedArray.getDimension(R.styleable.WaveProgress_waveHeight, Constant.DEFAULT_WAVE_HEIGHT);
mWaveNum = typedArray.getInt(R.styleable.WaveProgress_waveNum, 1);
mDarkWaveColor = typedArray.getColor(R.styleable.WaveProgress_darkWaveColor,
getResources().getColor(android.R.color.holo_blue_dark));
mLightWaveColor = typedArray.getColor(R.styleable.WaveProgress_lightWaveColor,
getResources().getColor(android.R.color.holo_green_light));
isR2L = typedArray.getInt(R.styleable.WaveProgress_lightWaveDirect, R2L) == R2L;
lockWave = typedArray.getBoolean(R.styleable.WaveProgress_lockWave, false);
typedArray.recycle();
}
private void initPaint() {
mHintPaint = new TextPaint();
// 设置抗锯齿,会消耗较大资源,绘制图形速度会变慢。
mHintPaint.setAntiAlias(antiAlias);
// 设置绘制文字大小
mHintPaint.setTextSize(mHintSize);
// 设置画笔颜色
mHintPaint.setColor(mHintColor);
// 从中间向两边绘制,不需要再次计算文字
mHintPaint.setTextAlign(Paint.Align.CENTER);
mCirclePaint = new Paint();
mCirclePaint.setAntiAlias(antiAlias);
mCirclePaint.setStrokeWidth(mCircleWidth);
mCirclePaint.setStyle(Paint.Style.STROKE);
mCirclePaint.setStrokeCap(Paint.Cap.ROUND);
mWavePaint = new Paint();
mWavePaint.setAntiAlias(antiAlias);
mWavePaint.setStyle(Paint.Style.FILL);
mPercentPaint = new Paint();
mPercentPaint.setTextAlign(Paint.Align.CENTER);
mPercentPaint.setAntiAlias(antiAlias);
mPercentPaint.setColor(mValueColor);
mPercentPaint.setTextSize(mValueSize);
}
private void initPath() {
mWaveLimitPath = new Path();
mWavePath = new Path();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(MiscUtil.measure(widthMeasureSpec, mDefaultSize),
MiscUtil.measure(heightMeasureSpec, mDefaultSize));
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
Log.d(TAG, "onSizeChanged: w = " + w + "; h = " + h + "; oldw = " + oldw + "; oldh = " + oldh);
int minSize = Math.min(getMeasuredWidth() - getPaddingLeft() - getPaddingRight() - 2 * (int) mCircleWidth,
getMeasuredHeight() - getPaddingTop() - getPaddingBottom() - 2 * (int) mCircleWidth);
mRadius = minSize / 2;
mCenterPoint.x = getMeasuredWidth() / 2;
mCenterPoint.y = getMeasuredHeight() / 2;
//绘制圆弧的边界
mRectF.left = mCenterPoint.x - mRadius - mCircleWidth / 2;
mRectF.top = mCenterPoint.y - mRadius - mCircleWidth / 2;
mRectF.right = mCenterPoint.x + mRadius + mCircleWidth / 2;
mRectF.bottom = mCenterPoint.y + mRadius + mCircleWidth / 2;
Log.d(TAG, "onSizeChanged: 控件大小 = " + "(" + getMeasuredWidth() + ", " + getMeasuredHeight() + ")"
+ ";圆心坐标 = " + mCenterPoint.toString()
+ ";圆半径 = " + mRadius
+ ";圆的外接矩形 = " + mRectF.toString());
initWavePoints();
//开始动画
setValue(mValue);
startWaveAnimator();
}
private void initWavePoints() {
//当前波浪宽度
float waveWidth = (mRadius * 2) / mWaveNum;
mAllPointCount = 8 * mWaveNum + 1;
mHalfPointCount = mAllPointCount / 2;
mDarkPoints = getPoint(false, waveWidth);
mLightPoints = getPoint(isR2L, waveWidth);
}
/**
* 从左往右或者从右往左获取贝塞尔点
*
* @return
*/
private Point[] getPoint(boolean isR2L, float waveWidth) {
Point[] points = new Point[mAllPointCount];
//第1个点特殊处理,即数组的中点
points[mHalfPointCount] = new Point((int) (mCenterPoint.x + (isR2L ? mRadius : -mRadius)), mCenterPoint.y);
//屏幕内的贝塞尔曲线点
for (int i = mHalfPointCount + 1; i < mAllPointCount; i += 4) {
float width = points[mHalfPointCount].x + waveWidth * (i / 4 - mWaveNum);
points[i] = new Point((int) (waveWidth / 4 + width), (int) (mCenterPoint.y - mWaveHeight));
points[i + 1] = new Point((int) (waveWidth / 2 + width), mCenterPoint.y);
points[i + 2] = new Point((int) (waveWidth * 3 / 4 + width), (int) (mCenterPoint.y + mWaveHeight));
points[i + 3] = new Point((int) (waveWidth + width), mCenterPoint.y);
}
//屏幕外的贝塞尔曲线点
for (int i = 0; i < mHalfPointCount; i++) {
int reverse = mAllPointCount - i - 1;
points[i] = new Point((isR2L ? 2 : 1) * points[mHalfPointCount].x - points[reverse].x,
points[mHalfPointCount].y * 2 - points[reverse].y);
}
//对从右向左的贝塞尔点数组反序,方便后续处理
return isR2L ? MiscUtil.reverse(points) : points;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawCircle(canvas);
drawLightWave(canvas);
drawDarkWave(canvas);
drawProgress(canvas);
}
/**
* 绘制圆环
*
* @param canvas
*/
private void drawCircle(Canvas canvas) {
canvas.save();
canvas.rotate(270, mCenterPoint.x, mCenterPoint.y);
int currentAngle = (int) (360 * mPercent);
//画背景圆环
mCirclePaint.setColor(mBgCircleColor);
canvas.drawArc(mRectF, currentAngle, 360 - currentAngle, false, mCirclePaint);
//画圆环
mCirclePaint.setColor(mCircleColor);
canvas.drawArc(mRectF, 0, currentAngle, false, mCirclePaint);
canvas.restore();
}
/**
* 绘制深色波浪(贝塞尔曲线)
*
* @param canvas
*/
private void drawDarkWave(Canvas canvas) {
mWavePaint.setColor(mDarkWaveColor);
drawWave(canvas, mWavePaint, mDarkPoints, mDarkWaveOffset);
}
/**
* 绘制浅色波浪(贝塞尔曲线)
*
* @param canvas
*/
private void drawLightWave(Canvas canvas) {
mWavePaint.setColor(mLightWaveColor);
//从右向左的水波位移应该被减去
drawWave(canvas, mWavePaint, mLightPoints, isR2L ? -mLightWaveOffset : mLightWaveOffset);
}
@TargetApi(Build.VERSION_CODES.KITKAT)
private void drawWave(Canvas canvas, Paint paint, Point[] points, float waveOffset) {
mWaveLimitPath.reset();
mWavePath.reset();
float height = lockWave ? 0 : mRadius - 2 * mRadius * mPercent;
//moveTo和lineTo绘制出水波区域矩形
mWavePath.moveTo(points[0].x + waveOffset, points[0].y + height);
for (int i = 1; i < mAllPointCount; i += 2) {
mWavePath.quadTo(points[i].x + waveOffset, points[i].y + height,
points[i + 1].x + waveOffset, points[i + 1].y + height);
}
//mWavePath.lineTo(points[mAllPointCount - 1].x, points[mAllPointCount - 1].y + height);
//不管如何移动,波浪与圆路径的交集底部永远固定,否则会造成上移的时候底部为空的情况
mWavePath.lineTo(points[mAllPointCount - 1].x, mCenterPoint.y + mRadius);
mWavePath.lineTo(points[0].x, mCenterPoint.y + mRadius);
mWavePath.close();
mWaveLimitPath.addCircle(mCenterPoint.x, mCenterPoint.y, mRadius, Path.Direction.CW);
//取该圆与波浪路径的交集,形成波浪在圆内的效果
mWaveLimitPath.op(mWavePath, Path.Op.INTERSECT);
canvas.drawPath(mWaveLimitPath, paint);
}
//前一次绘制时的进度
private float mPrePercent;
//当前进度值
private String mPercentValue;
private void drawProgress(Canvas canvas) {
float y = mCenterPoint.y - (mPercentPaint.descent() + mPercentPaint.ascent()) / 2;
if (BuildConfig.DEBUG) {
Log.d(TAG, "mPercent = " + mPercent + "; mPrePercent = " + mPrePercent);
}
if (mPrePercent == 0.0f || Math.abs(mPercent - mPrePercent) >= 0.01f) {
mPercentValue = String.format("%.0f%%", mPercent * 100);
mPrePercent = mPercent;
}
canvas.drawText(mPercentValue, mCenterPoint.x, y, mPercentPaint);
if (mHint != null) {
float hy = mCenterPoint.y * 2 / 3 - (mHintPaint.descent() + mHintPaint.ascent()) / 2;
canvas.drawText(mHint.toString(), mCenterPoint.x, hy, mHintPaint);
}
}
public float getMaxValue() {
return mMaxValue;
}
public void setMaxValue(float maxValue) {
mMaxValue = maxValue;
}
public float getValue() {
return mValue;
}
/**
* 设置当前值
*
* @param value
*/
public void setValue(float value) {
if (value > mMaxValue) {
value = mMaxValue;
}
float start = mPercent;
float end = value / mMaxValue;
Log.d(TAG, "setValue, value = " + value + ";start = " + start + "; end = " + end);
startAnimator(start, end, mDarkWaveAnimTime);
}
private void startAnimator(final float start, float end, long animTime) {
Log.d(TAG, "startAnimator,value = " + mValue
+ ";start = " + start + ";end = " + end + ";time = " + animTime);
//当start=0且end=0时,不需要启动动画
if (start == 0 && end == 0) {
return;
}
mProgressAnimator = ValueAnimator.ofFloat(start, end);
mProgressAnimator.setDuration(animTime);
mProgressAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mPercent = (float) animation.getAnimatedValue();
if (mPercent == 0.0f || mPercent == 1.0f) {
stopWaveAnimator();
} else {
startWaveAnimator();
}
mValue = mPercent * mMaxValue;
if (BuildConfig.DEBUG) {
Log.d(TAG, "onAnimationUpdate: percent = " + mPercent
+ ";value = " + mValue);
}
invalidate();
}
});
mProgressAnimator.start();
}
private void startWaveAnimator() {
startLightWaveAnimator();
startDarkWaveAnimator();
}
private void stopWaveAnimator() {
if (mDarkWaveAnimator != null && mDarkWaveAnimator.isRunning()) {
mDarkWaveAnimator.cancel();
mDarkWaveAnimator.removeAllUpdateListeners();
mDarkWaveAnimator = null;
}
if (mLightWaveAnimator != null && mLightWaveAnimator.isRunning()) {
mLightWaveAnimator.cancel();
mLightWaveAnimator.removeAllUpdateListeners();
mLightWaveAnimator = null;
}
}
private void startLightWaveAnimator() {
if (mLightWaveAnimator != null && mLightWaveAnimator.isRunning()) {
return;
}
mLightWaveAnimator = ValueAnimator.ofFloat(0, 2 * mRadius);
mLightWaveAnimator.setDuration(mLightWaveAnimTime);
mLightWaveAnimator.setRepeatCount(ValueAnimator.INFINITE);
mLightWaveAnimator.setInterpolator(new LinearInterpolator());
mLightWaveAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mLightWaveOffset = (float) animation.getAnimatedValue();
postInvalidate();
}
});
mLightWaveAnimator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
mLightWaveOffset = 0;
}
@Override
public void onAnimationEnd(Animator animation) {
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
mLightWaveAnimator.start();
}
private void startDarkWaveAnimator() {
if (mDarkWaveAnimator != null && mDarkWaveAnimator.isRunning()) {
return;
}
mDarkWaveAnimator = ValueAnimator.ofFloat(0, 2 * mRadius);
mDarkWaveAnimator.setDuration(mDarkWaveAnimTime);
mDarkWaveAnimator.setRepeatCount(ValueAnimator.INFINITE);
mDarkWaveAnimator.setInterpolator(new LinearInterpolator());
mDarkWaveAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mDarkWaveOffset = (float) animation.getAnimatedValue();
postInvalidate();
}
});
mDarkWaveAnimator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
mDarkWaveOffset = 0;
}
@Override
public void onAnimationEnd(Animator animation) {
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
mDarkWaveAnimator.start();
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
stopWaveAnimator();
if (mProgressAnimator != null && mProgressAnimator.isRunning()) {
mProgressAnimator.cancel();
mProgressAnimator.removeAllUpdateListeners();
mProgressAnimator = null;
}
}
}
/**
* Created by littlejie on 2017/2/26.
*/
public class Constant {
public static final boolean ANTI_ALIAS = true;
public static final int DEFAULT_SIZE = 150;
public static final int DEFAULT_START_ANGLE = 270;
public static final int DEFAULT_SWEEP_ANGLE = 360;
public static final int DEFAULT_ANIM_TIME = 1000;
public static final int DEFAULT_MAX_VALUE = 100;
public static final int DEFAULT_VALUE = 50;
public static final int DEFAULT_HINT_SIZE = 15;
public static final int DEFAULT_UNIT_SIZE = 30;
public static final int DEFAULT_VALUE_SIZE = 15;
public static final int DEFAULT_ARC_WIDTH = 15;
public static final int DEFAULT_WAVE_HEIGHT = 40;
}
import android.content.Context;
import android.graphics.Paint;
import android.view.View;
/**
* Created by littlejie on 2017/2/22.
*/
public class MiscUtil {
/**
* 测量 View
*
* @param measureSpec
* @param defaultSize View 的默认大小
* @return
*/
public static int measure(int measureSpec, int defaultSize) {
int result = defaultSize;
int specMode = View.MeasureSpec.getMode(measureSpec);
int specSize = View.MeasureSpec.getSize(measureSpec);
if (specMode == View.MeasureSpec.EXACTLY) {
result = specSize;
} else if (specMode == View.MeasureSpec.AT_MOST) {
result = Math.min(result, specSize);
}
return result;
}
/**
* dip 转换成px
*
* @param dip
* @return
*/
public static int dipToPx(Context context, float dip) {
float density = context.getResources().getDisplayMetrics().density;
return (int) (dip * density + 0.5f * (dip >= 0 ? 1 : -1));
}
/**
* 获取数值精度格式化字符串
*
* @param precision
* @return
*/
public static String getPrecisionFormat(int precision) {
return "%." + precision + "f";
}
/**
* 反转数组
*
* @param arrays
* @param <T>
* @return
*/
public static <T> T[] reverse(T[] arrays) {
if (arrays == null) {
return null;
}
int length = arrays.length;
for (int i = 0; i < length / 2; i++) {
T t = arrays[i];
arrays[i] = arrays[length - i - 1];
arrays[length - i - 1] = t;
}
return arrays;
}
/**
* 测量文字高度
* @param paint
* @return
*/
public static float measureTextHeight(Paint paint) {
Paint.FontMetrics fontMetrics = paint.getFontMetrics();
return (Math.abs(fontMetrics.ascent) - fontMetrics.descent);
}
}
<!-- 是否开启抗锯齿 -->
<attr name="antiAlias" format="boolean" />
<!-- 圆弧起始角度,3点钟方向为0,顺时针递增,小于0或大于360进行取余 -->
<attr name="startAngle" format="float" />
<!-- 圆弧度数 -->
<attr name="sweepAngle" format="float" />
<!-- 设置动画时间 -->
<attr name="animTime" format="integer" />
<!-- 绘制内容的数值 -->
<attr name="maxValue" format="float" />
<attr name="value" format="float" />
<!-- 绘制内容的单位 -->
<attr name="unit" format="string|reference" />
<attr name="unitSize" format="dimension" />
<attr name="unitColor" format="color|reference" />
<!-- 绘制内容相应的提示语 -->
<attr name="hint" format="string|reference" />
<attr name="hintSize" format="dimension" />
<attr name="hintColor" format="color|reference" />
<!-- 精度,默认为0 -->
<attr name="precision" format="integer" />
<attr name="valueSize" format="dimension" />
<attr name="valueColor" format="color|reference" />
<!-- 圆弧颜色,设置多个可实现渐变 -->
<attr name="arcColor1" format="color|reference" />
<attr name="arcColor2" format="color|reference" />
<attr name="arcColor3" format="color|reference" />
<!-- 背景圆弧颜色,默认白色 -->
<attr name="bgArcColor" format="color|reference" />
<!-- 圆弧宽度 -->
<attr name="arcWidth" format="dimension" />
<!-- 圆弧颜色, -->
<attr name="arcColors" format="color|reference" />
<!-- 文字的偏移量。相对于圆半径而言,默认三分之一 -->
<attr name="textOffsetPercentInRadius" format="float" />
<!-- 圆形进度条 -->
<declare-styleable name="CircleProgressBar">
<attr name="antiAlias" />
<attr name="startAngle" />
<attr name="sweepAngle" />
<attr name="animTime" />
<attr name="maxValue" />
<attr name="value" />
<attr name="precision" />
<attr name="valueSize" />
<attr name="valueColor" />
<attr name="textOffsetPercentInRadius" />
<!-- 绘制内容相应的提示语 -->
<attr name="hint" />
<attr name="hintSize" />
<attr name="hintColor" />
<!-- 绘制内容的单位 -->
<attr name="unit" />
<attr name="unitSize" />
<attr name="unitColor" />
<!-- 圆弧宽度 -->
<attr name="arcWidth" />
<attr name="arcColors" />
<!-- 背景圆弧颜色 -->
<attr name="bgArcColor" />
<!-- 背景圆弧宽度 -->
<attr name="bgArcWidth" format="dimension" />
</declare-styleable>
<declare-styleable name="DialProgress">
<attr name="antiAlias" />
<attr name="startAngle" />
<attr name="sweepAngle" />
<attr name="animTime" />
<attr name="maxValue" />
<attr name="value" />
<attr name="precision" />
<attr name="valueSize" />
<attr name="valueColor" />
<attr name="textOffsetPercentInRadius" />
<!-- 绘制内容的单位 -->
<attr name="unit" />
<attr name="unitSize" />
<attr name="unitColor" />
<!-- 绘制内容相应的提示语 -->
<attr name="hint" />
<attr name="hintSize" />
<attr name="hintColor" />
<!-- 圆弧的宽度 -->
<attr name="arcWidth" />
<!-- 刻度的宽度 -->
<attr name="dialWidth" format="dimension|reference" />
<!-- 刻度之间的间隔 -->
<attr name="dialIntervalDegree" format="integer" />
<!-- 圆弧颜色, -->
<attr name="arcColors" />
<!-- 背景圆弧线颜色 -->
<attr name="bgArcColor" />
<!-- 刻度线颜色 -->
<attr name="dialColor" format="color|reference" />
</declare-styleable>
<declare-styleable name="WaveProgress">
<!-- 是否开启抗锯齿 -->
<attr name="antiAlias" />
<!-- 深色水波动画时间 -->
<attr name="darkWaveAnimTime" format="integer" />
<!-- 浅色水波动画时间 -->
<attr name="lightWaveAnimTime" format="integer" />
<!-- 最大值 -->
<attr name="maxValue" />
<!-- 当前值 -->
<attr name="value" />
<attr name="valueColor" />
<attr name="valueSize" />
<!-- 绘制内容相应的提示语 -->
<attr name="hint" />
<attr name="hintSize" />
<attr name="hintColor" />
<!-- 圆环宽度 -->
<attr name="circleWidth" format="dimension" />
<!-- 圆环颜色 -->
<attr name="circleColor" format="color|reference" />
<!-- 背景圆环颜色 -->
<attr name="bgCircleColor" format="color|reference" />
<!-- 锁定水波不随圆环进度改变,默认锁定在50%处 -->
<attr name="lockWave" format="boolean" />
<!-- 水波数量 -->
<attr name="waveNum" format="integer" />
<!-- 水波高度,峰值和谷值之和 -->
<attr name="waveHeight" format="dimension" />
<!-- 深色水波颜色 -->
<attr name="darkWaveColor" format="color|reference" />
<!-- 是否显示浅色水波 -->
<attr name="showLightWave" format="boolean" />
<!-- 浅色水波颜色 -->
<attr name="lightWaveColor" format="color|reference" />
<!-- 浅色水波的方向 -->
<attr name="lightWaveDirect" format="enum">
<enum name="L2R" value="0" />
<enum name="R2L" value="1" />
</attr>
</declare-styleable>
<dimen name="small">5dp</dimen>
<dimen name="medium">10dp</dimen>
<dimen name="normal">15dp</dimen>
<dimen name="large">20dp</dimen>
<dimen name="xlarge">25dp</dimen>
<dimen name="xxlarge">30dp</dimen>
<!-- text size -->
<dimen name="text_size_35">35sp</dimen>
<dimen name="text_size_34">34sp</dimen>
<dimen name="text_size_33">33sp</dimen>
<dimen name="text_size_32">32sp</dimen>
<dimen name="text_size_31">31sp</dimen>
<dimen name="text_size_30">30sp</dimen>
<dimen name="text_size_29">29sp</dimen>
<dimen name="text_size_28">28sp</dimen>
<dimen name="text_size_26">26sp</dimen>
<dimen name="text_size_25">25sp</dimen>
<dimen name="text_size_24">24sp</dimen>
<dimen name="text_size_23">23sp</dimen>
<dimen name="text_size_22">22sp</dimen>
<dimen name="text_size_21">21sp</dimen>
<dimen name="text_size_20">20sp</dimen>
<dimen name="text_size_19">19sp</dimen>
<dimen name="text_size_18">18sp</dimen>
<dimen name="text_size_17">17sp</dimen>
<dimen name="text_size_16">16sp</dimen>
<dimen name="text_size_15">15sp</dimen>
<dimen name="text_size_14">14sp</dimen>
<dimen name="text_size_13">13sp</dimen>
<dimen name="text_size_12">12sp</dimen>
<dimen name="text_size_11">11sp</dimen>
<dimen name="text_size_10">10sp</dimen>
<dimen name="text_size_9">9sp</dimen>
<dimen name="text_size_8">8sp</dimen>
<dimen name="text_size_7">7sp</dimen>
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.littlejie.app.MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<Button
android:id="@+id/btn_reset_all"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="重置" />
<com.littlejie.circleprogress.CircleProgress
android:id="@+id/circle_progress_bar1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
app:antiAlias="true"
app:arcWidth="@dimen/small"
app:bgArcColor="@color/colorAccent"
app:bgArcWidth="@dimen/small"
app:hint="截止当前已走"
app:hintSize="15sp"
app:maxValue="10000"
app:startAngle="135"
app:sweepAngle="270"
app:unit="步"
app:unitSize="15sp"
app:value="10000"
app:valueSize="25sp" />
<com.littlejie.circleprogress.CircleProgress
android:id="@+id/circle_progress_bar2"
android:layout_width="100dp"
android:layout_height="200dp"
android:layout_gravity="center_horizontal"
app:antiAlias="true"
app:arcWidth="@dimen/small"
app:bgArcColor="@color/colorAccent"
app:bgArcWidth="@dimen/small"
app:hint="百分比"
app:hintSize="@dimen/text_size_15"
app:maxValue="100"
app:startAngle="135"
app:sweepAngle="270"
app:textOffsetPercentInRadius="0.5"
app:unit="%"
app:unitSize="@dimen/text_size_15"
app:value="75"
app:valueSize="@dimen/text_size_20" />
<com.littlejie.circleprogress.CircleProgress
android:id="@+id/circle_progress_bar3"
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_gravity="center_horizontal"
app:antiAlias="true"
app:arcWidth="@dimen/small"
app:bgArcColor="@android:color/darker_gray"
app:bgArcWidth="@dimen/small"
app:hint="当前进度"
app:hintSize="@dimen/text_size_25"
app:maxValue="100"
app:startAngle="270"
app:sweepAngle="360"
app:unit="%"
app:unitSize="@dimen/text_size_25"
app:value="100"
app:valueSize="@dimen/text_size_35" />
<com.littlejie.circleprogress.DialProgress
android:id="@+id/dial_progress_bar"
android:layout_width="300dp"
android:layout_height="300dp"
android:layout_gravity="center_horizontal"
android:padding="@dimen/medium"
app:animTime="1000"
app:arcColors="@array/gradient_arc_color"
app:arcWidth="@dimen/large"
app:dialIntervalDegree="3"
app:dialWidth="2dp"
app:hint="当前时速"
app:hintSize="@dimen/text_size_25"
app:maxValue="360"
app:startAngle="270"
app:sweepAngle="360"
app:unit="km/h"
app:unitSize="@dimen/text_size_25"
app:value="360"
app:valueSize="@dimen/text_size_35" />
<com.littlejie.circleprogress.WaveProgress
android:id="@+id/wave_progress_bar"
android:layout_width="300dp"
android:layout_height="300dp"
android:layout_gravity="center_horizontal"
app:darkWaveAnimTime="1000"
app:darkWaveColor="@color/dark"
app:lightWaveAnimTime="2000"
app:lightWaveColor="@color/light"
app:lightWaveDirect="R2L"
app:lockWave="false"
app:valueSize="@dimen/text_size_35"
app:waveHeight="30dp"
app:waveNum="1" />
</LinearLayout>
</ScrollView>
使用方法
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private final static int[] COLORS = new int[]{Color.RED, Color.YELLOW, Color.GREEN, Color.BLUE};
private Button mBtnResetAll;
private CircleProgress mCircleProgress1, mCircleProgress2, mCircleProgress3;
private DialProgress mDialProgress;
private WaveProgress mWaveProgress;
private Random mRandom;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mBtnResetAll = (Button) findViewById(R.id.btn_reset_all);
mCircleProgress1 = (CircleProgress) findViewById(R.id.circle_progress_bar1);
mCircleProgress2 = (CircleProgress) findViewById(R.id.circle_progress_bar2);
mCircleProgress3 = (CircleProgress) findViewById(R.id.circle_progress_bar3);
mDialProgress = (DialProgress) findViewById(R.id.dial_progress_bar);
mWaveProgress = (WaveProgress) findViewById(R.id.wave_progress_bar);
mBtnResetAll.setOnClickListener(this);
mCircleProgress1.setOnClickListener(this);
mCircleProgress2.setOnClickListener(this);
mCircleProgress3.setOnClickListener(this);
mDialProgress.setOnClickListener(this);
mWaveProgress.setOnClickListener(this);
mRandom = new Random();
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_reset_all:
mCircleProgress1.reset();
mCircleProgress2.reset();
mCircleProgress3.reset();
mDialProgress.reset();
break;
case R.id.circle_progress_bar1:
mCircleProgress1.setValue(mRandom.nextInt((int) mCircleProgress1.getMaxValue()));
break;
case R.id.circle_progress_bar2:
mCircleProgress2.setValue(mRandom.nextFloat() * mCircleProgress2.getMaxValue());
break;
case R.id.circle_progress_bar3:
//在代码中动态改变渐变色,可能会导致颜色跳跃
mCircleProgress3.setGradientColors(COLORS);
mCircleProgress3.setValue(mRandom.nextFloat() * mCircleProgress3.getMaxValue());
break;
case R.id.dial_progress_bar:
mDialProgress.setValue(mRandom.nextFloat() * mDialProgress.getMaxValue());
break;
case R.id.wave_progress_bar:
mWaveProgress.setValue(mRandom.nextFloat() * mWaveProgress.getMaxValue());
break;
}
}
}
color.xml
<color name="dark">#803cbcb7</color>
<color name="light">#800de6e8</color>
<color name="green">#00FF00</color>
<color name="blue">#EE9A00</color>
<color name="red">#EE0000</color>
<integer-array name="gradient_arc_color">
<item>@color/green</item>
<item>@color/blue</item>
<item>@color/red</item>
</integer-array>
dimens.xml
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
标签:进度条,int,void,float,private,升级,styleable,typedArray,Android 来源: https://blog.csdn.net/qq_42795723/article/details/113869495