现有需求,根据服务端获取到的数据,进行百分比展示,如下图所示:
若要使用自定义View来实现,有几个步骤,第一步,创建资源文件attrs.xml:首先添加
<resources xmlns:tools="http://schemas.android.com/tools">
<attr name="borderRadius" format="dimension" />
<attr name="borderColor" format="color" />
<attr name="type">
<enum name="circle" value="0" />
<enum name="round" value="1" />
</attr>
介绍一下这几个参数的作用:
dimension: 对应的是尺寸,可以是px、dp等
color:对应的是0xXXXXXX之类的16进制数,如:0xFF00CC56
一般都是带有透明度的颜色,这里的‘FF’的作用即是如此
value:value值就比较好理解了,即是对应的是原型还是方形
这是格式化参数类型,我们还要有我们所需要的参数的默认值
<declare-styleable name="HorizontalProgressBar">
<attr name="horizontalProgressColor" format="color" />
<attr name="horizontalUnProgressColor" format="color" />
<attr name="horizontalProgressWidth" format="dimension" />
<attr name="horizontalProgressHeight" format="dimension" />
<attr name="horizontalBorderWidth" format="dimension" />
</declare-styleable>
horizontalProgressColor:绘制的正确率的颜色
horizontalUnProgressColor:未绘制的颜色,如上方进度条的灰色部分颜色
horizontalProgressWidth:绘制的长度等
horizontalProgressHeight:绘制的部分高度
horizontalBorderWidth:进度条边框宽度
注:这些都是默认参数,颜色或者高度、边框宽度都可以通过传值进来或者自行设置
接下来就开始正式进行自定义View了,首先创建一个类,名为:HorizontalProgressBar,继承自ProgressBar,继承之后,需重写几个构造方法,一般为默认三个:
public HorizontalProgressBar(Context context) {
super(context);
}
public HorizontalProgressBar(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public HorizontalProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
这里我们只需要用到两个参数的构造方法,在其中调用init(context,attrs)方法
在init方法中,我们开始进行参数初始化
int mBoardWidth; // 进度条圆弧宽度
final int DEFAULT_UN_PROGRESS_COLOR = 0xFFE3EDF3; // 剩下的部分颜色
final int DEFAULT_PROGRESS_GOOD_COLOR = 0xFF00CC56; // 绘制部分的颜色
final TypedArray typedArray = context.obtainStyledAttributes(attrs,
R.styleable.HorizontalProgressBar); // 获取自定义的属性
mBoardWidth = typedArray.getDimensionPixelOffset(R.styleable.HorizontalProgressBar_horizontalBorderWidth,
DEFAULT_BOARD_WIDTH); // 获取自定义的圆弧边框
mProHeight = typedArray.getDimensionPixelOffset(R.styleable.HorizontalProgressBar_horizontalProgressHeight,
DEFAULT_PROGRESS_HEIGHT); // 获取自定义的进度条的高度
mProColor = typedArray.getColor(R.styleable.HorizontalProgressBar_horizontalProgressColor,
DEFAULT_PROGRESS_GOOD_COLOR); // 获取自定义的进度条的默认颜色
mUnProColor = typedArray.getColor(R.styleable.HorizontalProgressBar_horizontalUnProgressColor,
DEFAULT_UN_PROGRESS_COLOR); // 获取剩余部分的进度条默认颜色
上面的参数定义好理解有注释在,但是,这里要着重了解一下
final TypedArray typedArray = context.obtainStyledAttributes(attrs,
R.styleable.HorizontalProgressBar); // 获取自定义的属性
这行代码,这里的看到有一个没见过的方法,context.obtainStyledAttributes方法,我们点进去看,如下
public TypedArray obtainStyledAttributes(AttributeSet set,
@StyleableRes int[] attrs, @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
return mThemeImpl.obtainStyledAttributes(this, set, attrs, defStyleAttr, defStyleRes);
}
这里的第二个参数即是@StyleableRes int[] attrs
,这儿怎么理解,@StyleableRes
这个注解的意思即在R文件中找到对应的styleable资源类型,这儿可能会有一点疑问,它是怎么找到我们所需要的属性的呢?这个问题我用几行代码示例来表示:
<declare-styleable name="ArcProgressBar">
<attr name="arcBorderWidth" format="dimension" />
<attr name="arcUnProgressColor" format="color" />
<attr name="arcProgressColor" format="color" />
<attr name="arcRadius" format="dimension" />
<attr name="arcDegree" format="integer" />
<attr name="arcStartAngleBase" format="integer" />
<attr name="arcCapRound" format="boolean" />
</declare-styleable>
<!-- 同步练习作答问题的正确率横向进度条参数 -->
<declare-styleable name="HorizontalProgressBar">
<attr name="horizontalProgressColor" format="color" />
<attr name="horizontalUnProgressColor" format="color" />
<attr name="horizontalProgressWidth" format="dimension" />
<attr name="horizontalProgressHeight" format="dimension" />
<attr name="horizontalBorderWidth" format="dimension" />
</declare-styleable>
如上,在attrs.xml文件中,我定义了两个自定义View的资源文件,一个是name=“ArcProgressBar” 另一个是name=“HorizontalProgressBar”,那这儿就很明显了,在context.obtainStyledAttributes
方法中,第二个参数是通过在attrs.xml文件中通过name来找对应的资源的,而且注意它的返回类型,是一个数组@StyleableRes int[] attrs
,数组中的值即我们定义的绘制长度,绘制颜色以及绘制线条宽度等等,现在我们只需了解返回类型为数组就行了。
好了,继续往下,刚刚的代码继续往下读,也很简单,即把定义的各个变量进行赋值即可。赋值完是不是就可以开始画矩形了?NO,有件事儿还忘了,我们还需要对画笔进行初始化,即
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
这儿需要注意第二个参数Paint.Style.FILL_AND_STROKE
这是啥意思,这就相当于你给这个自定义的水平条形进度条设置的绘画格式,
Paint.Style.FILL:填充内部
Paint.Style.FILL_AND_STROKE :填充内部和描边
Paint.Style.STROKE :描边
这个好理解,填充内部的画,整个画的进度条就是实心的了,所以一般我们画那种中间有内容的进度条时,一般都是STROKE
参数,这个自行测试,不多讲。(这步如果忘了设置Paint的绘画格式的画,会报NPE的,,空指针)
继续往下,现在就来到了自定义View的最重要的一步了,即onMeasure()和onDraw()方法,首先,onMeasure方法,顾名思义,起到的作用即是测量作用,用来测量整个屏幕的宽度,这儿需要注意几个参数
wrap_content --- MeasureSpec.AT_MOST
match_parent --- MeasureSpec.EXACTLY
具体值 --- MeasureSpec.UNSPECIFIED
当我们在xml文件中设置了本个自定义view的长度是wrap_content、match_parent或者是具体的xxxdp的时候,对应的onMeasure的模式为以上三种,不过以上三种模式是否设置是可选值,可以不设置,这儿我并没有画时间了解,以后用到再了解
// 测量函数,用来测量整个自定义的View的宽高
@Override
protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthVal = MeasureSpec.getSize(widthMeasureSpec);
int heightVal = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(widthVal, heightVal);
}
尽管不用设置测量模式,但是要注意的是:
setMeasuredDimension(widthVal, heightVal);
这儿要注意,因为后面我们在绘制的时候,需要取高度和宽度,所以必须要这行代码,但是有人说这行可以不要吗?行,当然可以不要,你也可以通过super.onMeasure(widthMeasureSpec,heightMeasureSpec)
调用父类的onMeasure方法来达到目的,但是在ProgressBar的源码中
@Override
protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
/******** 上面有很多,咱们不看
setMeasuredDimension(measuredWidth, measuredHeight);
}
那么这就一目了然了,父类帮我们调用了setMeasureDimension()
方法,不过一般还是写上用来提醒自己注意这些细节也是好的。
接下来就开始了我们的onDraw()方法了,首先进行画布状态保存:
canvas.save(); // 用来保存画布状态的
canvas.translate(getPaddingLeft(), getHeight() / 2); //移动画布到 最左边 正中间
接下来就是获取屏幕宽度了,在onMeasure方法中,我们setMeasureDimension过了,所以这儿就能直接取到对应屏幕宽度值,mProWidth = getMeasuredWidth(); // 获取屏幕长度
,还记得我们的需求吗?要求从服务端获取到一个百分比,比如是40,那我们先拿到这个40
public void setProgress(int progress) {
if (mPaintedWidth != progress) {
mPaintedWidth = progress;
saveProgress = progress;
invalidate(); //去调用onDraw()方法
}
}
这儿要注意,获取到新的数据之后,调用invalidate()
去调用ondraw()
方法,ok,回到onDraw()
方法,拿到百分比之后,
mProWidth = getMeasuredWidth(); // 获取屏幕长度 mPaintedWidth = mPaintedWidth / 100 * mProWidth; // 将获取到的数据等比例放大到所需要绘制的长度
有注释也能看懂,接下来开始绘制了,这儿
mPaint.setColor(mUnProColor);
canvas.drawRoundRect(new RectF(left, top, mProWidth - rxRadius / 2,
mProHeight), rxRadius, ryRadius, mPaint);
// 上方的四个参数对应 左 上 右 下 即开始坐标和结束坐标,刚好给了自定义的View定了一个总体的绘制范围!
/*** 部分代码,省略,后面会贴出源码
canvas.restore(); // 这个别忘了 将画布状态更新到原点,即左上角(0,0),防止绘制多个view的时候,
上次的结束位置为下一次的开始位置,达不到需求
上面的那个好理解,设置画笔颜色而已,但是这儿有个drawRoundRect()
方法,跟进去瞧瞧,Canvas
类中
入参好理解,上面注释已经解释过了,继续跟进super.drawRoundRect
方法,进入到BaseCanvas
类中的
看了一番之后,发现还能继续跟进,最后,进入到BaseCanvas
类中的drawRoundRect
的重载方法中,
public void drawRoundRect(float left, float top, float right, float bottom, float rx, float ry,
@NonNull Paint paint) {
throwIfHasHwBitmapInSwMode(paint);
nDrawRoundRect(mNativeCanvasWrapper, left, top, right, bottom, rx, ry,
paint.getNativeInstance());
}
诶,到这儿发现,又有一个paint.getNativeInstance()
方法,抱着死磕的态度,我们继续跟进,到Paint方法中的getNativeInstance()
方法中一探究竟
public long getNativeInstance() {
long newNativeShader = mShader == null ? 0 : mShader.getNativeInstance();
if (newNativeShader != mNativeShader) {
mNativeShader = newNativeShader;
nSetShader(mNativePaint, mNativeShader);
}
long newNativeColorFilter = mColorFilter == null ? 0 : mColorFilter.getNativeInstance();
if (newNativeColorFilter != mNativeColorFilter) {
mNativeColorFilter = newNativeColorFilter;
nSetColorFilter(mNativePaint, mNativeColorFilter);
}
return mNativePaint;
}
哦,原来这儿的主要作用就是返回要给本地的paint的对象,刚刚我们在调用drawRoundRect
方法的时候传入了一个paint对象,所以这儿是用作为这个方法的入参的。那我们基本上就搞清楚了这个方法的作用了,,这个地方返回的是一个mNativePaint
参数,看名字就好理解了,一个本地的paint对象,不用过多深入。上面的drawRoundRect
方法的最后一个参数就是返回一个画笔对象,那么整个这个方法的意思就都弄清楚了,那我们也就大致弄清楚如何自定义ProgressBar了…
好了,以下是页面全部源码
package com.iflytek.icola.lib_base.views;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
import android.support.annotation.ColorInt;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.widget.ProgressBar;
import com.iflytek.icola.lib_base.R;
public class HorizontalProgressBar extends ProgressBar {
private final int DEFAULT_BOARD_WIDTH = dp2px(15);
private final int DEFAULT_PROGRESS_HEIGHT = dp2px(10);
private Paint mPaint;
private float mPaintedWidth; // 需要画的宽度(带颜色的)
private int mProHeight; // 进度条的高度
private int mProColor; // 进度条的颜色
private int mUnProColor; // 剩余部分的进度条颜色
float start; // 灰色进度条部分起始位置
private int saveProgress; // 保存progress大小
public HorizontalProgressBar(Context context) {
super(context);
}
public HorizontalProgressBar(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public HorizontalProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
/**
* 初始化参数
*/
private void init(Context context, AttributeSet attrs) {
int mBoardWidth; // 进度条圆弧宽度
final int DEFAULT_UN_PROGRESS_COLOR = 0xFFE3EDF3; // 剩下的部分颜色
final int DEFAULT_PROGRESS_GOOD_COLOR = 0xFF00CC56; // 良好 60~85
final TypedArray typedArray = context.obtainStyledAttributes(attrs,
R.styleable.HorizontalProgressBar); // 获取自定义的属性
mBoardWidth = typedArray.getDimensionPixelOffset(R.styleable.HorizontalProgressBar_horizontalBorderWidth,
DEFAULT_BOARD_WIDTH); // 获取自定义的圆弧边框
mProHeight = typedArray.getDimensionPixelOffset(R.styleable.HorizontalProgressBar_horizontalProgressHeight,
DEFAULT_PROGRESS_HEIGHT); // 获取自定义的进度条的高度
mProColor = typedArray.getColor(R.styleable.HorizontalProgressBar_horizontalProgressColor,
DEFAULT_PROGRESS_GOOD_COLOR); // 获取自定义的进度条的默认颜色
mUnProColor = typedArray.getColor(R.styleable.HorizontalProgressBar_horizontalUnProgressColor,
DEFAULT_UN_PROGRESS_COLOR); // 获取剩余部分的进度条默认颜色
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStrokeWidth(mBoardWidth);
/**
* Paint.Style.FILL:填充内部
* Paint.Style.FILL_AND_STROKE :填充内部和描边
* Paint.Style.STROKE :描边
*/
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
typedArray.recycle(); // 回收属性
}
// 测量函数,用来测量整个自定义的View的宽高
@Override
protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthVal = MeasureSpec.getSize(widthMeasureSpec);
int heightVal = MeasureSpec.getSize(heightMeasureSpec);
/**
* 这儿也可使用super.onMeasure(widthMeasureSpec,heightMeasureSpec)
*ProgressBar中的onMeasure()方法中有方法setMeasureDimension(widthMeasureSpec, heightMeasureSpec)方法
* 方便自己看,直接使用下列方式执行
* serMeasureDimension之后才能getMeasuredWidth()
*/
setMeasuredDimension(widthVal, heightVal);
}
@Override
protected synchronized void onDraw(Canvas canvas) {
int mProWidth; // 进度条的宽度(总的宽度)
canvas.save();
canvas.translate(getPaddingLeft(), getHeight() / 2); //移动画布到 最左边 正中间
mProWidth = getMeasuredWidth(); // 获取屏幕长度
mPaintedWidth = mPaintedWidth / 100 * mProWidth; // 将获取到的数据等比例放大到所需要绘制的长度
int rxRadius = 20; // x方向上的圆角半径
int ryRadius = 20;// y方向上的圆角半径
int left = rxRadius / 2; // 左边起始坐标
int top = 0; // 上边起始坐标
if (mPaintedWidth != 0) {
start = mPaintedWidth; // 计算灰色部分的起始位置
// 绘制默认的进度条
mPaint.setColor(mUnProColor);
//设置灰色条形的进度条
canvas.drawRoundRect(new RectF(left, top, mProWidth - rxRadius / 2,
mProHeight), rxRadius, ryRadius, mPaint);
// 绘制完成率
mPaint.setColor(mProColor);
// 对应四个参数 左 上 右 下
canvas.drawRoundRect(new RectF(left, top, mPaintedWidth - rxRadius / 2,
mProHeight), rxRadius, ryRadius, mPaint);
canvas.restore();
mPaintedWidth = saveProgress;
} else {
// 绘制默认的进度条
mPaint.setColor(mUnProColor);
canvas.drawRoundRect(new RectF(left, top, mProWidth - rxRadius / 2,
mProHeight), rxRadius, ryRadius, mPaint);
canvas.restore();
}
}
public void setProgress(int progress) {
if (mPaintedWidth != progress) {
mPaintedWidth = progress;
saveProgress = progress;
invalidate(); //去调用onDraw()方法
}
}
public void setProgressColor(@ColorInt int progressColor) {
if (mProColor != progressColor) {
mProColor = progressColor;
invalidate(); //去调用onDraw()方法
}
}
/**
* dp 2 px
*
* @param dpVal
*/
protected int dp2px(int dpVal) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
dpVal, getResources().getDisplayMetrics());
}
}
xml文件:
<com.iflytek.icola.lib_base.views.HorizontalProgressBar // 这儿是你得自定义View的类名
android:id="@+id/hpb_math_synchronous_exercise_class_analysis_right_rate"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginStart="@dimen/dimen_6"
android:layout_marginEnd="@dimen/dimen_5"
android:layout_toStartOf="@+id/tv_math_synchronous_exercise_class_analysis_right_rate"
android:layout_toEndOf="@+id/rl_pg_circle"
android:max="100"
android:progress="40"
app:horizontalBorderWidth="@dimen/dimen_4"
app:horizontalProgressColor="#FF00BAFF"
app:horizontalProgressHeight="@dimen/dimen_10"
app:horizontalProgressWidth="@dimen/dimen_600"
app:horizontalUnProgressColor="#FFE3EDF3" />
看着简单,但是做起来花了不少时间,慢慢来吧,这是个循序渐进的过程!对以上代码有不懂的地方,更欢迎问我,(实习生的第一次任务!) 以上!
转载请注明本片博客地址: https://blog.csdn.net/shilao9170/article/details/86681724