一、错误记录
当前MPAndroidChart图表的版本信息如下:
//https://github.com/PhilJay/MPAndroidChart
implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0'
具体报错信息如下:
java.lang.NegativeArraySizeException: -2
at com.github.mikephil.charting.utils.Transformer.generateTransformedValuesLine(Transformer.java:178)
at com.github.mikephil.charting.renderer.LineChartRenderer.drawValues(LineChartRenderer.java:549)
at com.github.mikephil.charting.charts.BarLineChartBase.onDraw(BarLineChartBase.java:278)
二、问题分析
定位到Transformer处的源码如下:
/**
* Transforms an List of Entry into a float array containing the x and
* y values transformed with all matrices for the LINECHART.
*
* @param data
* @return
*/
public float[] generateTransformedValuesLine(ILineDataSet data,
float phaseX, float phaseY,
int min, int max) {
final int count = ((int) ((max - min) * phaseX) + 1) * 2;
if (valuePointsForGenerateTransformedValuesLine.length != count) {
valuePointsForGenerateTransformedValuesLine = new float[count];
}
float[] valuePoints = valuePointsForGenerateTransformedValuesLine;
for (int j = 0; j < count; j += 2) {
Entry e = data.getEntryForIndex(j / 2 + min);
if (e != null) {
valuePoints[j] = e.getX();
valuePoints[j + 1] = e.getY() * phaseY;
} else {
valuePoints[j] = 0;
valuePoints[j + 1] = 0;
}
}
getValueToPixelMatrix().mapPoints(valuePoints);
return valuePoints;
}
报错位置是
valuePointsForGenerateTransformedValuesLine = new float[count];
这一行,很明显问题出现在count上,
报错的问题是NegativeArraySizeException: -2,数值大小为负数的异常,
定位到报错的位置,下面就继续分析,
count是通过final int count = ((int) ((max - min) * phaseX) + 1) * 2;赋值的,phaseX图表数据执行动画的animator对象的x相位值,那问题很有可能就出在(max - min)上,可能是max小于min的值,相减得到负数导致的,
继续往前查找调用上面方法的位置
@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) || dataSet.getEntryCount() < 1)
continue;
// apply the text-styling defined by the DataSet
applyValueTextStyle(dataSet);
Transformer trans = mChart.getTransformer(dataSet.getAxisDependency());
// make sure the values do not interfear with the circles
int valOffset = (int) (dataSet.getCircleRadius() * 1.75f);
if (!dataSet.isDrawCirclesEnabled())
valOffset = valOffset / 2;
mXBounds.set(mChart, dataSet);
float[] positions = trans.generateTransformedValuesLine(dataSet, mAnimator.getPhaseX(), mAnimator
.getPhaseY(), mXBounds.min, mXBounds.max);
ValueFormatter formatter = dataSet.getValueFormatter();
MPPointF iconsOffset = MPPointF.getInstance(dataSet.getIconsOffset());
iconsOffset.x = Utils.convertDpToPixel(iconsOffset.x);
iconsOffset.y = Utils.convertDpToPixel(iconsOffset.y);
for (int j = 0; j < positions.length; j += 2) {
float x = positions[j];
float y = positions[j + 1];
if (!mViewPortHandler.isInBoundsRight(x))
break;
if (!mViewPortHandler.isInBoundsLeft(x) || !mViewPortHandler.isInBoundsY(y))
continue;
Entry entry = dataSet.getEntryForIndex(j / 2 + mXBounds.min);
if (dataSet.isDrawValuesEnabled()) {
drawValue(c, formatter.getPointLabel(entry), x, y - valOffset, dataSet.getValueTextColor(j / 2));
}
if (entry.getIcon() != null && dataSet.isDrawIconsEnabled()) {
Drawable icon = entry.getIcon();
Utils.drawImage(
c,
icon,
(int)(x + iconsOffset.x),
(int)(y + iconsOffset.y),
icon.getIntrinsicWidth(),
icon.getIntrinsicHeight());
}
}
MPPointF.recycleInstance(iconsOffset);
}
}
}
mXBounds.min, mXBounds.max获取到的max和min值
mXBounds.set(mChart, dataSet);通过数据源进行赋值的,具体赋值的代码如下:
/**
* Calculates the minimum and maximum x values as well as the range between them.
*
* @param chart
* @param dataSet
*/
public void set(BarLineScatterCandleBubbleDataProvider chart, IBarLineScatterCandleBubbleDataSet dataSet) {
float phaseX = Math.max(0.f, Math.min(1.f, mAnimator.getPhaseX()));
float low = chart.getLowestVisibleX();
float high = chart.getHighestVisibleX();
Entry entryFrom = dataSet.getEntryForXValue(low, Float.NaN, DataSet.Rounding.DOWN);
Entry entryTo = dataSet.getEntryForXValue(high, Float.NaN, DataSet.Rounding.UP);
min = entryFrom == null ? 0 : dataSet.getEntryIndex(entryFrom);
max = entryTo == null ? 0 : dataSet.getEntryIndex(entryTo);
range = (int) ((max - min) * phaseX);
}
min是通过可见范围x的最小值,然后根据这个位置获取数据源对应的点,正常情况下应该是在数组的起始位置
max是通过可见范围x的最大值,然后根据这个位置获取数据源对应的点,正常情况下应该是数组的结束位置
正常情况下max大于min,但是出现NegativeArraySizeException这个错误,应该是max小于了min,min获取的数组中后面的位置,max获取到的是数组中前面的点,意味着添加的数据可能是乱序的,并不是按照x的位置一个一个的添加的,这时候要检查下数据源是否有问题了,排查发下确实存在这样的问题,然后添加判断,根据X轴顺序添加数据点就可以了
三、解决办法
具体解决办法需要打印出x对应的值,看下x值的顺序是否是乱序或者倒序的,根据x的值具体调整
简单的示例如下:
ArrayList<ILineDataSet> dataSets = new ArrayList<>();
ArrayList<Entry> entries1 = new ArrayList<>();
LineDataSet lineDataSet1 = new LineDataSet(entries1,"数量");
dataSets.add(lineDataSet1);
entries1.add(new Entry(6f,116f);
entries1.add(new Entry(5f,118f);
entries1.add(new Entry(4f,116f);
entries1.add(new Entry(3f,112f);
entries1.add(new Entry(2f,116f);
entries1.add(new Entry(1f,110f);
修改为:
ArrayList<ILineDataSet> dataSets = new ArrayList<>();
ArrayList<Entry> entries1 = new ArrayList<>();
LineDataSet lineDataSet1 = new LineDataSet(entries1,"数量");
dataSets.add(lineDataSet1);
entries1.add(new Entry(1f,116f);
entries1.add(new Entry(2f,116f);
entries1.add(new Entry(3f,116f);
entries1.add(new Entry(4f,116f);
entries1.add(new Entry(5f,116f);
entries1.add(new Entry(6f,116f);