前言
实习期间被分配到的第一个任务,完成大概如图这样一个界面。乍一看,整个界面的布局还是十分清晰的,即使是新手也能轻易完成。唯一的难题应该就是这个红色的进度条了,我一开始考虑使用TextView的drawableLeft
来实现,但又感觉不如自定义控件来得灵活,遂决定使用自定义控件的方式实现。然而,我高估了自己的水平😅,过程中遇到了不少坑,花了几天才误打误撞地完成这个“简易”进度条,也正因如此,才有了这篇文章来记录一下思考过程、遇到的问题以及解决方案。
设计分析
实现这样一个控件,要考虑的方面有:绘制(Draw)、测量(Measure)以及属性(Attribute)。
- 绘制。显而易见,绘制一条直线和几个圆形即可。
- 测量。我选择以圆形的直径为控件的宽(width),父容器的高度为控件的高(height)。
- 属性。比较自由,如直线的粗细、圆形的半径、圆环的位置等。为简便起见,只考虑几个比较关键的属性。
实现
根据上述分析,代码的逻辑也基本理清了,实现起来应该是水到渠成。
创建类
新建MyProgressView类,继承自View类,并实现相关构造方法,使代码入口一致。
public class MyProgressView extends View {
private static final String TAG = "MyProgressView";
public MyProgressView(Context context) {
this(context, null);
}
public MyProgressView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public MyProgressView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
设置属性
为方便使用,部分属性希望能在布局文件中直接修改,将其添加至attrs.xml文件中,并在控件的构造方法中完成初始化。另一部分属性,可能不方便于布局文件中修改或其他原因,为其设置get/set方法。
<declare-styleable name="MyProgressView">
<attr name="circleRadius" format="dimension"/>
<attr name="lineWidth" format="dimension"/>
<attr name="circlePosition1" format="float"/>
<attr name="circlePosition2" format="float"/>
<attr name="circlePosition3" format="float"/>
</declare-styleable>
public class MyProgressView extends View {
private static final String TAG = "MyProgressView";
//控件的高度
private int mHeight;
//绘制的起始点
private float mStartY = DEFAULT_START_Y;
public static final float DEFAULT_START_Y = 0;
//实线的宽度
private float mLineWidth;
//圆环的半径
private float mRadius;
//圆环的位置(圆心)
private float[] mCirclePositions = new float[3];
//当前进程
private int mProgress = -1;
public MyProgressView(Context context) {
this(context, null);
}
public MyProgressView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public MyProgressView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
//初始化属性
initAttrs(context, attrs);
}
private void initAttrs(Context context, AttributeSet attrs) {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MyProgressView);
//圆环的半径
int defaultRadius = SizeUtils.dip2px(5);
mRadius = a.getDimension(R.styleable.MyProgressView_circleRadius, defaultRadius);
//实线的宽度
int defaultWidth = SizeUtils.dip2px(2);
mLineWidth = a.getDimension(R.styleable.MyProgressView_lineWidth, defaultWidth);
/*
* 三个圆环的位置(y轴)
* 推荐使用setCirclePositions方法设置
*/
mCirclePositions[0] = a.getFloat(R.styleable.MyProgressView_circlePosition1, -1);
mCirclePositions[1] = a.getFloat(R.styleable.MyProgressView_circlePosition2, -1);
mCirclePositions[2] = a.getFloat(R.styleable.MyProgressView_circlePosition3, -1);
//回收
a.recycle();
}
/*
* 省略部分属性的get/set方法
*/
}
绘制
绘制直线和圆形所需要的的相关属性已经定义好了,除此之外还需要实例化对应的画笔。
private void initPaints() {
//初始化实线画笔
mLinePaint = new Paint();
mLinePaint.setColor(Color.parseColor("#FF0000"));
mLinePaint.setStrokeWidth(mLineWidth);
//初始化圆形画笔
innerCirclePaint = new Paint();
outerCirclePaint = new Paint();
innerCirclePaint.setColor(Color.parseColor("#FFFFFF"));
outerCirclePaint.setColor(Color.parseColor("#D0021B"));
}
紧接着就是绘制过程。圆环利用两个重合的大小圆形实现,根据mProgress的值决定是否只绘制一个圆形,以表示当前流程。
@Override
protected void onDraw(Canvas canvas) {
Log.i(TAG,"-- onDraw --");
super.onDraw(canvas);
//绘制实线
canvas.drawLine(mRadius, mStartY, mRadius, mHeight, mLinePaint);
//绘制圆环
if (mCirclePositions != null && mCirclePositions.length > 0) {
for (int i = 1; i <= 3; i ++) {
float f = mCirclePositions[i - 1];
if (f == -1)