自定义View——折线图
Posted 小向往
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了自定义View——折线图相关的知识,希望对你有一定的参考价值。
概述
折线图在很多的项目中都会出现,作为数据直观的展示。在之前的一个房地产数据管理的项目中,用到了很多统计图,起初也在网上看过一些图表库,但是大部分都不能满足UI给出的样式,所有只能自己搞了,把项目中的代码抽离了一些出来,下面手写了一个折线图。
效果图!
动图
实现
功能
- 支持多种折线数据共同显示
- 每种折线和点的颜色可配置
- 折线图可使用从左到右展开动画
- 支持设置圆滑的曲线
- 显示隐藏折线上数据值
- 显示隐藏折线上数据点
自定义属性
根据自己的需要,定义一些属性,其他使用默认的也行。
<declare-styleable name="TotcyChart">
<attr name="TextSize" format="dimension"/>
<attr name="YscaleHeight" format="dimension" />
<attr name="ChartPandding" format="dimension" />
<attr name="ChartLineWidth" format="dimension" />
</declare-styleable>
获取自定义属性
构造方法中获取。
/**
* 获得所有自定义的参数的值
*/
TypedArray a = context.getTheme().obtainStyledAttributes(attrs,
R.styleable.TotcyChart, defStyleAttr, 0);
int n = a.getIndexCount();
for (int i = 0; i < n; i++)
int attr = a.getIndex(i);
if (attr == R.styleable.TotcyChart_TextSize)
axisSize = (int) a.getDimension(attr, axisSize);
else if (attr == R.styleable.TotcyChart_YscaleHeight)
yScaleHeight = (int) a.getDimension(attr, yScaleHeight);
else if (attr == R.styleable.TotcyChart_ChartPandding)
padding = a.getDimension(attr, padding);
else if (attr == R.styleable.TotcyChart_ChartLineWidth)
lineStrokeWidth = a.getDimension(attr, lineStrokeWidth);
else
a.recycle();
初始化
绘制折线图,或者是其他的统计图,都可以从以下几点入手:
1、原点坐标(x,y);
yCount是y坐标数据的个数
圆点Y轴坐标 上面padding +xTabHeight +(yCount-1个y轴间隔)
dotY = padding + xTabHeight + (yCount - 1) * yScaleHeight;
圆点X轴坐标 左边距padding +画笔绘制最大值的长度 + marginXy(距离坐标轴的距离)
dotX = padding + xYAxisPaint.measureText(maxData + "") + marginXy;
2、确定View的宽高
一般宽我们都是设置屏幕宽度,当然也可以从xml中读取固定的值;
//宽度测量
if (widthMode == View.MeasureSpec.EXACTLY)
width = widthSize;
else
width = Utils.getScreenWidth(getContext());
View的高:从上往下计算
//高度测量 上边距padding + (yCount-1个y轴间隔) + marginXy +xTabHeight + padding
chartViewHigth = (int) (dotY + 2 * marginXy + xTabHeight + padding);
3、xy轴数据间隔
y轴数据间隔可读取属性中的值,默认是32dp;
x轴数据间隔:x轴长度除以x轴数据个数
xScaleWidth = (chartViewWidth - dotX - padding) / mLineData.getLables().size();
onMeasure
宽高测量
int widthSize = View.MeasureSpec.getSize(widthMeasureSpec);
int widthMode = View.MeasureSpec.getMode(widthMeasureSpec);
int heightMode = View.MeasureSpec.getMode(heightMeasureSpec);
int heightSize = View.MeasureSpec.getSize(heightMeasureSpec);
int height;
int width;
//宽度测量
if (widthMode == View.MeasureSpec.EXACTLY)
width = widthSize;
else
width = Utils.getScreenWidth(getContext());
chartViewWidth = width;
//圆点X轴坐标 最大值的长度
dotX = padding + xYAxisPaint.measureText(maxData + "") + marginXy;
//高度测量 上面padding + (yCount-1个y轴间隔) + marginXy +xTabHeight + padding
chartViewHigth = (int) (dotY + 2 * marginXy + xTabHeight + padding);
//计算出折线点之间的间隔
if (mLineData != null)
xScaleWidth = (chartViewWidth - dotX - padding) / mLineData.getLables().size();
if (heightMode == View.MeasureSpec.EXACTLY)
height = heightSize;
else
height = chartViewHigth;
setMeasuredDimension(width, height);
绘制纵坐标和横线
/**
* 绘制纵坐标以及纵坐标横线
*/
private void drawOrdinate(Canvas canvas)
xYAxisPaint.setTextSize(axisSize);
xYAxisPaint.setTextAlign(Paint.Align.LEFT);
for (int i = 0; i < yCount; i++)
xYAxisPaint.setColor(axisColor);
//横线
canvas.drawLine(dotX, dotY - i * yScaleHeight, chartViewWidth - padding, dotY - i * yScaleHeight, xYAxisPaint);
//纵坐标文字
xYAxisPaint.setColor(textColor);
String xNum = new BigDecimal(maxData)
.divide(new BigDecimal(yCount - 1), 0, BigDecimal.ROUND_HALF_UP)
.multiply(new BigDecimal(i)).toString();
canvas.drawText(xNum, padding, dotY - i * yScaleHeight, xYAxisPaint);
xYAxisPaint.setColor(axisColor);
//竖线
canvas.drawLine(dotX, padding + xTabHeight - 15, dotX, dotY, xYAxisPaint);
绘制xtab和折线图
绘制折线图核心就是要根据数据算出每个点在图上的坐标,
-
点的x坐标是从原点dotX开始,依次增加xScaleWidth;
点的y坐标:这里就需要一个换算:
-
Y轴数据的最大值所占的高度 = (yCount - 1) * yScaleHeight)
因为画布中的圆点是在左上角,所以点的高度=(1-数据/最大值 ) * 最大高度 + 上边距
最后用canvas.drawPath()绘制路径折线;
/**
* 获得折线图点的高度
*
* @return
*/
private float getBarHeight(float amt)
float result = (new BigDecimal(1).subtract(new BigDecimal(amt).divide(new BigDecimal(maxData), 2, BigDecimal.ROUND_HALF_UP)))
.multiply(new BigDecimal((yCount - 1) * yScaleHeight)).add(new BigDecimal(padding + xTabHeight)).floatValue();
return result < dotY ? result : dotY;
/**
* 绘制xtab和折线图
*/
private void drawAbscissaAndChart(Canvas canvas)
if (mLineData == null)
return;
xYAxisPaint.setColor(textColor);
xYAxisPaint.setTextSize(axisSize);
for (int i = 0; i < mLineData.getLables().size(); i++)
String xLable = mLineData.getLables().get(i);
xYAxisPaint.setTextAlign(Paint.Align.LEFT);
//横坐标文字
canvas.save();
canvas.drawText(xLable, dotX + i * xScaleWidth, dotY + marginXy + xTabHeight, xYAxisPaint);
canvas.restore();
//多条折线
for (LineDataSet dataSet : mLineData.getDataSet())
Path path = new Path();
path.moveTo(dotX, getBarHeight(dataSet.getyVals().get(0).getVal()));
int size = dataSet.getyVals().size();
//绘制折线
for (int i = 1; i < size; i++)
Entry entry = dataSet.getyVals().get(i);
//折线 path路径的点
path.lineTo(dotX + i * xScaleWidth * percent, getBarHeight(entry.getVal()));
//平滑曲线设置
if (isSmooth)
CornerPathEffect cornerPathEffect = new CornerPathEffect(10);
chartPaint.setPathEffect(cornerPathEffect);
else
CornerPathEffect cornerPathEffect = new CornerPathEffect(0);
chartPaint.setPathEffect(cornerPathEffect);
//空心
chartPaint.setStyle(Paint.Style.STROKE);
chartPaint.setColor(dataSet.getLineColor());
canvas.drawPath(path, chartPaint);
//绘制折线的圆点和每个点的的值(不在上一个循环内写是因为这些点和值要在折线的上面显示)
if (isShowValue || (!isSmooth && isIntersection))
for (int i = 0; i < size; i++)
Entry entry = dataSet.getyVals().get(i);
//绘制折线的圆点 不能是圆滑的曲线
if (!isSmooth && isIntersection)
chartPaint.setStyle(Paint.Style.FILL);
chartPaint.setColor(dataSet.getDotColor());
canvas.drawCircle(dotX + i * xScaleWidth * percent, getBarHeight(entry.getVal()), lineStrokeWidth * 2, chartPaint);
if (isShowValue)
//绘制折线点上的值
xYAxisPaint.setTextSize(axisSize * 2 / 3);
xYAxisPaint.setTextAlign(Paint.Align.LEFT);
canvas.drawText((int) entry.getVal() + "", dotX + i * xScaleWidth * percent, getBarHeight(entry.getVal()) - marginXy, xYAxisPaint);
onDraw
@Override
protected void onDraw(Canvas canvas)
super.onDraw(canvas);
drawOrdinate(canvas);
drawAbscissaAndChart(canvas);
使用
layout
<com.totcy.tchartlibrary.charts.LineChartView
android:id="@+id/lineChart"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@android:color/white"
app:ChartLineWidth="1dp"
app:ChartPandding="5dp"
app:TextSize="10sp"
app:YscaleHeight="32dp"
/>
Activity
private void initData()
//just for test
//X轴标签
ArrayList<String> lables = new ArrayList<>();
lables.add("一月");
lables.add("二月");
lables.add("三月");
lables.add("四月");
lables.add("五月");
lables.add("六月");
//折线集合
ArrayList<LineDataSet> lineDataSets = new ArrayList<>();
//折线数据1
LineDataSet lineDataSet1 = new LineDataSet();
lineDataSet1.setDotColor(Color.RED);
lineDataSet1.setLineColor(Color.parseColor("#0696f5"));
//折线数据1 Y value
ArrayList<Entry> entries1 = new ArrayList<>();
entries1.add(new Entry(120, 0));
entries1.add(new Entry(20, 1));
entries1.add(new Entry(80, 2));
entries1.add(new Entry(37, 3));
entries1.add(new Entry(94, 4));
entries1.add(new Entry(234, 5));
lineDataSet1.setyVals(entries1);
lineDataSets.add(lineDataSet1);
//折线数据2
LineDataSet lineDataSet2 = new LineDataSet();
lineDataSet2.setDotColor(Color.RED);
lineDataSet2.setLineColor(Color.parseColor("#60b027"));
//折线数据2 Y value
ArrayList<Entry> entries2 = new ArrayList<>();
entries2.add(new Entry(50, 0));
entries2.add(new Entry(70, 1));
entries2.add(new Entry(150, 2));
entries2.add(new Entry(77, 3));
entries2.add(new Entry(407, 4));
entries2.add(new Entry(124, 5));
lineDataSet2.setyVals(entries2);
lineDataSets.add(lineDataSet2);
mLineData = new LineData(lables, lineDataSets);
public void onClick(View view)
//mLineChartView.setLineData(mLineData);
mLineChartView.setLineDataWithAnim(mLineData);
这是一个普通的自定义折线图,当然还有很多需要改进的地方,以后也会写一些条形图、以及组合使用的控件。
代码戳github。
以上是关于自定义View——折线图的主要内容,如果未能解决你的问题,请参考以下文章