kotlin 尝试绘制图表后程序崩溃

uinbv5nw  于 2023-10-23  发布在  Kotlin
关注(0)|答案(1)|浏览(107)

我有一个应用程序,用户可以选择日期(从/到),货币列表,然后,显示图表与一些数据。这个图表,只是堆积的条形图,从MPAndroidChart开始。最近,我实现了SwiperRefreshLayout,因此用户可以进行不止一个API调用。显示图表有两种情况:

**首先(一切正常)**当用户将选择例如2种货币时,他可以进行另一次调用,但他不能超过第一次选择的货币数量。所以在这种情况下,他可以选择一种或两种货币。不管是哪一个。
第二,程序崩溃用户将选择X数量的货币,图表将显示,刷新布局后,用户将选择X+1货币。这使得程序崩溃。

由于我已经花了几天时间处理这个问题,我试图清除数据,使图表无效等。事实证明,这个命令使程序崩溃,

mBinding.myChart.setCustom(myEntries)

我可以运行整个程序没有它,但它是必要的,因为它提供了标签的图表。
下面是一些代码:

全局变量

private var myChartData: MutableList<BarEntry> = mutableListOf()
    private var set: BarDataSet? = null

API调用

private val apiCall: Job
    get() =
        viewLifecycleOwner.lifecycleScope.launch(start = CoroutineStart.LAZY) {

            for (i in 0 until pickedCurrs.size) {
                myPickedCurrencies += "${pickedCurrs[i]}, "
            }

            mViewModel.fetchData(
                myBaseCurrency,
                myPickedCurrencies,
                fromDate,
                toDate
            )

            mViewModel.myApiData.observe(viewLifecycleOwner, Observer { status ->
                when (status) {
                    is MyWrapper.Success -> {
                        status.data?.timeSeriesRates?.entries?.forEachIndexed { index, entry ->
                            myChartData.add(
                                BarEntry(
                                    index.toFloat(),
                                    entry.value.values.map { it.toFloat() }.toFloatArray()
                                )
                            )
                        }
                        makeChart(
                            myChartData,
                            formatDate(status.data?.timeSeriesRates?.keys!!.toList()),
                            mSelectedCurrencies
                        )
                    }
                    is MyWrapper.Error -> {
                        Log.i(TAG, "onCreateView: ERROR")
                    }
                }
            })
        }

makeChart

private fun prepareChart(
        entries: MutableList<BarEntry>,
        xAxisValues: ArrayList<String>,
        pickedCurrs: MutableList<String>
    ) {
        val colorsList = mutableListOf<Int>()
        val legEn = mutableListOf<LegendEntry>()

        for (i in pickedCurrs.indices) {
            val color =
                Color.argb(
                    255,
                    Random().nextInt(256),
                    Random().nextInt(256),
                    Random().nextInt(256)
                )
            if (!colorsList.contains(color)) {
                colorsList.add(color)
            }
        }
        set = BarDataSet(myEntries, "x")
        set?.colors = colorsList
        set?.highLightAlpha = 0

        val data = BarData(set)
        mBinding.timeSeriesChart.data = data
        mBinding.timeSeriesChart.description?.isEnabled = false
        mBinding.timeSeriesChart.setVisibleXRangeMaximum(6f)
        mBinding.timeSeriesChart.barData?.setValueTextSize(12f)
        mBinding.timeSeriesChart.xAxis?.position = XAxis.XAxisPosition.BOTTOM
        mBinding.timeSeriesChart.setExtraOffsets(0f, 0f, 0f, 15f)
//
//        //xAxis
        mBinding.timeSeriesChart.xAxis?.setDrawLabels(true)
        mBinding.timeSeriesChart.xAxis?.position = XAxis.XAxisPosition.BOTTOM_INSIDE
        mBinding.timeSeriesChart.xAxis?.granularity = 1f
        mBinding.timeSeriesChart.xAxis?.textSize = 11.5f
        mBinding.timeSeriesChart.xAxis?.valueFormatter = IndexAxisValueFormatter(myXAxisValues)
        mBinding.timeSeriesChart.xAxis?.labelRotationAngle = -20f
        mBinding.timeSeriesChart.axisRight?.setDrawGridLines(false)
        mBinding.timeSeriesChart.axisLeft.isEnabled = false
        mBinding.timeSeriesChart.axisRight.isEnabled = false
//
//        //legend
        val legend = mBinding.timeSeriesChart.legend
        legend?.verticalAlignment = Legend.LegendVerticalAlignment.BOTTOM
        legend?.horizontalAlignment = Legend.LegendHorizontalAlignment.LEFT
        legend?.orientation = Legend.LegendOrientation.HORIZONTAL
        legend?.setDrawInside(false)

        for (i in pickedCurrs.indices) {
            legEn.add(
                LegendEntry(
                    pickedCurrs[i],
                    Legend.LegendForm.SQUARE,
                    15f,
                    15f,
                    null,
                    colorsList[i]
                )
            )
        }
       
        legend?.setCustom(legEn)

        set?.valueFormatter = object : ValueFormatter() {
            override fun getFormattedValue(value: Float): String {
                return String.format("%2.02f", value)
            }
        }
        mBinding.timeSeriesChart.invalidate()
        mBinding.timeSeriesChart.refreshDrawableState()
    }

编辑堆栈跟踪

Process: com.example.x, PID: 15091
    java.lang.IndexOutOfBoundsException: Index 1 out of bounds for length 1
        at jdk.internal.util.Preconditions.outOfBounds(Preconditions.java:64)
        at jdk.internal.util.Preconditions.outOfBoundsCheckIndex(Preconditions.java:70)
        at jdk.internal.util.Preconditions.checkIndex(Preconditions.java:266)
        at java.util.Objects.checkIndex(Objects.java:359)
        at java.util.ArrayList.get(ArrayList.java:434)
        at com.github.mikephil.charting.renderer.LegendRenderer.renderLegend(LegendRenderer.java:377)
        at com.github.mikephil.charting.charts.BarLineChartBase.onDraw(BarLineChartBase.java:281)
        at android.view.View.draw(View.java:23889)
        at android.view.View.updateDisplayListIfDirty(View.java:22756)
        at android.view.View.draw(View.java:23620)
        at android.view.ViewGroup.drawChild(ViewGroup.java:4556)
        at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4317)
        at androidx.constraintlayout.widget.ConstraintLayout.dispatchDraw(ConstraintLayout.java:1994)
        at android.view.View.updateDisplayListIfDirty(View.java:22747)
        at android.view.View.draw(View.java:23620)
        at android.view.ViewGroup.drawChild(ViewGroup.java:4556)
        at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4317)
        at android.view.View.draw(View.java:23892)
        at android.view.View.updateDisplayListIfDirty(View.java:22756)
        at android.view.View.draw(View.java:23620)
        at android.view.ViewGroup.drawChild(ViewGroup.java:4556)
        at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4317)
        at androidx.constraintlayout.widget.ConstraintLayout.dispatchDraw(ConstraintLayout.java:1994)
        at android.view.View.updateDisplayListIfDirty(View.java:22747)
        at android.view.View.draw(View.java:23620)
        at android.view.ViewGroup.drawChild(ViewGroup.java:4556)
        at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4317)
        at android.view.View.updateDisplayListIfDirty(View.java:22747)
        at android.view.View.draw(View.java:23620)
        at android.view.ViewGroup.drawChild(ViewGroup.java:4556)
        at androidx.recyclerview.widget.RecyclerView.drawChild(RecyclerView.java:5545)
        at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4317)
        at android.view.View.draw(View.java:23892)
        at androidx.recyclerview.widget.RecyclerView.draw(RecyclerView.java:4944)
        at android.view.View.updateDisplayListIfDirty(View.java:22756)
        at android.view.View.draw(View.java:23620)
        at android.view.ViewGroup.drawChild(ViewGroup.java:4556)
        at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4317)
        at android.view.View.updateDisplayListIfDirty(View.java:22747)
        at android.view.View.draw(View.java:23620)
        at android.view.ViewGroup.drawChild(ViewGroup.java:4556)
        at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4317)
        at android.view.View.updateDisplayListIfDirty(View.java:22747)
        at android.view.View.draw(View.java:23620)
        at android.view.ViewGroup.drawChild(ViewGroup.java:4556)
        at androidx.fragment.app.FragmentContainerView.drawChild(FragmentContainerView.kt:235)
        at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4317)
        at androidx.fragment.app.FragmentContainerView.dispatchDraw(FragmentContainerView.kt:225)
        at android.view.View.updateDisplayListIfDirty(View.java:22747)
        at android.view.View.draw(View.java:23620)
        at android.view.ViewGroup.drawChild(ViewGroup.java:4556)
        at androidx.fragment.app.FragmentContainerView.drawChild(FragmentContainerView.kt:235)
        at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4317)
        at androidx.fragment.app.FragmentContainerView.dispatchDraw(FragmentContainerView.kt:225)
        at android.view.View.updateDisplayListIfDirty(View.java:22747)
        at android.view.View.draw(View.java:23620)
        at android.view.ViewGroup.drawChild(ViewGroup.java:4556)
        at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4317)
        at android.view.View.updateDisplayListIfDirty(View.java:22747)
        at android.view.View.draw(View.java:23620)
        at android.view.ViewGroup.drawChild(ViewGroup.java:4556)
        at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4317)
hwamh0ep

hwamh0ep1#

TL;DR调用legend.setCustom(entries)后再调用chart.notifyDataSetChanged()

我认为这是MPDandroidChart中的一个bug。如果您增加自定义图例中的条目数并通知图表数据集已更改,则会发生崩溃。我做了一个简单的例子,复制这个问题来测试修复。
如果您运行这个演示问题并单击“添加条目”按钮,它将崩溃,并出现您看到的相同错误。

class MainActivity : AppCompatActivity() {

    private var entries = mutableListOf(
        BarEntry(0f, 3f),
        BarEntry(1f,2.5f),
        BarEntry(2f,4f)
    )
    private var labels = mutableListOf("Foo","Bar","Baz")

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val chart = findViewById<BarChart>(R.id.chart)
        val addEntry = findViewById<Button>(R.id.add_entry)

        addEntry.setOnClickListener {
            entries.add(BarEntry(entries.size.toFloat(), 3.2f))
            labels.add("Xtra")
            refreshChart(chart)
        }

        refreshChart(chart)
    }

    private fun refreshChart(chart: BarChart) {
        val colors = entries.map {
            Color.argb(
                255,
                Random.nextInt(256),
                Random.nextInt(256),
                Random.nextInt(256)
            )
        }
        val ds = BarDataSet(entries, "x")
        ds.colors = colors
        chart.data = BarData(ds)

        val legend = chart.legend
        legend.setDrawInside(false)
        val legendEntries = labels.zip(colors).map {lab_col ->
            LegendEntry(lab_col.first, Legend.LegendForm.SQUARE, 15f, 15f, null, lab_col.second)
        }

        legend.setCustom(legendEntries)
        chart.invalidate()
    }
}

原因是Legend对象(calculatedLabelSizes)中计算的图例文本宽度数组在渲染图例之前不会自动更新为新增加的条目数,从而导致LegendRenderer中的IndexOutOfBoundsException。修复方法是在图表上调用notifyDataSetChanged,以强制它重新测量图例,并在渲染之前调整内部数组的大小以匹配图例条目的数量,如下所示

legend.setCustom(legendEntries)

// This forces the legend to resize its size array to match the size
// of the newly set legendEntries array
chart.notifyDataSetChanged()

chart.invalidate()

这样做,演示工作正常,新的图例条目可以添加没有问题。

注意

您的if (!colorsList.contains(color)) {子句也有可能导致您的应用崩溃。如果您的随机颜色生成器遇到重复的颜色,colorsList长度将太短。这不太可能,但并非不可能。

相关问题