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
<?xmlversion="1.0" encoding="utf-8"?>
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
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">
<com.github.mikephil.charting.charts.LineChart
android:id="@+id/chart1"
android:layout_width="match_parent"
android:layout_height="200dp"
android:layout_margin="10dp"
android:background="@drawable/background"/>
<com.github.mikephil.charting.charts.LineChart
android:id="@+id/chart2"
android:layout_width="match_parent"
android:layout_height="200dp"
android:layout_margin="10dp"
android:background="@drawable/background"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp">
<Button
android:id="@+id/addOne"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:text="添加1个"/>
<Button
android:id="@+id/addAll"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:text="自动"/>
</LinearLayout>
</LinearLayout>
样式-background.XML
<?xmlversion="1.0" encoding="utf-8"?>
<shapexmlns:android="http://schemas.android.com/apk/res/android">
<cornersandroid:radius="10dp"/>
<paddingandroid:bottom="10dp"
android:top="10dp"
android:left="10dp"
android:right="10dp"/>
<solidandroid:color="@color/teal_200"/>
</shape>
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<String>dateTime=newArrayList<>();
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博客