线性的流程图经常在订单界面用到,类似的在行APP就用到了这种流程。最近在学习自定义控件,就写了一个,开源出来,也参考了网上的一些代码。
最终效果图:
项目地址:https://github.com/GinSmile/FlowViewDemo
可以想象的到,要实现这么一个视图,要画三个东西,分别是
- 圆圈
- 圆圈之间的线段
- 圆圈上面的字
所以,我们要做的最重要的事情就是确定圆圈的圆心的坐标。
1,首先需要新建一个继承自View的FlowView
public class FlowView extends View {
...
2,重写onMeasure
让wrap_content有效,所以重写onMeasure,设置FlowView的默认的长和宽为200,50。
@Override
protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = 200;
if (MeasureSpec.EXACTLY == MeasureSpec.getMode(widthMeasureSpec)) {
width = MeasureSpec.getSize(widthMeasureSpec);
}else{
width = Math.min(width, MeasureSpec.getSize(widthMeasureSpec));
}
int height = 50;
if (MeasureSpec.EXACTLY == MeasureSpec.getMode(heightMeasureSpec)) {
height = MeasureSpec.getSize(heightMeasureSpec);
}else{
height = Math.min(height, MeasureSpec.getSize(heightMeasureSpec));
}
setMeasuredDimension(width, height);
}
3,重写onSizeChanged
获取各种坐标。
- 圆圈的Y轴坐标mCenterY,用0.5f * getHeight(),即为整个视图竖直中心的坐标。
- 圆圈的X轴坐标,如果有两端的线段,那么第一个X轴坐标即为线段的长度。如果没有两端的线段,那么第一个X轴坐标即为第一个字符串的像素长度和圆圈半径中最大的。
另外还要注意线段的左上角的Y坐标, mCenterY - (mLineHeight / 2),要去掉线段的高度的一半。
由于执行顺序为 构造器->onMeasure()->onSizeChanged()->onDraw(),所以在onSizeChanged()里面得不到最新的onDraw()里面的结果,所以这里有一个地方要注意,就是在获取label0的字符总像素长度的时候,一定要在这里设置字体大小。
mDonePaint.setTextSize(mTextSize);
float textSize0 = mDonePaint.measureText(labels.get(0));
onSizeChanged()如下:
@Override
public void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mCenterY = 0.5f * getHeight();//画到view的中央
mLeftX = 0;
mLeftY = mCenterY - (mLineHeight / 2);//最左边的点的位置
mRightX = getWidth() - mRadius;
mRightY = (float) (0.5f * getHeight() + 0.5 * mLineHeight);//最右边的位置
//如果要画两端的线段,那么需要画的线就有 mFlowNums + 1 条
if(is_has_start_end_line){
float mLineLength = (mRightX - mLeftX) / (mFlowNums + 1);//两个圆心之间的距离
mCenterXPosition.add(mLineLength);//第一个圆心在初始位置右边mLineLength的地方
for (int i = 1; i < mFlowNums - 1; i++) {
mCenterXPosition.add(mLeftX + (i + 1) * mLineLength);
}
mCenterXPosition.add(mLeftX + mLineLength * mFlowNums);
}else{
//如果不画两端的线段,那么需要画的线就有 mFlowNums + 1 条
if(labels.size() > 0){
//为了防止显示不全,选择第一个字符串的像素长度和圆圈半径中最大的,作为第一个圆心的X坐标
//开始的点mLeftX,结束的点mRightX
mDonePaint.setTextSize(mTextSize);
float textSize0 = mDonePaint.measureText(labels.get(0));//获取label0的字符总长度,像素
Log.v("tag",""+textSize0 + labels.get(0));
mLeftX = Math.max(textSize0/2, mRadius);//最开始的x坐标是:第一个字副串的像素长度,和圆圈半径中最大的
float textSize1 = mDonePaint.measureText(labels.get(labels.size()-1));//获取label0的字符总长度,像素
mRightX = getWidth() - Math.max(textSize1/2, mRadius);//最开始的x坐标是:第一个字副串的像素长度,和圆圈半径中最大的
}
float mLineLength = (mRightX - mLeftX) / (mFlowNums - 1);///两个圆心之间的距离
mCenterXPosition.add(mLeftX);//第一个圆心的x坐标在mRadius处。
for (int i = 1; i < mFlowNums - 1; i++) {
mCenterXPosition.add(mLeftX + i * mLineLength);
}
mCenterXPosition.add(mRightX);
}
}
4,重写onDraw
在有没有两段的线段的时候,有些地方的坐标有很大的不同。
/**
* 画开始处和结束处的线段
*/
private void drawSELine(Canvas canvas){
//最左边的线段
float firstEndX = 0;
if(mCenterXPosition.size() > 0){
firstEndX = mCenterXPosition.get(0) - mRadius - mPaddingCircle;
}
canvas.drawRect(
0,
mLeftY,
firstEndX,
mRightY,
(0 < mDoneNums) ? mDonePaint : mTodoPaint);
//最右边的线段
float lastStartX = 0;
if(mCenterXPosition.size() > 0){
lastStartX = mCenterXPosition.get(mCenterXPosition.size() - 1) + mRadius + mPaddingCircle;
}
canvas.drawRect(
lastStartX,
mLeftY,
getWidth(),
mRightY,
(mDoneNums >= mFlowNums) ? mDonePaint : mTodoPaint);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mTodoPaint.setColor(mTodoPaintColor);
mTodoPaint.setAntiAlias(true);
mTodoPaint.setStyle(Paint.Style.FILL);
mTodoPaint.setStrokeWidth(3);
mTodoPaint.setTextSize(mTextSize);
mTodoPaint.setTextAlign(Paint.Align.CENTER);
mDonePaint.setColor(mDonePaintColor);
mDonePaint.setAntiAlias(true);
mDonePaint.setStyle(Paint.Style.FILL);
mDonePaint.setStrokeWidth(3);
mDonePaint.setTextSize(mTextSize);
mDonePaint.setTextAlign(Paint.Align.CENTER);
//如果要画最开始处和结束处的线段
if(is_has_start_end_line){
drawSELine(canvas);
}
//画中间的线段
for(int i = 0; i < mCenterXPosition.size() - 1; i++){
canvas.drawRect(
mCenterXPosition.get(i) + mRadius + mPaddingCircle,
mLeftY,
mCenterXPosition.get(i + 1) - mRadius - mPaddingCircle,
mRightY,
(i < mDoneNums - 1) ? mDonePaint : mTodoPaint);
}
//画圆圈
for(int i = 0; i < mCenterXPosition.size(); i++){
canvas.drawCircle(mCenterXPosition.get(i), mCenterY, mRadius,
(i < mDoneNums) ? mDonePaint : mTodoPaint);
}
//在圆圈上写label
for(int i = 0; i < mCenterXPosition.size(); i++){
canvas.drawText(
labels.get(i),
0,
labels.get(i).length(),
mCenterXPosition.get(i),
mCenterY - mRadius - mPaddingCircle - mTextPadding,
(i < mDoneNums) ? mDonePaint : mTodoPaint);
}
}