Android使用MPandroidChart折线双轴多线段实时更新与问题解决加食用教程

记录一次在android开发使用图表框架MpandroidChart时遇到绘制双折线图后,复刻官方demo代码后报错的解决。

环境:

版本
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

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns: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

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <corners android:radius="10dp"/>
    <padding android:bottom="10dp"
        android:top="10dp"
        android:left="10dp"
        android:right="10dp"/>

    <solid android:color="@color/teal_200"/>
</shape>

MainActivity

public class MainActivity extends AppCompatActivity {
    private LineChart chart, chart1;
    private CreationChart setChart1, setChart2;
    private Thread thread;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
    }

    /**
     * 初始化控件
     */
    private void initView() {
        chart = findViewById(R.id.chart1);
        chart1 = findViewById(R.id.chart2);
        setChart1 = new CreationChart(chart);
        setChart2 = new CreationChart(chart1);
        setChart1.init();
        setChart2.init();
        //单折线添加一个随机数据
        findViewById(R.id.addOne).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                setChart1.AddData((float) (Math.random() * 40) + 30f);
            }
        });

        //双折线 添加2个
        findViewById(R.id.addTwo).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                setChart2.AddData((float) (Math.random() * 40) + 30f, (float) (Math.random() * 40) + 30f);
            }
        });

        //启动线程
        findViewById(R.id.addAll).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                AddDataThread();
            }
        });
    }

    /**
     * 循环添加数据   每100毫秒添加一次
     */
    private void AddDataThread() {
        if (thread != null)
            thread.interrupt();

        thread = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    try {
                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                setChart1.AddData((float) (Math.random() * 40) + 30f);
                                setChart2.AddData((float) (Math.random() * 40) + 30f, (float) (Math.random() * 40) + 30f);
                            }
                        });
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        Log.e("绘制", e.toString());
                    }
                }
            }
        });
        thread.start();
    }

}

封装的图表类 - CreationChart

/**
 * 简单封装一下
 */
public class CreationChart {
    private LineChart chart;
    private List<String> dateTime = new ArrayList<>();

    public CreationChart(LineChart chart) {
        this.chart = chart;
    }

    /***
     * 初始化
     */
    public void init() {
        // 开启文本描述
        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));
        LineData data = new LineData();
        data.setValueTextColor(Color.WHITE);

        XAxis xl = 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轴数据
        YAxis leftAxis = 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轴数据
        YAxis rightAxis = chart.getAxisRight();
        rightAxis.setEnabled(false);
    }

    /**
     * 添加数据
     *
     * @param data
     */
    public void AddData(float... data) {
        //获取当前时间  格式为“HH:mm:ss”
        SimpleDateFormat formatter = new SimpleDateFormat("HH:mm:ss");
        String date = formatter.format(new Date());
        dateTime.add(date);
        LineDataSet newDataSet, newDataSet2;
        chart.getXAxis().setValueFormatter(new IndexAxisValueFormatter(dateTime));

        switch (data.length) {
            // 单折线
            case 1:
                if (chart.getData() != null &&
                        chart.getData().getDataSetCount() > 0) {
                    newDataSet = (LineDataSet) chart.getData().getDataSetByIndex(0);
                    newDataSet.addEntry(new Entry(newDataSet.getEntryCount(), data[0]));
                } else {
                    newDataSet = CreateSet(LineChartType.CHART1);
                    LineData lineData1 = new LineData(newDataSet);
                    chart.setData(lineData1);
                }
                break;
            // 多折线
            case 2:
                if (chart.getData() != null &&
                        chart.getData().getDataSetCount() > 0) {
                    newDataSet = (LineDataSet) chart.getData().getDataSetByIndex(0);
                    newDataSet2 = (LineDataSet) chart.getData().getDataSetByIndex(1);
                    newDataSet.addEntry(new Entry(newDataSet.getEntryCount(), data[0]));
                    newDataSet2.addEntry(new Entry(newDataSet2.getEntryCount(), data[1]));
                } else {
                    newDataSet = CreateSet(LineChartType.CHART1);
                    newDataSet2 = CreateSet(LineChartType.CHART2);
                    LineData lineData1 = new LineData(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
     */
    private LineDataSet CreateSet(LineChartType type) {
        LineDataSet set = null;
        switch (type) {
            case CHART1:
                set = new LineDataSet(null, "测试1");
                set.setColor(ColorTemplate.getHoloBlue()); //折线颜色
                set.setFillAlpha(65);//填充透明度
                set.setFillColor(ColorTemplate.getHoloBlue());
                break;
            case CHART2:
                set = new LineDataSet(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);  // 类型,折线还是曲线还是 平线
        return set;
    }

    /**
     * 枚举类型
     */
    private enum LineChartType {
        CHART1, CHART2
    }
}

效果展示

请添加图片描述

注:转载请标明出处

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐