Android圆形进度条控件-CircleSeekBar
1.引言 博主Android开发起步没多久,一脚踏入社会工作。对Android可以说是非常的喜欢,这里花了一天多的时间写了一个圆形进度条的控件,没有参考其他类似控件的实现方式。如果有什么好的建议,或者需要我改善的,希望大家能够指出,和你们一起进步哦。 另外项目我已经放到Git上,大家可以随意使用。CircleSeekBar项目地址:https://github.com/Hellobird/CircleSeekBar-For-Android。 2.预览图 感觉效果还可以吧,下面讲下主要的方法。
3.主要方法
首先该类是继承自View类的,重写了onDraw、onMeasure方法。onDraw主要控制界面的绘制,onMeasure主要计算了一些重要参数,列如进度条边框的Rect,View的高度宽度等。
3.1 onDraw()方法
该方法因为是绘制调用的方法,所以不能涉及大量的运算,或者声明变量。这里主要是包括进度的绘制,文字的绘制,以及淡入淡出效果、缩放效果效果的设置。进度绘制主要使用了drawArc()这个方法,可以根据自己的设定画弧。动画的刷新主要靠判断当当前进度还未到达目标进度时,会调用invalidate()继续刷新。我也不知道常用滑动效果是否是这样不断刷新出来的,如果有更好的方法务必请告诉我一下。
@Override
protected void onDraw(Canvas canvas) {
// 判断当前角度偏移方向
if (mCurrentAngle > mTargetAngle) {
mCurrentAngle = mCurrentAngle - mVelocity;
if (mCurrentAngle < mTargetAngle) {
mCurrentAngle = mTargetAngle;
}
} else if (mCurrentAngle < mTargetAngle) {
mCurrentAngle = mCurrentAngle + mVelocity;
if (mCurrentAngle > mTargetAngle) {
mCurrentAngle = mTargetAngle;
}
}
float ratio = mCurrentAngle / 360f;
// 设置透明度
if (mFadeEnable) {
int alpha = (int) ((mEndAlpha - mStartAlpha) * ratio);
mProgressPaint.setAlpha(alpha);
}
// 设置二级进度缩放效果
if (mZoomEnable) {
zoomSProgressRect(ratio);
}
// 绘制二级进度条
canvas.drawArc(mSProgressRect, 0, 360f, false, mSProgressPaint);
// 绘制进度条
canvas.drawArc(mProgressRect, mStartAngle, mCurrentAngle, mUseCenter,
mProgressPaint);
// 绘制字体
if (mShowText) {
String text = formatProgress(mCurrentAngle / 360f * mMaxProgress);
mTextPaint.getTextBounds(text, 0, text.length(), mTextBounds);
canvas.drawText(text, (getWidth() - mTextBounds.width()) >> 1,
(getHeight() >> 1) + (mTextBounds.height() >> 1),
mTextPaint);
}
// 如果当前进度不等于目标进度,继续绘制
if (mCurrentAngle != mTargetAngle) {
invalidate();
}
}
3.2 onMeasure()方法 该方法主要是计算控件所需空间,以及确定进度环绘制的位置。这里的计算凡是除以2的,我都用的位运算替代,因为这个比除法快多了,不过不知道是否有隐患,知道的高手希望能解答一下。
@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);
int width;
int height;
if (widthMode == MeasureSpec.EXACTLY) {
width = widthSize;
} else {
int desired = (int) (getPaddingLeft()
+ DimenUtils.dip2px(mContext, MIN_WIDTH) + getPaddingRight());
width = desired;
}
if (heightMode == MeasureSpec.EXACTLY) {
height = heightSize;
} else {
int desired = (int) (getPaddingTop()
+ DimenUtils.dip2px(mContext, MIN_HEIGHT) + getPaddingBottom());
height = desired;
}
setMeasuredDimension(width, height);
/* 计算进度显示的矩形框 */
float radius = width > height ? height >> 1 : width >> 1;
float maxStrokeWidth = mProgressStrokeWidth > mSProgressStrokeWidth ? mProgressStrokeWidth
: mSProgressStrokeWidth;
radius = radius - getMaxPadding() - maxStrokeWidth;
int centerX = width >> 1;
int centerY = height >> 1;
mProgressRect.set(centerX - radius, centerY - radius, centerX + radius,
centerY + radius);
mSProgressRect = new RectF(mProgressRect);
3.3 控件属性定义
3.4总结
Android原生控件虽然少,但魅力就在可以随心所欲制定自己想要的控件和样式。这个方法里其实最重要的也就是onDraw和onMeasure方法,加起来一百来行代码,实现的效果感觉还不错。另外对控件有什么疑问或者是觉得可以改进和增加的效果,可以评论我哦。如果想要自己动手,也可以去git上挥洒你的代码。希望大家相互学习支持,让Android学习更加有趣。最后附上完整代码。
public class CircleSeekBar extends View { /* 最小宽度,单位为dp */
private static int MIN_WIDTH = 50;
/* 最小高度,单位为dp */
private static int MIN_HEIGHT = 50;
/* 默认模式 */
public static int MODE_DEFAULT = 0;
/* 笔画模式 */
public static int MODE_STROKE = 0;
/* 填充模式 */
public static int MODE_FILL = 1;
/* 笔画&填充模式 */
public static int MODE_FILL_AND_STROKE = 2;
/* 进度格式化默认值 */
private static String PROGRESS_FORMAT_DEFAULT = "##0.0";
/* 进度默认最大值 */
private static float MAX_PROGRESS_DEFAULT = 100f;
/* 开始位置角度默认值 */
private static final float START_ANGLE_DEFAULT = 0f;
/* 刷新滑动速度默认值 */
private static final float VELOCITY_DEFAULT = 3.0f;
/* 文字大小默认值,单位为sp */
private static final float TEXT_SIZE_DEFAULT = 10.0f;
/* 默认文字颜色 */
private static final int TEXT_COLOR_DEFAULT = 0xffbf5252;
/* 进度条边框宽度默认值,单位为dp */
private static final float PROGRESS_WIDTH_DEFAULT = 5.0f;
/* 默认进度颜色 */
private static final int PROGRESS_COLOR_DEFAULT = 0xff3d85c6;
/* 进度条底色默认值,单位为dp */
private static final float S_PROGRESS_WIDTH_DEFAULT = 2.0f;
/* 默认进度颜色 */
private static final int S_PROGRESS_COLOR_DEFAULT = 0xffdddddd;
private Context mContext;
private Paint mPaint;
private Paint mTextPaint;
private Paint mProgressPaint;
private Paint mSProgressPaint;
private int mMode;
// 进度模式
private float mMaxProgress;
// 最大进度
private boolean mShowText;
// 是否显示文字
private float mStartAngle;
// 起始角度
private float mVelocity;
// 速度
private float mTextSize;
// 字体大小
private int mTextColor;
// 字体颜色
private float mProgressStrokeWidth;
// 进度条宽度
private int mProgressColor;
// 进度颜色
private float mSProgressStrokeWidth;
// 二级进度宽度
private int mSProgressColor;
// 二级进度颜色
private boolean mFadeEnable;
// 是否开启淡入淡出效果
private int mStartAlpha;
// 开始透明度,0~255
private int mEndAlpha;
// 结束透明度,0~255
private boolean mZoomEnable;
// 二级进度缩放 private RectF mProgressRect;
private RectF mSProgressRect;
private Rect mTextBounds;
private float mCurrentAngle;
// 当前角度
private float mTargetAngle;
// 目标角度
private boolean mUseCenter;
// 是否从中心绘制
private DecimalFormat mFormat;
// 格式化数值 public CircleSeekBar(Context context, AttributeSet attrs) {
this(context, attrs, 0);
} public CircleSeekBar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mContext = context;
init(attrs);
} private void init(AttributeSet attrs) {
if (attrs != null) {
TypedArray type = mContext.obtainStyledAttributes(attrs,
R.styleable.CircleSeekBar);
mMode = type.getInt(R.styleable.CircleSeekBar_mode, MODE_DEFAULT);
mMaxProgress = type.getFloat(R.styleable.CircleSeekBar_maxProgress,
MAX_PROGRESS_DEFAULT);
mShowText = type.getBoolean(R.styleable.CircleSeekBar_showText,
true);
mStartAngle = type.getFloat(R.styleable.CircleSeekBar_startAngle,
START_ANGLE_DEFAULT);
mVelocity = type.getFloat(R.styleable.CircleSeekBar_velocity,
VELOCITY_DEFAULT);
mTextSize = type.getDimension(R.styleable.CircleSeekBar_textSize,
DimenUtils.dip2px(mContext, TEXT_SIZE_DEFAULT));
mTextColor = type.getColor(R.styleable.CircleSeekBar_textColor,
TEXT_COLOR_DEFAULT);
mProgressStrokeWidth = type.getDimension(
R.styleable.CircleSeekBar_progressWidth,
DimenUtils.dip2px(mContext, PROGRESS_WIDTH_DEFAULT));
mProgressColor = type.getColor(
R.styleable.CircleSeekBar_progressColor,
PROGRESS_COLOR_DEFAULT);
mSProgressStrokeWidth = type.getDimension(
R.styleable.CircleSeekBar_sProgressWidth,
DimenUtils.dip2px(mContext, S_PROGRESS_WIDTH_DEFAULT));
mSProgressColor = type.getColor(
R.styleable.CircleSeekBar_sProgressColor,
S_PROGRESS_COLOR_DEFAULT);
mFadeEnable = type.getBoolean(R.styleable.CircleSeekBar_fadeEnable,
false);
mStartAlpha = type
.getInt(R.styleable.CircleSeekBar_startAlpha, 255);
mEndAlpha = type.getInt(R.styleable.CircleSeekBar_endAlpha, 255);
mZoomEnable = type.getBoolean(R.styleable.CircleSeekBar_zoomEnable,
false);
float progress = type.getFloat(R.styleable.CircleSeekBar_progress,
0);
progress = progress > mMaxProgress || progress < 0f ? 0f : progress;
mTargetAngle = progress / mMaxProgress * 360f;
mCurrentAngle = mTargetAngle;
type.recycle();
} else {
mMode = MODE_DEFAULT;
mMaxProgress = MAX_PROGRESS_DEFAULT;
mStartAngle = START_ANGLE_DEFAULT;
mVelocity = VELOCITY_DEFAULT;
mTextSize = TEXT_SIZE_DEFAULT;
mTextColor = TEXT_COLOR_DEFAULT;
mProgressStrokeWidth = PROGRESS_WIDTH_DEFAULT;
mProgressColor = PROGRESS_COLOR_DEFAULT;
mSProgressStrokeWidth = S_PROGRESS_WIDTH_DEFAULT;
mSProgressColor = S_PROGRESS_COLOR_DEFAULT;
mTargetAngle = 0f;
mCurrentAngle = 0f;
mStartAlpha = 255;
mEndAlpha = 255;
mZoomEnable = false;
}
mPaint = new Paint();
mPaint.setAntiAlias(true);
mTextPaint = new Paint(mPaint);
mTextPaint.setColor(mTextColor);
mTextPaint.setTextSize(mTextSize);
mProgressPaint = new Paint(mPaint);
mProgressPaint.setColor(mProgressColor);
mProgressPaint.setStrokeWidth(mProgressStrokeWidth);
mSProgressPaint = new Paint(mProgressPaint);
mSProgressPaint.setColor(mSProgressColor);
mSProgressPaint.setStrokeWidth(mSProgressStrokeWidth);
if (mMode == MODE_FILL_AND_STROKE) {
mProgressPaint.setStyle(Style.FILL);
mSProgressPaint.setStyle(Style.FILL_AND_STROKE);
mUseCenter = true;
} else if (mMode == MODE_FILL) {
mProgressPaint.setStyle(Style.FILL);
mSProgressPaint.setStyle(Style.FILL);
mUseCenter = true;
} else {
mProgressPaint.setStyle(Style.STROKE);
mSProgressPaint.setStyle(Style.STROKE);
mUseCenter = false;
}mProgressRect = new RectF();
mTextBounds = new Rect();
mFormat = new DecimalFormat(PROGRESS_FORMAT_DEFAULT);
} @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);
int width;
int height;
if (widthMode == MeasureSpec.EXACTLY) {
width = widthSize;
} else {
int desired = (int) (getPaddingLeft()
+ DimenUtils.dip2px(mContext, MIN_WIDTH) + getPaddingRight());
width = desired;
}
if (heightMode == MeasureSpec.EXACTLY) {
height = heightSize;
} else {
int desired = (int) (getPaddingTop()
+ DimenUtils.dip2px(mContext, MIN_HEIGHT) + getPaddingBottom());
height = desired;
}
setMeasuredDimension(width, height);
/* 计算进度显示的矩形框 */
float radius = width > height ? height >> 1 : width >> 1;
float maxStrokeWidth = mProgressStrokeWidth > mSProgressStrokeWidth ? mProgressStrokeWidth
: mSProgressStrokeWidth;
radius = radius - getMaxPadding() - maxStrokeWidth;
int centerX = width >> 1;
int centerY = height >> 1;
mProgressRect.set(centerX - radius, centerY - radius, centerX + radius,
centerY + radius);
mSProgressRect = new RectF(mProgressRect);
} @Override
protected void onDraw(Canvas canvas) {
// 判断当前角度偏移方向
if (mCurrentAngle > mTargetAngle) {
mCurrentAngle = mCurrentAngle - mVelocity;
if (mCurrentAngle < mTargetAngle) {
mCurrentAngle = mTargetAngle;
}
} else if (mCurrentAngle < mTargetAngle) {
mCurrentAngle = mCurrentAngle + mVelocity;
if (mCurrentAngle > mTargetAngle) {
mCurrentAngle = mTargetAngle;
}
}
float ratio = mCurrentAngle / 360f;
// 设置透明度
if (mFadeEnable) {
int alpha = (int) ((mEndAlpha - mStartAlpha) * ratio);
mProgressPaint.setAlpha(alpha);
}
// 设置二级进度缩放效果
if (mZoomEnable) {
zoomSProgressRect(ratio);
}
// 绘制二级进度条
canvas.drawArc(mSProgressRect, 0, 360f, false, mSProgressPaint);
// 绘制进度条
canvas.drawArc(mProgressRect, mStartAngle, mCurrentAngle, mUseCenter,
mProgressPaint);
// 绘制字体
if (mShowText) {
String text = formatProgress(mCurrentAngle / 360f * mMaxProgress);
mTextPaint.getTextBounds(text, 0, text.length(), mTextBounds);
canvas.drawText(text, (getWidth() - mTextBounds.width()) >> 1,
(getHeight() >> 1) + (mTextBounds.height() >> 1),
mTextPaint);
}
// 如果当前进度不等于目标进度,继续绘制
if (mCurrentAngle != mTargetAngle) {
invalidate();
}
} /**
* 格式化进度
*
* @param progress
* @return
*/
private String formatProgress(float progress) {
return mFormat.format(progress);
} /**
* 获取内边距最大值
*
* @return
*/
private int getMaxPadding() {
int maxPadding = getPaddingLeft();
int paddingRight = getPaddingRight();
int paddingTop = getPaddingTop();
int paddingBottom = getPaddingBottom();
if (maxPadding < paddingRight) {
maxPadding = paddingRight;
}
if (maxPadding < paddingTop) {
maxPadding = paddingTop;
}
if (maxPadding < paddingBottom) {
maxPadding = paddingBottom;
}
return maxPadding;
} /**
* 缩放二级进度条
*
* @param ratio
*/
private void zoomSProgressRect(float ratio) {
float width = mProgressRect.width();
float height = mProgressRect.height();
float centerX = mProgressRect.centerX();
float centerY = mProgressRect.centerY();
float offsetX = width * 0.5f * ratio;
float offsetY = height * 0.5f * ratio;
float left = centerX - offsetX;
float right = centerX + offsetX;
float top = centerY - offsetY;
float bottom = centerY + offsetY;
mSProgressRect.set(left, top, right, bottom);
} @Override
protected void onDisplayHint(int hint) {
if (hint == View.VISIBLE) {
mCurrentAngle = 0;
invalidate();
}
super.onDisplayHint(hint);
} /**
* 设置目标进度
*
* @param progress
*/
public void setProgress(float progress) {
progress = progress > mMaxProgress || progress < 0f ? 0f : progress;
mTargetAngle = progress / mMaxProgress * 360f;
postInvalidate();
} /**
* 设置目标进度
*
* @param progress
*进度值
* @param isAnim
*是否有动画
*/
public void setProgressWithAnim(float progress, boolean isAnim) {
if (isAnim) {
setProgress(progress);
} else {
progress = progress > mMaxProgress || progress < 0f ? 0f : progress;
mCurrentAngle = progress / mMaxProgress * 360f;
mTargetAngle = mCurrentAngle;
postInvalidate();
}
} /**
* 设置进度画笔着色方式
*
* @param shader
*/
public void setProgressShader(Shader shader) {
this.mProgressPaint.setShader(shader);
invalidate();
} /**
* 设置二级进度画笔着色方式
*
* @param shader
*/
public void setSProgressShader(Shader shader) {
this.mSProgressPaint.setShader(shader);
invalidate();
} public void setMaxProgress(float max) {
this.mMaxProgress = max;
} public float getMaxProgress() {
return mMaxProgress;
} public int getMode() {
return mMode;
} public void setMode(int mMode) {
this.mMode = mMode;
} public float getStartAngle() {
return mStartAngle;
} public void setStartAngle(float mStartAngle) {
this.mStartAngle = mStartAngle;
} public float getVelocity() {
return mVelocity;
} public void setVelocity(float mVelocity) {
this.mVelocity = mVelocity;
} public float getTextSize() {
return mTextSize;
} public void setTextSize(float mTextSize) {
this.mTextSize = mTextSize;
} public int getTextColor() {
return mTextColor;
} public void setTextColor(int mTextColor) {
this.mTextColor = mTextColor;
} public float getProgressStrokeWidth() {
return mProgressStrokeWidth;
} public void setProgressStrokeWidth(float mProgressStrokeWidth) {
this.mProgressStrokeWidth = mProgressStrokeWidth;
} public int getProgressColor() {
return mProgressColor;
} public void setProgressColor(int mProgressColor) {
this.mProgressColor = mProgressColor;
} public float getSProgressStrokeWidth() {
return mSProgressStrokeWidth;
} public void setSProgressStrokeWidth(float mSProgressStrokeWidth) {
this.mSProgressStrokeWidth = mSProgressStrokeWidth;
} public int getSProgressColor() {
return mSProgressColor;
} public void setSProgressColor(int mSProgressColor) {
this.mSProgressColor = mSProgressColor;
} public boolean isFadeEnable() {
return mFadeEnable;
} public void setFadeEnable(boolean mFadeEnable) {
this.mFadeEnable = mFadeEnable;
} public int getStartAlpha() {
return mStartAlpha;
} public void setStartAlpha(int mStartAlpha) {
this.mStartAlpha = mStartAlpha;
} public int getEndAlpha() {
return mEndAlpha;
} public void setEndAlpha(int mEndAlpha) {
this.mEndAlpha = mEndAlpha;
} public boolean isZoomEnable() {
return mZoomEnable;
} public void setZoomEnable(boolean mZoomEnable) {
this.mZoomEnable = mZoomEnable;
}
}
【Android圆形进度条控件-CircleSeekBar】
推荐阅读
- 学习笔记|uni-app开发小程序
- java计算文本MD5值
- 学习笔记|安卓中一些界面过场动画的实现
- MongoDB-存储
- 学习笔记|Burnside引理和polay计数学习笔记
- Python|【网易2019年秋招笔试题】编程题第二题(香槟塔里倒香槟——参考代码和编程思路)
- Android|安装APP损坏,出现[INSTALL_FAILED_DEXOPT]的解决办法
- Android|java日期格式化
- Android|android 读取json数据(遍历JSONObject和JSONArray