先明确一下需求
由于数据太多,每条数据又分属不同的数据表,如果等到查询完毕数据再统一绘制,体验很不好。从点击查询到绘制图形大约三秒左右,大部分时间都耗费在查询数据库上。所以为了减少等待时间,就采用每查询一条数据,就绘制一个图形的方式。
看了许多博客,大多是关于折线图的动态添加数据。经过摸索,实现了柱状图的动态添加数据,在此记录一下。
布局引入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的数据