自定义ProgressBar

无简介

完成后的样式

ProgressBarDemo_1

分析

首先我们从这张图的表面就可以看到三样:

  • 1.左边的进度条
  • 2.中间的文字
  • 3.右边的进度条

其实除了这些,还有一个

  • 文字与左右的边距

当我们将这个View编写完成,交给其他编程人员使用时,他们肯定希望在xml中定义的时候就可以设置以上的那些属性值,方便他们的布局以及样式的定义。所以

  • 一.首先我们应该编写attrs

attrs编写完成之后,我们就应该编写我们的view了

  • 二.创建view,继承ProgressBar,实现构造方法

构造方法实现了,在我们画出我们想要的图形之前,我们应该先获得用户在xml中对上面那些属性的自定义的值,如果没有这些值,我们应该给它一个默认值

  • 三.获取自定义属性

属性获取完成之后,开始测量控件的大小

  • 四.测量控件

最后,画出我们想要的图形

  • 五.画出进度条

编写attrs

根据上面所写出的属性,我们可以分析并编写出以下attr,其中unreach表示未到达的进度条,也就是文字后面的进度条,reach表示已经到达的进度条,也就是文字前面的进度条,color和height分别代表了进度条的颜色以及它的高度(宽度),text则是中间的文字,color和size分别代表了文字的颜色和文字的大小。text_offset代表文字与两边进度条的总间距。(注意:是总间距,一遍的间距就是总间距的一半)







创建view

public class HorizontalProgressBar extends ProgressBar {

public HorizontalProgressBar(Context context) {
    this(context, null);
}

public HorizontalProgressBar(Context context, AttributeSet attrs) {
    this(context, attrs, 0);
}

public HorizontalProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
}

}

这一步之后,至少我们现在是一个ProgressBar了

获取自定义属性

现在我们要获取用户对各属性定义的值了,但是我们要想,如果用户没有定义这个值,那么我们就必须为这个属性设置一个默认值 下面就先给上面说的那7个属性设置默认值

private static final int DEFAULT_TEXT_SIZE = 10;//文字大小
private static final int DEFAULT_TEXT_COLOR = 0xffFC00D1;//文字颜色
private static final int DEFAULT_Color_UNREACH = 0xFFD3D6DA;//文字右侧进度条颜色
private static final int DEFAULT_HEIGHT_UNREACH = 2;//文字右侧进度条高度
private static final int DEFAULT_COLOR_REACH = DEFAULT_TEXT_COLOR;//文字左侧进度条颜色
private static final int DEFAULT_HEIGHT_REACH = 2;//文字右侧进度条高度
private static final int DEFAULT_TEXT_OFFSET = 10;//文字两边的间距的总和

上面定义的这些默认值,有的是sp(文字的大小),有的是dp(各种间距以及高度),我们需要将它们统一转为px

private int spToPx(int spValue) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, spValue, getResources().getDisplayMetrics());
}

private int dpToPx(int dpValue) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpValue, getResources().getDisplayMetrics());
}

通过上面的两个方法,我们把默认值进行转换

private int mTextSize = spToPx(DEFAULT_TEXT_SIZE);
private int mTextColor = DEFAULT_TEXT_COLOR;
private int mUnreachHeight = dpToPx(DEFAULT_HEIGHT_UNREACH);
private int mUnreachColor = DEFAULT_Color_UNREACH;
private int mReachHeight = dpToPx(DEFAULT_HEIGHT_REACH);
private int mReachColor = DEFAULT_COLOR_REACH;
private int mTextOffeset = dpToPx(DEFAULT_TEXT_OFFSET);

接着,我们就可以获取用户自定义的那些属性了,这里我们创建了一个方法,在三个参数的构造方法中调用

private void obtainStyleAttrs(AttributeSet attrs) {
TypedArray ta = getContext().obtainStyledAttributes(attrs, R.styleable.HorizontalProgressBar);
mTextColor = ta.getColor(R.styleable.HorizontalProgressBar_progress_text_color, mTextColor);
mTextSize = (int) ta.getDimension(R.styleable.HorizontalProgressBar_progress_text_size, mTextSize);
mReachColor = ta.getColor(R.styleable.HorizontalProgressBar_progress_reach_color, mReachColor);
mReachHeight = (int) ta.getDimension(R.styleable.HorizontalProgressBar_progress_reach_height, mReachHeight);
mUnreachColor = ta.getColor(R.styleable.HorizontalProgressBar_progress_unreach_color, mUnreachColor);
mUnreachHeight = (int) ta.getDimension(R.styleable.HorizontalProgressBar_progress_unreach_height, mUnreachHeight);
ta.recycle();
}

这样完成之后,我们就获得了用户定义的各属性的值,用户没有定义的值,我们也给它用默认值填上了。 万事具备

测量控件

我们知道,当我们的父元素要开始绘制我们的时候,它会来问我们:你需要多大的地方,你告诉我,我把你画出来,同时给了我们两个东西,一个是widthMeasureSpec,另一个是heightMeasureSpec。从这两个参数中,我们就可以获得它的模式以及它的大小。我们以高度为例,来测量下他的真实高度:

private int measureHeight(int heightMeasureSpec) {
int result = 0;
int mode = MeasureSpec.getMode(heightMeasureSpec);
int size = MeasureSpec.getSize(heightMeasureSpec);
if (mode == MeasureSpec.EXACTLY) {
result = size;
} else {
int textSize = (int) (mPaint.descent() - mPaint.ascent());
result = getPaddingTop() + getPaddingBottom() + Math.max(Math.max(mUnreachHeight, mReachHeight), Math.abs(textSize));
if (mode == MeasureSpec.AT_MOST) {
result = Math.min(size, result);
}
}
return result;
}

可以看到,我们在进入这个测量方法的时候,首先就先获得了在高度上它的模式是什么,如果它的模式是EXACTLY,也就是绝对布局,那么我们获得什么高度就给他设置什么高度就可以了,这种是最简单的。除了EXACTLY之外,还有另外两种模式:UNSPECIFIED以及AT_MOST,下面是他们的介绍

  1. UNSPECIFIED(未指定),父元素部队自元素施加任何束缚,子元素可以得到任意想要的大小;
  2. EXACTLY(完全),父元素决定自元素的确切大小,子元素将被限定在给定的边界里而忽略它本身大小;
  3. AT_MOST(至多),子元素至多达到指定大小的值。

除了EXACTLY是由用户自定义了高度之外,其他的都需要我们去测量高度。在高度上,我们分成了三部分,我们需要知道文字的高度,左侧进度条的高度,右侧进度条的高度,并从这三者中获得最高的那个作为我们测量出来的结果高度。 文字的高度我们通过来计算,具体看图 paint_text_info

1.基准点是baseline 2.ascent:是baseline之上至字符最高处的距离 3.descent:是baseline之下至字符最低处的距离 4.leading:是上一行字符的descent到下一行的ascent之间的距离,也就是相邻行间的空白距离 5.top:是指的是最高字符到baseline的值,即ascent的最大值 6.bottom:是指最低字符到baseline的值,即descent的最大值

int textSize = (int) (mPaint.descent() - mPaint.ascent());

这样我们就获得了文字的高度,通过Math的max方法,找到三者中最大的值,同时不要忘了加上它的paddingTop以及paddingBottom 现在我们获得了我们测量出来的值,如果模式是AT_MOST,这就表示,我给你设置了一个值,如果你的值大于我预设的这个值,你必须得按我的走

if (mode == MeasureSpec.AT_MOST) {
result = Math.min(size, result);
}

这样我们就测量出来了高度。 最后我们通过setMeasuredDimension方法告诉父控件,就按照这个给我绘制吧。 最后我们可以获得到控件真正的宽度,下面会用到。

mRealWidth = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();

画出进度条

所有的都完成之后,我们开始绘制进度条,进度条绘制在onDraw中 首先,我们对画布进行操作

canvas.save();
canvas.translate(getPaddingLeft(), getHeight() / 2);

然后我们来绘制文字左侧的进度条。 我们可以想象,现在我们要绘制的进度条分成了三个部分,左侧进度条,文字,右侧进度条,这三个部分加起来应该是等于mRealWidth,文字两边的边距,我们要分别从左侧进度条和右侧进度条中去扣掉。那么现在我们可以很容易的获得现在progressBar的进度的百分比,进而获得左侧进度条实际上占据了mRealWidth中的多少

float radio = getProgress() * 1.0f / getMax();
float progressX = radio * mRealWidth;

这里我们要真正理解 progressX 指的是什么,progressX 指的是左侧进度条+文字的左边距,那么,如果这个progressX - 文字的左边距小于0,这个左侧进度条就不用显示了 下面的代码计算了 progressX(进度条与间距的和)- mTextOffset(间距) = 进度条真正显示的长度,如果这个长度是大于0的,那么这个进度条才需要显示,才需要被画出来

float endX = progressX - mTextOffset / 2;
if (endX > 0) {
mPaint.setColor(mReachColor);
mPaint.setStrokeWidth(mReachHeight);
canvas.drawLine(0, 0, endX, 0, mPaint);
}

想明白了这个,再想一下,等进度到了99%,这个时候左侧进度条和文字的宽度加起来应该已经和mRealWidth差不多宽了,我们当然不需要绘制右侧的进度条了 所以,在这里我们要计算一下,如果左侧进度条的宽度加上文字的宽度已经大于或者等于了mRealWidth,那么我们就设置一个标志位,不再绘制右侧的进度条,同时,左侧进度条的宽度恒定为mRealWidth减去文字的宽度,这个结果实际上包括了左侧的间距

String text = getProgress() + “%”;
float textWidth = mPaint.measureText(text);
if (textWidth + progressX >= mRealWidth) {
progressX = mRealWidth - textWidth;
noNeedUnreach = true;
}

左侧进度条绘制完成之后开始绘制文字

mPaint.setColor(mTextColor);
mPaint.setStrokeWidth(mTextSize);
int y = (int) (Math.abs(mPaint.descent() + mPaint.ascent()) / 2) ;
canvas.drawText(text, progressX, y, mPaint);

最后绘制右侧的进度条,根据我们的标志位来获得。开始绘制的位置 = 左侧进度条显示长度 + 文字长度 + 右侧间距,当然这个值有可能超过mRealWidth,所以我们取两者之间小的

if (!noNeedUnreach) {
float start = progressX + mTextOffset / 2 + textWidth;
mPaint.setColor(mUnreachColor);
mPaint.setStrokeWidth(mUnreachHeight);
canvas.drawLine(Math.min(start, mRealWidth), 0, mRealWidth, 0, mPaint);
}

最后不要忘了

canvas.restore();

使用

在xml中定义如下

<cn.sumile.progress.HorizontalProgressBar
android:id=“@+id/progress”
android:layout_width=“match_parent”
android:layout_height=“wrap_content”
android:layout_marginTop=“30dp”
sumile:progress_text_color=“#000000”
sumile:progress_unreach_color=“#169711”
sumile:progress_reach_color=“#1a36be”
android:padding=“5dp”
android:progress=“35”
android:indeterminate=“false” />

兄弟篇下

自定义圆形进度条 完 视频观看:慕课网 [gt href=‘https://github.com/wudkj/DemoApplication/tree/master/progress’]GitHub地址[/gt]

-------------本文结束  感谢您的阅读-------------
下次一定