MPAndroidChart BarChart添加柱顶图片时触发空指针异常求助
嘿,从你贴的报错栈信息来看,空指针出现在父类BarChartRenderer.drawDataSet()的第138行,对应代码是BarBuffer buffer = mBarBuffers[index];——说白了,这就是mBarBuffers数组或者对应索引的BarBuffer对象是null,核心问题是自定义Renderer的缓冲区没被正确初始化。下面给你一步步拆解排查和解决的思路:
1. 先把自定义Renderer的设置时机搞对
BarChartRenderer里的mBarBuffers是在initBuffers()方法里初始化的,而这个方法只有在数据集变化时(比如调用chart.setData())才会触发。如果你的代码是在chart.setData()之前就设置了自定义Renderer,那Renderer的initBuffers()根本不会被调用,自然mBarBuffers就是空的。
修正后的代码示例:
在你的setupBarChartWind方法里,把设置Renderer的代码挪到chart.setData(lineData)之后:
private void setupBarChartWind(Forecast forecast) { // ... 你原来的初始化entries、dataset、x轴y轴配置这些代码都保留 ... BarData lineData = new BarData(mdataSet); chart.setData(lineData); // 先把数据设置给图表 // 先加载你要显示的图片(这里一定要确保Bitmap加载成功,别传null!) Bitmap windIcon = BitmapFactory.decodeResource(getResources(), R.drawable.your_wind_icon); // 实例化自定义Renderer并设置给图表 BarChartImageRenderer customRenderer = new BarChartImageRenderer(chart, chart.getAnimator(), chart.getViewPortHandler(), windIcon); chart.setRenderer(customRenderer); // ... 后面的动画、invalidate这些代码不变 ... }
2. 修复自定义Renderer里的循环逻辑
你写的drawBarImages方法里,循环条件j < buffer.buffer.length * mAnimator.getPhaseX()有坑:buffer.buffer的长度是条目数*4(每个柱子对应4个坐标点),乘以动画相位后很容易导致索引越界,或者访问到还没初始化的缓冲区数据。咱们改成基于数据集的条目数量来循环更靠谱:
protected void drawBarImages(Canvas c, IBarDataSet dataSet, int index) { BarBuffer buffer = mBarBuffers[index]; if (buffer == null) return; // 先加个非空判断,避免踩坑 int entryCount = dataSet.getEntryCount(); // 结合动画相位,计算实际要绘制的条目数 int phaseCount = (int) Math.ceil(entryCount * mAnimator.getPhaseX()); for (int j = 0; j < phaseCount; j++) { int bufferIndex = j * 4; if (bufferIndex >= buffer.buffer.length) break; float left = buffer.buffer[bufferIndex]; float right = buffer.buffer[bufferIndex + 2]; float top = buffer.buffer[bufferIndex + 1]; float bottom = buffer.buffer[bufferIndex + 3]; float x = (left + right) / 2f; if (!mViewPortHandler.isInBoundsRight(x)) break; if (!mViewPortHandler.isInBoundsY(top) || !mViewPortHandler.isInBoundsLeft(x)) continue; // 获取当前柱子对应的条目 BarEntry entry = dataSet.getEntryForIndex(j); // 基于当前柱子的宽度缩放图片(别再用第一个柱子的宽度了,适配不同宽度的柱子) float barWidth = right - left; final Bitmap scaledBarImage = Bitmap.createScaledBitmap(imageToRender, (int)barWidth, (int)barWidth, false); int imageOffset = scaledBarImage.getWidth() / 2; // 注意:原代码的top是柱子的顶端,要把图片放在柱子顶部的话,y坐标应该是top减去图片高度,不然图片会盖住柱子顶部 drawImage(c, scaledBarImage, x - imageOffset, top - scaledBarImage.getHeight()); } }
3. 加一些必要的非空保护
在自定义Renderer的关键位置加几个非空判断,防患于未然:
- 在
drawBarImages开头先检查buffer是不是null - 在图片缩放逻辑里检查
imageToRender是不是null - 加载Bitmap的时候,可以加个判断,比如
if (windIcon == null) { Log.e("TAG", "Failed to load wind icon"); return; },避免传null给Renderer
4. 再确认父类方法的调用
你在自定义Renderer里调用了super.drawDataSet(c, dataSet, index);,这没问题,但父类方法能正常执行的前提是mBarBuffers已经初始化好了——这又回到了第一步的时机问题,只要把Renderer的设置放在setData之后,这个问题就解决了。
总结
你当前遇到的空指针,大概率是因为自定义Renderer设置时机不对,没在chart.setData()之后设置,导致Renderer的缓冲区没被初始化。按照上面的步骤调整代码后,应该就能解决这个问题啦。
内容的提问来源于stack exchange,提问作者Johan Fick

