Android使用MPandroidChart折线双轴多线段实时更新与问题解决
记录一次在android开发使用图表框架MpandroidChart时遇到绘制双折线图后,复刻官方demo代码后报错的解决。由于不支持markdown,懒得再去改了。
环境:
版本
JavaSDK11
Gradle7.04
android studio2020.3.1
MPandroidChart3.1.0
问题详情
根据MPandroidChart的demo,我复刻了单轴的实时更新(后文会实现),并且通过独立封装后完美重现。在阅读多轴源码后发现,只需要 开启右边的Y轴,然后向图表实例中加入新的LineDataSet对象,即可创建新折线,而通过getDataSetByIndex(Index)来获取多线段中指定的一条来进行操作,每条互不影响,只是在同一个View里罢了。因为要实时更新,就需要想view提交更新的数据,并且为了有更好的效果,我会在数据更新以后将视图移动到最新的一条。于是问题就出现了,在调用moveViewToX()后就会卡死整个主程序,哪怕我是用的子线程。此时问题就很明显了,就是moveViewToX()错误调用的问题,但我还傻乎乎的没注意,看了报错说超过最大数什么的。。。。好像也告诉我了出什么问题了。。。。于是我成功的避开,然后不断阅读源码,查找资料。。。。最后,终于。。放弃了。本来就想将就一下,虽然能够更新数据,但是最新的数据需要手动划过去,并且还要刷新整个view,就导致下一次数据刷新的时候,就要重新划过去。。。
问题解决
阅读源码,可以发现,移动到最新一条数据,是通过指定X轴的具体值来进行移动的。为什么是float类型?我定义的X轴,明明是时间格式展示的呀。其实那只是改了X轴显示的标签值罢了,真正的X,Y值都是通过float来进行存储的(看源码猜的)。再看看我错误的代码。。。
chart.getData().notifyDataChanged();//提交表内数据更新
chart.notifyDataSetChanged();//提交数据更新,在View没有大的改动,只更新数据就只使用这个就可以了。
//更新视图,当然都跟新View了,数据也是会更新的,但相当于重启,就会导致上面说到的更新以后要重新再划过去最新的目录。
//chart.invalidate();
//移动到最新的数据,数据更新以后就会在最右边显示出来。
chart.moveViewToX(chart.getData().getEntryCount());
这是getEntryCount()的源码
到这里,明白原理的其实已经知道问题所在了。单折线之所以用上面的方法没有问题,是因为mValues.size()返回的数值就只有一根折线的x轴的数值。通过日志输出,是可以发现单折线有几个数据,就返回的是几。在使用双曲线后,再输出,发现竟然变成双倍了!X轴明明是第4个值,却输出了8。这才导致了报错,也是为什么报错说超过最大数的原因。既然大了两倍,那chart.moveViewToX(chart.getData().getEntryCount()/2);不就好了。。。。按道理来说是的,但是结果依然是报错的。正确的解决思路是,既然使用的是多折线, 多折线数据X轴数值都是一致的,那么直接取其中其中一个不就好了
chart.moveViewToX(((LineDataSet)chart.getData().getDataSetByIndex(0)).getEntryCount());
这样,就可以正常运行且不报错了,能够通过线程实时更新数据了,也不会更新View,只会跟新数据。好的,思路讲完。下面上测试代码。
测试代码实现
导入依赖
这个可以直接搜索GitHub的,然后下面有怎么在线导入,但有可能导入了但实际没成功导入(无法使用),就需要使用离线的jar包导入。
界面-activity_main
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".MainActivity"> android:id="@+id/chart1" android:layout_width="match_parent" android:layout_height="200dp" android:layout_margin="10dp" android:background="@drawable/background"/> android:id="@+id/chart2" android:layout_width="match_parent" android:layout_height="200dp" android:layout_margin="10dp" android:background="@drawable/background"/> android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="10dp"> 样式-background.XML android:top="10dp" android:left="10dp" android:right="10dp"/> MainActivity publicclassMainActivityextendsAppCompatActivity{ privateLineChartchart,chart1; privateCreationChartsetChart1,setChart2; privateThreadthread; @Override protectedvoidonCreate(BundlesavedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initView(); } /** * 初始化控件 */ privatevoidinitView() { chart=findViewById(R.id.chart1); chart1=findViewById(R.id.chart2); setChart1=newCreationChart(chart); setChart2=newCreationChart(chart1); setChart1.init(); setChart2.init(); //单折线添加一个随机数据 findViewById(R.id.addOne).setOnClickListener(newView.OnClickListener() { @Override publicvoidonClick(Viewview) { setChart1.AddData((float) (Math.random()*40)+30f); } }); //双折线 添加2个 findViewById(R.id.addTwo).setOnClickListener(newView.OnClickListener() { @Override publicvoidonClick(Viewview) { setChart2.AddData((float) (Math.random()*40)+30f, (float) (Math.random()*40)+30f); } }); //启动线程 findViewById(R.id.addAll).setOnClickListener(newView.OnClickListener() { @Override publicvoidonClick(Viewview) { AddDataThread(); } }); } /** * 循环添加数据 每100毫秒添加一次 */ privatevoidAddDataThread() { if(thread!=null) thread.interrupt(); thread=newThread(newRunnable() { @Override publicvoidrun() { for(inti=0;i<100;i++) { try{ runOnUiThread(newRunnable() { @Override publicvoidrun() { setChart1.AddData((float) (Math.random()*40)+30f); setChart2.AddData((float) (Math.random()*40)+30f, (float) (Math.random()*40)+30f); } }); Thread.sleep(100); }catch(InterruptedExceptione) { Log.e("绘制",e.toString()); } } } }); thread.start(); } } 封装的图表类 - CreationChart /** * 简单封装一下 */ publicclassCreationChart{ privateLineChartchart; privateList publicCreationChart(LineChartchart) { this.chart=chart; } /*** * 初始化 */ publicvoidinit() { // 开启文本描述 chart.getDescription().setEnabled(false); chart.setDragDecelerationFrictionCoef(0.9f); // 开启触摸手势 chart.setTouchEnabled(true); // 允许缩放和拖动 chart.setDragEnabled(true);//拖动 chart.setScaleEnabled(false);//缩放 chart.setDrawGridBackground(false); chart.setHighlightPerDragEnabled(true); // 如果禁用,可以分别在x轴和y轴上进行缩放 chart.setPinchZoom(true); // 设置一个替代背景 //chart.setBackgroundColor(Color.rgb(255, 255, 255)); LineDatadata=newLineData(); data.setValueTextColor(Color.WHITE); XAxisxl=chart.getXAxis(); xl.setPosition(XAxis.XAxisPosition.BOTTOM_INSIDE);//标签位置 xl.setTextColor(Color.WHITE);// x值为白色 xl.setDrawGridLines(false); xl.setLabelCount(4);//分为几个 xl.setAxisLineColor(Color.rgb(248,248,255));//x线的颜色 xl.setAvoidFirstLastClipping(true); xl.setEnabled(true); //左边的Y轴数据 YAxisleftAxis=chart.getAxisLeft(); leftAxis.setTextColor(Color.WHITE); //leftAxis.setAxisMaximum(200f); //最大条目 leftAxis.setAxisMinimum(0f);//最小条目 leftAxis.setLabelCount(6);//设置最大分为几格 leftAxis.setDrawGridLines(true); leftAxis.setAxisLineColor(Color.rgb(248,248,255)); //右边的Y轴数据 YAxisrightAxis=chart.getAxisRight(); rightAxis.setEnabled(false); } /** * 添加数据 * * @param data */ publicvoidAddData(float...data) { //获取当前时间 格式为“HH:mm:ss” SimpleDateFormatformatter=newSimpleDateFormat("HH:mm:ss"); Stringdate=formatter.format(newDate()); dateTime.add(date); LineDataSetnewDataSet,newDataSet2; chart.getXAxis().setValueFormatter(newIndexAxisValueFormatter(dateTime)); switch(data.length) { // 单折线 case1: if(chart.getData()!=null&& chart.getData().getDataSetCount()>0) { newDataSet=(LineDataSet)chart.getData().getDataSetByIndex(0); newDataSet.addEntry(newEntry(newDataSet.getEntryCount(),data[0])); }else{ newDataSet=CreateSet(LineChartType.CHART1); LineDatalineData1=newLineData(newDataSet); chart.setData(lineData1); } break; // 多折线 case2: if(chart.getData()!=null&& chart.getData().getDataSetCount()>0) { newDataSet=(LineDataSet)chart.getData().getDataSetByIndex(0); newDataSet2=(LineDataSet)chart.getData().getDataSetByIndex(1); newDataSet.addEntry(newEntry(newDataSet.getEntryCount(),data[0])); newDataSet2.addEntry(newEntry(newDataSet2.getEntryCount(),data[1])); }else{ newDataSet=CreateSet(LineChartType.CHART1); newDataSet2=CreateSet(LineChartType.CHART2); LineDatalineData1=newLineData(newDataSet,newDataSet2); chart.setData(lineData1); } break; } chart.getData().notifyDataChanged(); //提交数据更新 chart.notifyDataSetChanged(); //设置X最大可见条目 chart.setVisibleXRange(2,10); //移动到最新数据 if(chart.getData().getDataSetByIndex(0).getEntryCount()>0) chart.moveViewToX(chart.getData().getDataSetByIndex(0).getEntryCount()); } /** * 设置数据格式 * * @param type chart1 或者 chart2 * @return LineDataSet */ privateLineDataSetCreateSet(LineChartTypetype) { LineDataSetset=null; switch(type) { caseCHART1: set=newLineDataSet(null,"测试1"); set.setColor(ColorTemplate.getHoloBlue());//折线颜色 set.setFillAlpha(65);//填充透明度 set.setFillColor(ColorTemplate.getHoloBlue()); break; caseCHART2: set=newLineDataSet(null,"测试2"); set.setColor(Color.YELLOW);//折线颜色 set.setFillAlpha(65);//填充透明度 set.setFillColor(ColorTemplate.colorWithAlpha(Color.YELLOW,200)); break; } set.setAxisDependency(YAxis.AxisDependency.LEFT); set.setLineWidth(1f); set.setCircleRadius(4f); set.setHighLightColor(Color.rgb(124,117,117));//高亮颜色 set.setDrawValues(true);//绘画值 set.setDrawCircles(false);//绘画圆圈 set.setDrawFilled(true);//充满底部 set.setMode(LineDataSet.Mode.CUBIC_BEZIER);// 类型,折线还是曲线还是 平线 returnset; } /** * 枚举类型 */ privateenumLineChartType{ CHART1,CHART2 } } 效果展示 注:转载请标明出处 CSDN:Android使用MPandroidChart折线双轴多线段实时更新与问题解决_Dream's的博客-CSDN博客