先看效果图
1,首先是自定义一些属性,可自己再添加,挺基础的,上代码
<declare-styleable name="BrokenLineTrendView">
<attr name="trend_view_color_dotted_line" format="color"/>
<attr name="trend_view_color_text" format="color"/>
<attr name="trend_view_text_size" format="dimension"/>
</declare-styleable>
2,构造数据模型类,这里主要有个两个类,BrokenLineTrendData和BrokenLineDimension
BrokenLineTrendData.class
public List<String> mXLineDataList;//Xz轴的数据
public List<BrokenLineDimension> mDimensionList;//数据
public List<Double> mYLineDataList;//y轴的数据
public int mSelectColor;//X轴选中的的颜色
BrokenLineDimension .class
public class BrokenLineDimension {
public int mBrokenLineColor;//线的颜色
public int mBrokenPointColor;//点的颜色
public int mBrokenPointOutColor;//选择的点的外部颜色
public int mBrokenPointIntColor;//选择的点的内部颜色
public List<Double> mDatasList; //数据源
public String remark;//备注
}
3,初始化一些变量,
/**
* 控件的宽
*/
private float mWith;
/**
* 控件的高
*/
private float mHeight;
/**
* 折线的颜色
*/
private int mBrokenLineColor;
/**
* 虚线的颜色
*/
private int mDottedLineColor = getResources().getColor(R.color.color_dotted_line);
/**
* 文字的颜色
*/
private int mTexColor = getResources().getColor(R.color.color_text);
/**
* 折线的粗细
*/
private float mBrokenLineWith = DensityUtil.dip2px(getContext(),1.5f);
/**
* 字体大小
*/
private int mTextSize = DensityUtil.dip2px(getContext(), 12);
/**
* 最大的Y轴点
*/
private Double mMaxYPoint;
/**
* 最小的Y轴点
*/
private Double mMinYPoint;
/**
* Y轴的虚线paint
*/
private Paint mDottedPaint;
/**
* 文字的paint
*/
private Paint mTextPaint;
/**
* 折线的paint
*/
private Paint mBrokenLinePaint;
/**
* 折线点的paint
*/
private Paint mBrokenPointPaint;
private Paint mScorePaint;
/**
* 顶部的remark Paint
*/
private Paint mRemarkPaint;
/**
* 数据源
*/
private BrokenLineTrendData mBrokenLineTrendData;
/**
* X轴坐标轴的数据
*/
private List<String> mXLineDataList = new ArrayList<>();
/**
* Y轴坐标点的数据
*/
private List<BrokenLineDimension> mDataList = new ArrayList<>();
/**
* Y轴的坐标轴的数据
*/
private List<Double> mYLineDataList = new ArrayList<>();
/**
* 折线点的坐标list
*/
private List<List<Point>> mScorePointsList;
/**
* 选中中的index
*/
private int mSelectIndex = 0;
/**
* padding 值
*/
private int mLeftPadding = DensityUtil.dip2px(getContext(), 15);
private int mRightPadding = DensityUtil.dip2px(getContext(), 15);
private int mTopPadding = DensityUtil.dip2px(getContext(), 15);
private int mBottomPadding = DensityUtil.dip2px(getContext(), 15);
private int mTextBottomPadding = DensityUtil.dip2px(getContext(), 8);
/**
* Y轴区域部分的高度
*/
private float mYHeight;
/**
* Y轴区域部分的高度
*/
private OnItemClick mOnItemClick;
/**
* 记录Y轴的虚线的纵坐标的List
*/
private List<Float> dottedLineList = new ArrayList<>();
4,在构筑方法中初始化
public BrokenLineTrendView(Context context) {
this(context, null);
}
public BrokenLineTrendView(Context context, AttributeSet attrs) {
this(context, attrs, -1);
}
public BrokenLineTrendView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initConfig(context, attrs);
}
private void initConfig(Context context, AttributeSet attrs) {
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.BrokenLineTrendView);
int count = array.getIndexCount();
for (int i = 0; i < count; i++) {
int attr = array.getIndex(i);
switch (attr) {
case R.styleable.BrokenLineTrendView_trend_view_color_dotted_line:
mDottedLineColor = array.getColor(attr, mBrokenLineColor);
break;
case R.styleable.BrokenLineTrendView_trend_view_color_text:
mTexColor = array.getColor(attr, mTexColor);
break;
case R.styleable.BrokenLineTrendView_trend_view_text_size:
mTextSize = array.getDimensionPixelSize(attr, mTextSize);
break;
}
}
array.recycle();
initPaint();
initData();
}
private void initPaint() {
mDottedPaint = new Paint();
mDottedPaint.setAntiAlias(true);
mDottedPaint.setStyle(Paint.Style.STROKE);
mDottedPaint.setColor(mDottedLineColor);
mDottedPaint.setStrokeWidth(mBrokenLineWith);
//mDottedPaint.setStrokeCap(Paint.Cap.SQUARE);
mTextPaint = new Paint();
mTextPaint.setAntiAlias(true);
mTextPaint.setColor(mTexColor);
mTextPaint.setStyle(Paint.Style.FILL);
mTextPaint.setTextSize(mTextSize);
mBrokenLinePaint = new Paint();
mBrokenLinePaint.setAntiAlias(true);
mBrokenLinePaint.setStyle(Paint.Style.STROKE);
mBrokenLinePaint.setColor(Color.GREEN);
mBrokenLinePaint.setStrokeWidth(mBrokenLineWith);
mBrokenLinePaint.setStrokeCap(Paint.Cap.ROUND);
mBrokenPointPaint = new Paint();
mBrokenPointPaint.setAntiAlias(true);
mBrokenPointPaint.setStyle(Paint.Style.STROKE);
mBrokenPointPaint.setColor(Color.GREEN);
mBrokenPointPaint.setStrokeWidth(mBrokenLineWith);
mBrokenPointPaint.setStrokeCap(Paint.Cap.ROUND);
mScorePaint = new Paint();
mScorePaint = new Paint();
mScorePaint.setColor(Color.WHITE);
mScorePaint.setTextSize(DensityUtil.dip2px(getContext(), 12));
mScorePaint.setAntiAlias(true);
mRemarkPaint = new Paint();
mRemarkPaint.setAntiAlias(true);
mRemarkPaint.setStyle(Paint.Style.FILL);
}
private void initData() {
mBrokenLineTrendData = new BrokenLineTrendData();
mDataList = mBrokenLineTrendData.mDimensionList;
mXLineDataList = mBrokenLineTrendData.mXLineDataList;
mYLineDataList = mBrokenLineTrendData.mYLineDataList;
}
5,绘制X轴的数据
/**
* 绘制X轴的text
* @param canvas canvas
*/
private void drawXLineText(Canvas canvas) {
int count = mXLineDataList.size();//拿到X轴数据,进行循环,
if (count > 0) {
canvas.save();
float newWidth = mWith - (mWith * 0.1f) * 2;
float startX;
for (int i = 0; i < count; i++) {
//在循环中计算相应的坐标点
mTextPaint.setColor(mTexColor);
String text = mXLineDataList.get(i);
startX = ((newWidth * i) / (count - 1) + mWith * 0.1f);
Rect rectText = new Rect();
mTextPaint.getTextBounds(text, 0, text.length(), rectText);
float textSize = rectText.width();//文字的宽
float textHeight = rectText.height();//文字的高
if (mSelectIndex == i) {
//绘制选中后的text和框框
mTextPaint.setStyle(Paint.Style.STROKE);
int XDataSelectColor = mBrokenLineTrendData.mSelectColor;
mTextPaint.setColor(XDataSelectColor);
RectF rectF = new RectF();
rectF.left = startX - DensityUtil.dip2px(getContext(), 8);
rectF.right = startX + textSize + DensityUtil.dip2px(getContext(), 8);
rectF.bottom = mHeight - mBottomPadding;
rectF.top = rectF.bottom - mTextBottomPadding - textHeight - DensityUtil.dip2px(getContext(), 4);
canvas.drawRoundRect(rectF, 10, 10, mTextPaint);
mYHeight = rectF.top;//得到Y轴区域的高度,拿这个高度去计算Y轴需要多高
}
canvas.drawText(text, startX, mHeight - mBottomPadding - mTextBottomPadding, mTextPaint);
}
canvas.restore();
}
}
这样我们就绘制好了X轴的,如图
5,绘制顶部
/**
* 绘制顶部的remark
* @param canvas canvas
*/
private void drawRemark(Canvas canvas) {
canvas.save();
mRemarkPaint.setStrokeWidth(DensityUtil.dip2px(getContext(),2));
int size = mDataList.size();
int paddingRight = DensityUtil.dip2px(getContext(), 40);//右边Padding值
int totalPadding = paddingRight * (size - 1);//总的Padding值
int oneWidth = DensityUtil.dip2px(getContext(), 90);//一个itme的长度
int width = oneWidth * size + totalPadding;//一个item的总长度
int x = (int) (mWith - width) / 2;//确定开始是坐标位置
int y = mTopPadding;
for (int i = 0; i < size; i++) {
BrokenLineDimension dimension = mDataList.get(i);
mRemarkPaint.setColor(dimension.mBrokenLineColor);
int startX = x + (i * oneWidth) + paddingRight;//开始的x点
int endX = startX + oneWidth - paddingRight;
canvas.drawLine(startX, y, endX, y, mRemarkPaint);//绘制线条
float textWidth = mTextPaint.measureText(dimension.remark);
int offset = (int) ((oneWidth - textWidth - paddingRight) / 2);
canvas.drawText(dimension.remark, startX + offset , y * 2, mTextPaint);//绘制文字
}
canvas.restore();
}
效果如下: