一、首先描述控件大致的使用流程:
1.构建EntryList
一个List集合包含了一条曲线上的所有数据,EntryBean包含X,Y坐标以及一个自定义的数据提供者对象
val dryGoalEntryList = ArrayList<Entry>()
dryGoalEntryList.add(Entry(x1,y1, TextData("干球目标温度:$data.value")))
dryGoalEntryList.add(Entry(x2,y2, TextData("干球目标温度:$data.value")))
dryGoalEntryList.add(Entry(x3,y3, TextData("干球目标温度:$data.value")))
2.构建LineDataSet
LineDataSet即该曲线的代表,构造参数传入EntryList和label曲线标签.然后设置曲线属性,如:曲线颜色、是否节点绘制圈、valueFormatter、是否绘制value等·
val dryLineDataSet = LineDataSet(dryGoalEntryList, "干球设定℃")
dryLineDataSet.color = ContextCompat.getColor(appContent, R.color.colorChartRed)
dryLineDataSet.setDrawCircles(false)
dryLineDataSet.valueFormatter = TempValueFormatter()
dryLineDataSet.setDrawValues(true)
3.构建LineData
LineData是chart图表的数据集类,只需要将一条条曲线添加进去就可以
LineData.addDataSet(dryLineDataSet)
4.构建图表类LineChart
LineChart设置XY轴属性,设置缩放属性等,最后传入LineData图表数据集,调用invalidate就可以正常显示了。
val xAxis = contentView.lineChart.xAxis
val axisLeft = contentView.lineChart.axisLeft
val axisRight = contentView.lineChart.axisRight
xAxis.isEnabled = true //去掉x轴
xAxis.position = XAxis.XAxisPosition.BOTTOM
xAxis.setDrawGridLines(false)
xAxis.textColor = android.R.color.transparent
axisLeft.isEnabled = true //去掉左y轴
axisLeft.setDrawGridLines(false)
axisRight.isEnabled = false //去掉右y轴
....
contentView.lineChart.data = lineData
contentView.lineChart.invalidate()
有话说:
图表的使用流程其实是比较简单的,假如我们自定义一个图表,简单来说就是XY轴的计算,曲线数据节点间的绘制。如果难一点就是缩放处理以及事件的添加再后面的结构设计代码封装处理。
二、代码结构分析
我们采用自底而上的分析方法往上走
1.图表类LineChart,ScatterChart,BubbleChart等
public class MLineChart extends BarLineChartBase<LineData> implements LineDataProvider {
public MLineChart(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
protected void init() {
super.init();
mRenderer = new MLineChartRenderer(this, mAnimator, mViewPortHandler);
}
@Override
public LineData getLineData() {
return mData;
}
@Override
protected void onDetachedFromWindow() {
// releases the bitmap in the renderer to avoid oom error
if (mRenderer != null && mRenderer instanceof LineChartRenderer) {
((LineChartRenderer) mRenderer).releaseBitmap();
}
super.onDetachedFromWindow();
}
}
观察LineChart,包含构造方法、初始化、LineDataGet方法、detached回调方法。如此简单的结构注定它只是一个实现类,因此查找父类BarLineChartBase。
2.BarLineChartBase类分析
/**
* Base-class of LineChart, BarChart, ScatterChart and CandleStickChart.
*
* @author Philipp Jahoda
*/
@SuppressLint("RtlHardcoded")
public abstract class BarLineChartBase<T extends BarLineScatterCandleBubbleData<? extends
IBarLineScatterCandleBubbleDataSet<? extends Entry>>>
extends Chart<T> implements BarLineScatterCandleBubbleDataProvider {
作为一个程序员 很有必要看注释!base-class~简单明了,就是LineChart等实现类的抽象基类。
多的不看,自定义View我只看onDraw方法!!
//XY轴
mXAxisRenderer.renderAxisLine(canvas);
mAxisRendererLeft.renderAxisLine(canvas);
mAxisRendererRight.renderAxisLine(canvas);
mXAxisRenderer.renderGridLines(canvas);
mAxisRendererLeft.renderGridLines(canvas);
mAxisRendererRight.renderGridLines(canvas);
mRenderer.drawData(canvas); //绘制曲线
mRenderer.drawValues(canvas);//绘制曲线节点数据
满足基本需求XY轴以及曲线和折点数据绘制的就是这些代码了。可以直观的看到绘制工作都交给Renderer类去完成了。实际上,BarLineChartBase在意义上大致类似于LineChart,完成了一些差异曲线的工作,基本的图表功能还是包含在Chart类中。
3.Chart类分析
/**
* Baseclass of all Chart-Views.
* @author Philipp Jahoda
*/
@SuppressLint("NewApi")
public abstract class Chart<T extends ChartData<? extends IDataSet<? extends Entry>>> extends
ViewGroup
implements ChartInterface {
继承与ViewGroup,证明已经走到头了。ChartInterface 封装了一些get方法的接口方法。图表中的数据集Data,GestureListener,Highlighter,Description,Legend,DefaultValueFormatter等常用功能都写在这里,如果要修改原有的功能可查看此部分源代码。
类继承关系如下:
关系.png
三、使用中总结
1.需求:每个物品都有自己的曲线数据集,切换物品曲线需要自动刷新
实现分析:Chart图表支持动态修改数据集,因此先动态修改DataSet数据集,然后调用notifyDataSetChanged()方法来更新视图。
Bug: 图表支持MarkView,当我们点击了折点弹出markView后再去切换数据更新视图会出现崩溃现象
分析:点击时会将位置信息保存在Highlight中,当我们重新设置数据源后就有可能出现拿不到的情况因此抛出异常。
解决办法:修改图表数据后手动调用LineChart.highlightValues(arrayOf()) 来清除原先的Highlight数据
2.需求:图表有多条曲线,固定前两条曲线显示折点数据,其他的曲线缩放合适时显示数据
实现分析:LineDataSet可以通过setDrawValues(true)来确定是否需要显示折点数据,因此每一个曲线我们都设置为true观察效果
问题:由于折点数据集太大,曲线过于密集,数据都不会显示出来
分析:我们知道曲线的绘画是交于实现类中持有的Renderer来完成的,我们看下LineChart中LineChartRenderer的绘画逻辑。
@Override
public void drawValues(Canvas c) {
if (isDrawingValuesAllowed(mChart)) {
List<ILineDataSet> dataSets = mChart.getLineData().getDataSets();
for (int i = 0; i < dataSets.size(); i++) {
ILineDataSet dataSet = dataSets.get(i);
if (!shouldDrawValues(dataSet))
continue;
........
}
}
}
protected boolean isDrawingValuesAllowed(ChartInterface chart) {
return chart.getData().getEntryCount() < chart.getMaxVisibleCount()
* mViewPortHandler.getScaleX();
}
首先通过isDrawingValuesAllowed来判断曲线是否可以显示数据文字,这个逻辑控制了所有曲线是否绘制!~~ 一旦不满足绘画要求,后面的shouldDrawValues也就是我们前面设置的setDrawValues是没有卵用的!因此我们需要修改此部分逻辑。
解决办法:将isDrawingValuesAllowed放到for循环内部,强制需要绘画value的则不用进行控制。
看源码是很有趣的,逻辑是人写的,因此要解决问题还是得从源头解决~~~
网友评论