MPAndroidChart BarChart柱状图动态添加数据,自动向右滑动

1,824 阅读3分钟

先明确一下需求

由于数据太多,每条数据又分属不同的数据表,如果等到查询完毕数据再统一绘制,体验很不好。从点击查询到绘制图形大约三秒左右,大部分时间都耗费在查询数据库上。所以为了减少等待时间,就采用每查询一条数据,就绘制一个图形的方式。

看了许多博客,大多是关于折线图的动态添加数据。经过摸索,实现了柱状图的动态添加数据,在此记录一下。

布局引入com.github.mikephil.charting.charts.BarChart

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/cableHistoryPage2Fragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#0a000000"
    android:orientation="vertical">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="45dp"
        android:background="@color/white"
        android:orientation="horizontal">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_marginHorizontal="10dp"
            android:gravity="center"
            android:text="时间"
            android:textSize="18sp" />

        <TextView
            android:id="@+id/cableHistoryPage2TimeSelector"
            android:layout_width="180dp"
            android:layout_height="match_parent"
            android:layout_marginVertical="5dp"
            android:background="@drawable/spinner_border"
            android:gravity="center"
            android:text="时间选择"
            android:textColor="@color/zijinghong"
            android:textSize="18sp" />

        <TextView
            android:layout_width="1dp"
            android:layout_height="match_parent"
            android:layout_marginHorizontal="20dp"
            android:layout_marginVertical="5dp"
            android:background="@color/colorPrimary" />

        <ImageView
            android:id="@+id/cableHistoryPage2SearchHistory"
            android:layout_width="45dp"
            android:layout_height="45dp"
            android:background="@drawable/search"
            android:backgroundTint="@color/colorPrimary" />

        <TextView
            android:layout_width="match_parent"
            android:text="同一时刻温度对比图"
            android:gravity="center"
            android:textSize="18sp"
            android:textColor="@color/colorPrimaryDark"
            android:layout_height="match_parent"/>
    </LinearLayout>

    <TextView
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:background="@color/colorPrimaryDark" />

    <com.github.mikephil.charting.charts.BarChart
        android:id="@+id/cableDataHistoryBarChart"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/white" />
</LinearLayout>

初始化柱状图

    private fun initBarChart() {
        cableDataHistoryBarChart.apply {
            setDrawBorders(true) //显示边界
            setDrawBarShadow(false) //设置每个直方图阴影为false
            setDrawValueAboveBar(true) //这里设置为true每一个直方图的值就会显示在直方图的顶部
            description.isEnabled = false //设置描述文字不显示,默认显示
            setDrawGridBackground(false) //设置不显示网格
	   //setBackgroundColor(Color.parseColor("#F3F3F3")) //设置图表的背景颜色
            legend.isEnabled = false //设置不显示比例图
            setScaleEnabled(true) //设置是否可以缩放
	    //x轴设置
            xAxis.apply {
                position = XAxis.XAxisPosition.BOTTOM//X轴的位置 默认为上面
                setDrawGridLines(false);  //是否绘制X轴上的网格线(背景里面的竖线)
		//axisRight.isEnabled = false//隐藏右侧Y轴   默认是左右两侧都有Y轴
                granularity = 1f
                labelCount = 100
                /*valueFormatter = object : ValueFormatter() {
                    override fun getFormattedValue(value: Float): String {
                      //TODO 自定义X轴label格式
                    }
                }*/
            }
            //保证Y轴从0开始,不然会上移一点
            axisLeft.axisMinimum = 0f
            axisRight.axisMinimum = 0f
        }
    }

查询按钮添加监听

  override fun onClick(view: View?) {
        when (view) {
            cableHistoryPage2SearchHistory -> if (time != null) {
                if (job != null && !job!!.isCompleted) {
                    return
                }
                if (cableDataHistoryBarChart.barData != null) {
                    cableDataHistoryBarChart.barData.dataSets[0].clear()
                    cableDataHistoryBarChart.notifyDataSetChanged()
                }
                //查询数据库
                loadDataFromDB()
            } else {
                snackBarShowShort(mContext, cableHistoryPage2Fragment, "请先选择查询参数!")
            }
        }
    }

查询数据库并绘制柱状图

    //定义一个全局协程
    private val coroutineScope = CoroutineScope(Dispatchers.IO)
    private var job: Job? = null
    private fun loadDataFromDB() {
        if (time == null) return
        job = coroutineScope.launch {
            repeat(BaseApplication.cableSP.getInt(cableDetPointNumKey)) {
            	//异步查询数据库,每查询到一条数据,就添加到图表进行绘制
                val asyncTask = async {
                    return@async queryHistoryRecord(it + 1, time!!)
                }
                val temp = if (asyncTask.await().isNullOrEmpty()) 0f else NumberFormatTools.string2Float(
                        asyncTask.await()[0].temp
                 )
                addEntry(BarEntry(it + 1f, temp))
            }
        }
    }
    
    //这里要进行图像绘制,所以要切回UI线程,否则会报错
    private suspend fun addEntry(entry: BarEntry) = withContext(Dispatchers.Main) {
    	//第一次查询要添加一个空的BarDataSet
        if (cableDataHistoryBarChart.barData == null) {
            cableDataHistoryBarChart.data =
                BarData(BarDataSet(mutableListOf<BarEntry>(), "测温点").apply {
                    // 柱子的颜色
                    color = ContextCompat.getColor(mContext, R.color.zijinghong)
                    // 设置点击某个柱子时,柱子的颜色
                    highLightColor = ContextCompat.getColor(mContext, R.color.xiancaizi)
		    //barDataSet.setHighlightEnabled(false);//选中柱子是否高亮显示  默认为true
                })
            cableDataHistoryBarChart.invalidate()
        }
        cableDataHistoryBarChart.apply {
            barData.addEntry(entry, 0)
            //通知数据已经改变
	    //lineData.notifyDataChanged()
            notifyDataSetChanged()
            //设置在图表中显示的最大X轴数量
            setVisibleXRangeMaximum(30f)
            //当图表中显示的X轴数量超过30时,就开始向右移动
            // moveViewToX(barData.entryCount.toFloat() - 30)
            //这里用29是因为30的话,最后一条柱子只显示了一半
            moveViewToX(barData.entryCount.toFloat() - 29)
        }
    }

小结

在摸索过程中遇到的问题

  • 第二次点击查询时,数组越界

      root cause:大概也许是线程切换造成的,具体原因不明
    
  • java.util.ConcurrentModificationException

       root cause:添加BarEntry时,未切换到同一线程,即主线程
    
  • 第二次点击查询时,抛出Exception:Cannot add Entry because dataSetIndex too high or too low.

      root cause:添加BarEntry时,未清除上一次dataSets的数据