Android圆形进度条控件-CircleSeekBar

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()继续刷新。我也不知道常用滑动效果是否是这样不断刷新出来的,如果有更好的方法务必请告诉我一下。
<span style="white-space:pre">	</span>@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的,我都用的位运算替代,因为这个比除法快多了,不过不知道是否有隐患,知道的高手希望能解答一下。

<span style="white-space:pre">	</span>@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 控件属性定义

<resources>

    <declare-styleable name="CircleSeekBar">
        <attr name="mode">
            <enum name="stroke" value="0" />
            <enum name="fill" value="1" />
            <enum name="fill_and_stroke" value="2" />
        </attr>
        <attr name="showText" format="boolean" />
        <attr name="startAngle" format="float" />
        <attr name="textSize" format="dimension" />
        <attr name="textColor" format="color" />
        <attr name="progressWidth" format="dimension" />
        <attr name="progressColor" format="color" />
        <attr name="sProgressWidth" format="dimension" />
        <attr name="sProgressColor" format="color" />
        <attr name="progress" format="float" />
        <attr name="maxProgress" format="float" />
        <attr name="velocity" format="float" />
        <attr name="fadeEnable" format="boolean" />
        <attr name="startAlpha" format="integer" />
        <attr name="endAlpha" format="integer" />
        <attr name="zoomEnable" format="boolean" />
    </declare-styleable>

</resources>

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;
	}
}




评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值