Android 图表开发开源库 MPAndroidChart 使用总结

1. 引言

电视项目中需要一个折线图表示节电数据变化情况,类比 H5 来说,Android 中也应该有比较成熟的控件,经过调研后,发现 MPAndroidChart 功能比较强大,网上也有人说可能是目前 Android 开发最好用的一个三方库了,功能非常强大,集成简单。

这里把我的集成使用过程及使用中发现的强大惊喜记录下来,留作团队财富,可供后续参考。

首先,简单的介绍下强大的 MPAndroidChart,它支持常用的各种图:柱状图(横向,竖向)、线状图(多种效果)、饼状图、点状图,属性也很简单,我们使用的时候只需要熟悉控件的 11 各种属性即可。核心功能如下:

  • 支持 x,y 轴缩放

  • 支持拖拽

  • 支持手指滑动

  • 支持高亮显示

  • 支持保存图表到文件中

  • 支持从文件(txt)中读取数据

  • 预先定义颜色模板

  • 自动生成标注

  • 支持自定义 x,y 轴的显示标签

  • 支持 x,y 轴动画

  • 支持 x,y 轴设置最大值和附加信息

  • 支持自定义字体,颜色,背景,手势,虚线等

2. 集成

集成很简单,直接导入作为依赖就可以,以下写了一个最小集成的例子,主要为了说明步骤:

2.1 引入依赖到工程中

java 复制代码
// 项目工程的 build.gradle
repositories {
   maven { url "https://jitpack.io" }
}

// Module的 build.gradle
dependencies {
       implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0'
}

2.2 Layout 中添加控件

java 复制代码
<!--节电折线图-->
<com.github.mikephil.charting.charts.LineChart
    android:id="@+id/chart1"
    android:layout_width="0dp"
    android:layout_height="0dp"
    android:layout_alignParentTop="true"
    android:layout_alignParentBottom="true"
    android:layout_alignParentStart="true"
    android:layout_alignParentEnd="true"
    android:layout_marginTop="@dimen/tvcommon_px131"
    android:layout_marginBottom="@dimen/tvcommon_px60"
    android:layout_marginStart="@dimen/tvcommon_px44"
    android:layout_marginEnd="@dimen/tvcommon_px30" />

2.3 Activity 中使用

java 复制代码
// 3.4 节电详情页
mChart = findViewById(R.id.chart1)
initChat()
setChatData()

2.3.1 初始化

java 复制代码
private fun initChat() {
    mChart.setBackgroundColor(resources.getColor(R.color.color_00000000))
    mChart.description.isEnabled = false

    // 2. X 轴样式
    val xAxis: XAxis = mChart.getXAxis()
    xAxis.setDrawGridLines(false)
    // xAxis.enableGridDashedLine(10f, 10f, 0f);
    xAxis.position = XAxis.XAxisPosition.BOTTOM

    // 3. Y轴样式
    mChart.axisRight.isEnabled = false  // disable dual axis (only use LEFT axis)
    var yAxis: YAxis = mChart.axisLeft
    yAxis.axisMaximum = 200f
    yAxis.axisMinimum = 0f
}

2.3.2 Activity 中设置数据

java 复制代码
public void setChatData(){
        List<Entry> entries=new ArrayList<>();
        List<Entry> entries1=new ArrayList<>();
        entries.add(new Entry(3f,20));
        entries1.add(new Entry(5f,30));
 
        LineDataSet dataSet=new LineDataSet(entries,"数据一");
        LineDataSet dataSet1=new LineDataSet(entries1,"数据二");
        List<ILineDataSet> list=new ArrayList<>();
        list.add(dataSet);
        list.add(dataSet1);
        LineData lineData=new LineData(list);

        mChat.setData(lineData); //将模拟数据用于线形图,在线形图显示
    }

2.3.3 大功告成

3. 使用总结

MPAndroidChart 的强大之处在于它的很多功能都可定制,只要你有想法,大部分都有解决方法,哪怕一时没有,只要肯找,说不准就能发现。

电视项目中要求的是一个折线图,所以这里的使用总结大多集中在折线及我们要实现的效果上,其它未涉及的图和属性暂时不写,后续使用的时候再作探索及总结。

3.1 整体功能及专业术语一览(网上找的一张图,镇楼,哈哈)

3.2 基础设置 (非数据类型,在初始化时设置)

首先,在 initChat()函数中,进行了一些基础设置,把一些用不到的功能关闭:

java 复制代码
private fun initChat() {
    mChart.setBackgroundColor(resources.getColor(R.color.color_00000000))
    mChart.description.isEnabled = false
    mChart.setTouchEnabled(false)
    mChart.setDrawGridBackground(false)
    mChart.isDragEnabled = false
    mChart.setScaleEnabled(false)
    mChart.setPinchZoom(false)
    mChart.legend.isEnabled = false
}

3.3 x、y 轴设置 (非数据类型,在初始化时设置)

这个也不属于数据设置,所以在初始化时进行,如下,分别进行了线宽,线颜色,Label 的字体大小、颜色、还有网格线:

java 复制代码
private fun initChat() {
    ...
    // 2. X 轴样式
    val xAxis: XAxis = mChart.getXAxis()
    xAxis.setDrawGridLines(false)
    // xAxis.enableGridDashedLine(10f, 10f, 0f);
    xAxis.position = XAxis.XAxisPosition.BOTTOM
    xAxis.axisLineWidth = resources.getDimension(R.dimen.tvcommon_px1)
    xAxis.axisLineColor = resources.getColor(R.color.color_979797)
    xAxis.textSize = resources.getDimension(R.dimen.tvcommon_sp16)
    xAxis.textColor = resources.getColor(R.color.white_60alpha)
    xAxis.valueFormatter = object : ValueFormatter() {
        override fun getAxisLabel(value: Float, axis: AxisBase?): String {
            // 自定义 X 轴显示内容
            return super.getAxisLabel(value, axis) + "月"
        }
    }
    
    // 3. Y轴样式
    mChart.axisRight.isEnabled = false  // disable dual axis (only use LEFT axis)
    var yAxis: YAxis = mChart.axisLeft
    yAxis.axisLineWidth = resources.getDimension(R.dimen.tvcommon_px1)
    yAxis.axisLineColor = resources.getColor(R.color.color_979797)
    yAxis.textSize = resources.getDimension(R.dimen.tvcommon_sp16)
    yAxis.textColor = resources.getColor(R.color.white_60alpha)
    yAxis.setDrawGridLines(true)        // horizontal grid lines
    yAxis.enableGridDashedLine(resources.getDimension(R.dimen.tvcommon_px6),
        resources.getDimension(R.dimen.tvcommon_px6),
        0f)
    yAxis.axisMaximum = 200f
    yAxis.axisMinimum = 0f
}

3.4 折线类型设置

需要在 填充数据时,给 LineDataSet 设置一个 mode,如下

java 复制代码
private fun setChatData() {
    ...
    val set1 = LineDataSet(values, "DataSet 1")
    set1.mode = LineDataSet.Mode.HORIZONTAL_BEZIER  // 折线类型
}

setMode(LineDataSet.Mode mode),设置模式有四种: 1.CUBIC_BEZIER 立方曲线 2.LINEAR 直线 3.STEPPED 阶梯 4.HORIZONTAL_BEZIER 水平曲线

电视项目这里需要的是一个曲线,所以设置为 HORIZONTAL_BEZIER

3.5 顶点设置

顶点可进行 icon 绘制、小圆点(实心/空心)、自定义值显示

java 复制代码
private fun setChatData() {
    ...
    val set1 = LineDataSet(values, "DataSet 1")
    set1.mode = LineDataSet.Mode.HORIZONTAL_BEZIER  // 折线类型
    // 顶点
    set1.setValueFormatter(object : ValueFormatter() {
        override fun getFormattedValue(value: Float): String {
            return "" // 这里返回空,即顶点不显示值,否则不太好看
        }
    })
    set1.valueTextSize = resources.getDimension(R.dimen.tvcommon_sp12)
    set1.setDrawIcons(false) // 不显示端点的icon
    set1.setDrawCircles(false) // 显示顶点圆quan
    set1.setDrawCircleHole(true) // 空心还是实心
}

3.6 折线及填充设置

java 复制代码
private fun setChatData() {
    ...
    // 折线
    set1.color = resources.getColor(R.color.blue_gray_32B5E6)
    set1.setCircleColor(resources.getColor(R.color.blue_gray_32B5E6))
    set1.lineWidth = resources.getDimension(R.dimen.tvcommon_px2)
    set1.circleRadius = resources.getDimension(R.dimen.tvcommon_px4)
    
    // 折线包裹起来的区域
    set1.fillFormatter = IFillFormatter { dataSet, dataProvider -> mChart.getAxisLeft().getAxisMinimum() }
    if (Utils.getSDKInt() >= 18) {
        // drawables only supported on api level 18 and above
        val drawable = ContextCompat.getDrawable(this, R.drawable.shape_chat_fill_blue)
        set1.fillDrawable = drawable
    } else {
        set1.fillColor = R.color.blue_gray_32B5E6
    }
    set1.setDrawFilled(true)
}

3.7 X 轴自定义显示 (非数据类型,在初始化时设置)

再回到 X 轴,由于项目要求,X 轴要显示节电的日期,但是构造数据时只是一个数组,默认显示的是数组的下标,所以这里需要自定义

java 复制代码
private fun initChat() {
    ... 
    // 2. X 轴样式
    val xAxis: XAxis = mChart.getXAxis()
    ...
    xAxis.valueFormatter = object : ValueFormatter() {
        override fun getAxisLabel(value: Float, axis: AxisBase?): String {
            // 自定义 X 轴显示内容,这里可以通过 axis 中的 postion 从数据中进行一个映射,找到它对应的日期
            return super.getAxisLabel(value, axis) + "月"
        }
    }
}

4. 踩坑及解决

4.1 宽度或 margin 跟自己设置的不一样

  1. 效果出来后,发现最右侧不是我设置的,比我预计的靠左了,离边比较远,达不到 UI 设计的效果,如下:
  1. 这里的设置应该不是通过简单的 XML 位置属性就能修改的了,因为这属于 MPAndroidChat 控件的内部了,所以得深入 MPAndroidChat,找到产生间隙的原因,于是开始翻源码

  2. 经过翻阅源码得知,它内部有一个 minOffset

java 复制代码
@Override
public void calculateOffsets() {
    ...
    float minOffset = Utils.convertDpToPixel(mMinOffset);
    mViewPortHandler.restrainViewPort(
        Math.max(minOffset, offsetLeft),
        Math.max(minOffset, offsetTop),
        Math.max(minOffset, offsetRight),
        Math.max(minOffset, offsetBottom));
    ...
}
public void setMinOffset(float minOffset) {
    mMinOffset = minOffset;
}

再往下查,原来 mMinOffset,是可能通过 setMinOffset()设置进去的,所以在我们的代码 initChat()中添加上一行,再看效果,OK 啦~

java 复制代码
private fun initChat() {
    ...
    mChart.minOffset = 0f
    ...
}

4.2 设置 x 轴的 Label 后,发现最底下一层有一点显示不全,被截断了,如下

  1. 分析原因

(1)这个应该也是 MPAndroidChat 内部实现导致的,修改外部 Layout 中的 margin, padding 应该不生效,果然,试过之后,没有生效,问题依旧

(2)16sp 时显示是这个效果,而缩小字号后,比如 12sp, 就没有问题,能够显示,猜测,是它内部写死了一个距离,但看源码内的相关说明,它的高度是自动计算的,如下

java 复制代码
/**
 * Class representing the x-axis labels settings. Only use the setter methods to
 * modify it. Do not access public variables directly. Be aware that not all
 * features the XLabels class provides are suitable for the RadarChart.
 *
 * @author Philipp Jahoda
 */
public class XAxis extends AxisBase {

    /**
     * width of the x-axis labels in pixels - this is automatically
     * calculated by the computeSize() methods in the renderers
     */
    public int mLabelWidth = 1;

    /**
     * height of the x-axis labels in pixels - this is automatically
     * calculated by the computeSize() methods in the renderers
     */
    public int mLabelHeight = 1;

(3)按理说不应该出现这种情况,难道是 MPAndroidChat 的 Bug,是不是高版本就好了呢,于是上网查了一下,https://gitee.com/jiangsongbai/MPAndroidChart, Github 打不开,到这里的一个 fork 上看一下,发现我用的已经是最新版了 v3.1.0 (吐槽:版本号竟然带个 v,看来不专业啊~)

(4)小插曲:由于项目中使用的是 dimen 中定义的 tvcommon_sp16,在不同分辨率下,可能不一样,我原先是写死的,抱着一点小希望,在代码中使用 dimen 试一下,期望字体能够变小一点,不触发此问题,结果又失望了

java 复制代码
xAxis.textSize = resources.getDimension(R.dimen.tvcommon_sp16)

2. extraBottomOffset

(1)再翻阅源码,还是在 BarLineChatBase.java 中的 calculateOffsets() 函数,发现了端倪,如下:

java 复制代码
@Override
public void calculateOffsets() {
    ....
    offsetTop += getExtraTopOffset();
    offsetRight += getExtraRightOffset();
    offsetBottom += getExtraBottomOffset();
    offsetLeft += getExtraLeftOffset();
    
    float minOffset = Utils.convertDpToPixel(mMinOffset);
    
    mViewPortHandler.restrainViewPort(
            Math.max(minOffset, offsetLeft),
            Math.max(minOffset, offsetTop),
            Math.max(minOffset, offsetRight),
            Math.max(minOffset, offsetBottom));
    ....
}

(2)这里有一个 getExtraBottomOffset(),再往下看,它是在基类 chat.java 中,有一个 mExtraBottomOffset,

java 复制代码
/**
 * @return the extra offset to be appended to the viewport's bottom
 */
public float getExtraBottomOffset() {
    return mExtraBottomOffset;
}

(3)看注释,像是有点用,尝试一下

java 复制代码
private fun initChat() {
    ....
    mChart.minOffset = 0f
    mChart.extraBottomOffset = resources.getDimension(R.dimen.tvcommon_px2) // 因为X轴的Label字体设为 16sp 后,最底下有一点显示不全,需要在这里打上一个小补丁。
    ....
}

(4)大功告成~

4.3 顶点的数据显示想要隔位置显示效果

跟产品讨论的时候,把 UI 原先设计的选中点的值显示出来的效果去掉了(因为电视上没有触摸,通过遥控器交互暂时先不做选中效果),如果显示出来所有的数据,会显得比较凌乱,所以想进行隔几个显示或什么效果,研究后,发现重截函数中没有位置信息,无法进行计算是哪一个 postion,暂时无法做到不同的效果,只能统一显示或不显示,后续再进行深入研究,看是否能够做到

java 复制代码
private fun setChatData() {
    ...
    val set1 = LineDataSet(values, "DataSet 1")
    set1.mode = LineDataSet.Mode.HORIZONTAL_BEZIER  // 折线类型
    // 顶点
    set1.setValueFormatter(object : ValueFormatter() {
        override fun getFormattedValue(value: Float): String {
            return "" // 这里返回空,即顶点不显示值,否则不太好看
        }
    })
    ...
}

5. 小结

这里记录了在电视项目中使用 MPAndroidChat 的一些使用心得,重点集中在折线上,通过对它的各种属性进行修改自定义了自己的 UI 界面,达到与 UI 设计图一样的效果,也发掘出了 minOffset、extraBottomOffset这样的小众属性的使用场景及效果,希望能够引起大家的一些共鸣,发现问题时,快速找到解决方法。

6. 团队介绍

**「三翼鸟数字化技术平台-场景设计交互平台」**主要负责设计工具的研发,包括营销设计工具、家电VR设计和展示、水电暖通前置设计能力,研发并沉淀素材库,构建家居家装素材库,集成户型库、全品类产品库、设计方案库、生产工艺模型,打造基于户型和风格的AI设计能力,快速生成算量和报价;同时研发了门店设计师中心和项目中心,包括设计师管理能力和项目经理管理能力。实现了场景全生命周期管理,同时为水,空气,厨房等产业提供商机管理工具,从而实现了以场景贯穿的B端C端全流程系统。

相关推荐
萌面小侠Plus1 小时前
Android笔记(三十三):封装设备性能级别判断工具——低端机还是高端机
android·性能优化·kotlin·工具类·低端机
慢慢成长的码农1 小时前
Android Profiler 内存分析
android
大风起兮云飞扬丶1 小时前
Android——多线程、线程通信、handler机制
android
L72561 小时前
Android的Handler
android
清风徐来辽1 小时前
Android HandlerThread 基础
android
非著名程序员2 小时前
腾讯为什么支持开源?
开源
CSDN云计算2 小时前
如何以开源加速AI企业落地,红帽带来新解法
人工智能·开源·openshift·红帽·instructlab
HerayChen2 小时前
HbuildderX运行到手机或模拟器的Android App基座识别不到设备 mac
android·macos·智能手机
顾北川_野2 小时前
Android 手机设备的OEM-unlock解锁 和 adb push文件
android·java
hairenjing11232 小时前
在 Android 手机上从SD 卡恢复数据的 6 个有效应用程序
android·人工智能·windows·macos·智能手机