1 前言
BarChart
用来显示股票成交量。由于股票交易成交量数额巨大,且在滑动查看K线图历史数据时,BarChart
的Y轴标签需要动态变化,原生的chart.setAutoScaleMinMaxEnabled(true);
只是实现了标签的数值大小适配滑动,但是下图这种更加合理的效果(股/万股/亿股的动态显示)却无法做到。
本文就从源码出发,尝试实现这种动态化Y轴标签。
2 基本思路
基于MPAndroidChart实现股票K线图之自定义HighLight样式及源码浅析这篇文章提到不同的Renderer类负责绘制整个Chart的不同部分,自然本文的重点也是在负责Y轴绘制逻辑的
YAxisRenderer
。
整体上看,绘制的过程是:
1、根据可见的直方图内的最大值和最小值,计算出要在Y轴上显示的label数组(是一个float数组);
2、确定label的x坐标 ;
3、根据计算好的位置,绘制label数组里的数字。
以上对应YAxisRenderer
类里的方法是:
-
computeAxis(float yMin, float yMax);
根据框架使用者设置的labelCount,从可见范围内的最小到最大值等差地生成一个label 数组(是一个float数组)mEntries
。 -
renderAxisLabels(Canvas c);
根据框架使用者设置的 AxisDependency(LEFT or RIGHT)
以及 YAxisLabelPosition(OUTSIDE_CHART or INSIDE_CHART)来确定label的x坐标。 -
drawYLabels(Canvas c, float fixedPosition, float[] positions, float offset);
通过canvas.drawText( String text, float x, float y, Paint paint)
进行绘制。
protected void drawYLabels(Canvas c, float fixedPosition, float[] positions, float offset) {
// draw
for (int i = 0; i < mYAxis.mEntryCount; i++) {
//调用mYAxisValueFormatter.getFormattedValue(mEntries[index], this)得到绘制的文本
String text = mYAxis.getFormattedLabel(i);
if (!mYAxis.isDrawTopYLabelEntryEnabled() && i >= mYAxis.mEntryCount - 1)
return;
c.drawText(text, fixedPosition, positions[i * 2 + 1] + offset, mAxisLabelPaint);
}
}
以上三个方法由上向下也是代码执行的顺序。而且computeAxis(float yMin, float yMax);
计算得到的float数组mEntries
,经过mYAxisValueFormatter格式化以后的文本才是被绘制的label文本。
那么理论上说,只要在mEntries
确定之后,drawYLabels()
发生之前,动态地去设置YAxisValueFormatter
就能够达到文章开头想要的效果。
3 实践
3.1 YAxisValueFormatter
已知成交量直方图Y轴标签的规则是 基于可见范围内的最大值来确定量级:
- 成交量<10000,显示 xx股;
- 成交量>10000 && 成交量<100000000,显示 xx万股;
- 成交量>100000000,显示 xx亿股。
所以YAxisValueFormatter 的实现如下:
public class CustomYAxisFormatter implements YAxisValueFormatter {
private static final int TEN_THOUSAND = 10000;
private static final int HUNDRED_MILLION = 100000000;
private String unit;
private int divisor;
public USVolumeYAxisFormatter(float maxVolume) {
this.unit = getUnit(maxVolume);
this.divisor = getDivisor(maxVolume);
}
@Override
public String getFormattedValue(float value, YAxis yAxis) {
if (value == 0) {
//坐标原点,显示单位(股/万股/亿股)
return unit;
}
return String.format("%.2f", value / divisor);
}
/** 根据最大值的大小来确定单位*/
public String getUnit(double num) {
int e = (int) Math.floor(Math.log10(num));
if (e >= 8) {
return "亿股";
} else if (e >= 4) {
return "万股";
} else {
return "股";
}
}
/** 根据最大值的大小来确定除数*/
public int getDivisor(float maxValue) {
int e = (int) Math.floor(Math.log10(maxValue));
if (e >= 8) {
return HUNDRED_MILLION;
} else if (e >= 4) {
return TEN_THOUSAND;
} else {
return 1;
}
}
}
3.2 YAxisRenderer
然后按照前面所述,在YAxisRenderer 的computeAxis()
完成label数组的计算后,根据最大值来动态设置YAxisValueFormatter
public class CustomeYAxisRenderer extends YAxisRenderer {
public CustomeYAxisRenderer(ViewPortHandler viewPortHandler, YAxis yAxis, Transformer trans) {
super(viewPortHandler, yAxis, trans);
}
@Override
public void computeAxis(float yMin, float yMax) {
super.computeAxis(yMin, yMax);
if (mYAxis.mEntryCount > 1) {
float topLabel = Math.abs(mYAxis.mEntries[mYAxis.mEntryCount - 1]);
mYAxis.setValueFormatter(new USVolumeYAxisFormatter(topLabel));
}
}
}
3.3 chart.setRendererLeftYAxis
最后,在BarChart
控件初始化的时候,将自定义的YAxisRenderer
设置上去:
CustomeYAxisRenderer yAxisRenderer = new CustomeYAxisRenderer (barChart.getViewPortHandler(), barChart.getAxisLeft(), barChart.getTransformer(YAxis.AxisDependency.LEFT));
chart.setRendererLeftYAxis(yAxisRenderer);
大功告成!
效果如上
网友评论