MPAndroidChart LineChart 折线图 你要的都在这里了

2023-05-16

前言
  MPAndroidChart已经出了很长的一段时间,相信大家也有所耳闻,自己也使用了有一段时间,固在此写下文章,根据项目的需求,记录一些见解与问题,作为参考。望大家取其精华去其糟粕。

最终效果图


涉及到的问题以及知识点
图表样式以及基础数据 (快速入门)
x轴标签自定义标签(Formatting Data Values (ValueFormatter))
自定义覆盖物(MarkerView)
自定义多个覆盖物(MarkerView)
默认选中覆盖物(Highlighting Values)
线条的隐藏以及显示(visible)
实现左右滑动
数据更新
当前演示 Demo

快速入门
1.编写布局文件

 <com.github.mikephil.charting.charts.LineChart
        android:id="@+id/chart"
        android:layout_width="match_parent"
        android:layout_height="195dp"
        />

2.实例化并且,设置x轴和y轴的点

mLineChart = findViewById(R.id.chart);

//1.设置x轴和y轴的点
List<Entry> entries = new ArrayList<>();
   for (int i = 0; i < 12; i++)
      entries.add(new Entry(i, new Random().nextInt(300)));


3 .把数据赋值到你的线条

  LineDataSet dataSet = new LineDataSet(entries, "Label"); // add entries to dataset

4.设置数据刷新图表

//3.chart设置数据
  LineData lineData = new LineData(dataSet);
  mLineChart.setData(lineData);
  mLineChart.invalidate(); // refresh
 


很简单吧,但是离我们的效果图还差了好多现在我们开始完善样式,一步一步去设置

样式设置
1.线条样式

        LineDataSet dataSet = new LineDataSet(entries, "Label"); // add entries to dataset
        dataSet.setColor(Color.parseColor("#7d7d7d"));//线条颜色
        dataSet.setCircleColor(Color.parseColor("#7d7d7d"));//圆点颜色
        dataSet.setLineWidth(1f);//线条宽度

2.x和y轴样式

        //设置样式
        YAxis rightAxis = mLineChart.getAxisRight();

        //设置图表右边的y轴禁用
        rightAxis.setEnabled(false);
        YAxis leftAxis = mLineChart.getAxisLeft();
        //设置图表左边的y轴禁用
        leftAxis.setEnabled(false);
        //设置x轴
        XAxis xAxis = mLineChart.getXAxis();
        xAxis.setTextColor(Color.parseColor("#333333"));
        xAxis.setTextSize(11f);
        xAxis.setAxisMinimum(0f);
        xAxis.setDrawAxisLine(true);//是否绘制轴线
        xAxis.setDrawGridLines(false);//设置x轴上每个点对应的线
        xAxis.setDrawLabels(true);//绘制标签  指x轴上的对应数值
        xAxis.setPosition(XAxis.XAxisPosition.BOTTOM);//设置x轴的显示位置
        xAxis.setGranularity(1f);//禁止放大后x轴标签重绘

3.隐藏图例与描述


        //透明化图例
        Legend legend = mLineChart.getLegend();
        legend.setForm(Legend.LegendForm.NONE);
        legend.setTextColor(Color.WHITE);

        //隐藏x轴描述
        Description description = new Description();
        description.setEnabled(false);
        mLineChart.setDescription(description);


4.填充数据

        //chart设置数据
        LineData lineData = new LineData(dataSet);
        //是否绘制线条上的文字
        lineData.setDrawValues(false);
        mLineChart.setData(lineData);
        mLineChart.invalidate(); // refresh

效果图 


是不是已经很接近效果图了,我们在格式化一下x轴标签

x轴标签自定义标签(Formatting Data Values (ValueFormatter))
格式化x轴标签有好几种方式,这里说两个方法 
1.要么自己实现接口的方式

 XAxis xAxis = mLineChart.getXAxis();
 xAxis.setValueFormatter(new IAxisValueFormatter() {
            @Override
            public String getFormattedValue(float value, AxisBase axis) {
                return String.valueOf((int) value + 1).concat("月");
            }
        });

2.要么用库已经写好的类

//准备好每个点对应的x轴数值
List<String> list = new ArrayList<>();
 for (int i = 0; i < 12; i++) {
     list.add(String.valueOf(i+1).concat("月"));
 }
  XAxis xAxis = mLineChart.getXAxis();
 xAxis.setValueFormatter(new IndexAxisValueFormatter(list));

格式化Y轴也是同样的道理

效果图 


自定义覆盖物(MarkerView)
(1) 继承MarkerView复写其中的方法就OJBK了

直接上代码解释吧 -v-

public class DetailsMarkerView extends MarkerView {

    private TextView mTvMonth;
    private TextView mTvChart1;

    /**
     * 在构造方法里面传入自己的布局以及实例化控件    
     * @param context 上下文
     * @param 自己的布局 
   */
    public DetailsMarkerView(Context context, int layoutResource) {
        super(context, layoutResource);
        mTvMonth = findViewById(R.id.tv_chart_month);
        mTvChart1 = findViewById(R.id.tv_chart_1);
    }

    //每次重绘,会调用此方法刷新数据
    @Override
    public void refreshContent(Entry e, Highlight highlight) {
        super.refreshContent(e, highlight);
        try {
            //收入
            if (e.getY() == 0) {
                mTvChart1.setText("暂无数据");
            } else {
                mTvChart1.setText(concat(e.getY(), "支出:"));
            }
            mTvMonth.setText(String.valueOf((int) e.getX() + 1).concat("月"));
        } catch (Exception e1) {
            e1.printStackTrace();
        }
        super.refreshContent(e, highlight);
    }

    //布局的偏移量。就是布局显示在圆点的那个位置
    // -(width / 2) 布局水平居中
    //-(height) 布局显示在圆点上方
    @Override
    public MPPointF getOffset() {
        return new MPPointF(-(getWidth() / 2), -getHeight());
    }

    public String concat(float money, String values) {
        return values + new BigDecimal(money).setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "元";
    }

}

(2) 设置覆盖物

DetailsMarkerView detailsMarkerView = new DetailsMarkerView(this);
//一定要设置这个玩意,不然到点击到最边缘的时候不会自动调整布局
detailsMarkerView.setChartView(mLineChart);
mLineChart.setDetailsMarkerView(detailsMarkerView);

效果图

自定义多个覆盖物(MarkerView)
接下来我们继续完善,达到下面的效果图

要达到上面的效果,我们可以把它当作3个覆盖物 
就是这个意思 


1.先定义好3个覆盖物,DetailsMarkerView(详情),PositionMarker (中间的标杆)RoundMarker(圆点)

class DetailsMarkerView  extends MarkerView{...}
class PositionMarker  extends MarkerView{...}
class RoundMarkerextends MarkerView{...}
1
2
3
2.继承LineChart,重写drawMarkers 方法。我们直接把drawMarkers方法直接复制下来,加上自己所需要的MarkerView,然后计算它们的位置即可

public class MyLineChart extends LineChart {
      //弱引用覆盖物对象,防止内存泄漏,不被回收
    private WeakReference<DetailsMarkerView> mDetailsReference;
    private WeakReference<RoundMarker> mRoundMarkerReference;
    private WeakReference<PositionMarker> mPositionMarkerReference;

    /**
     * 所有覆盖物是否为空
     *
     * @return TRUE FALSE
     */
    public boolean isMarkerAllNull() {
        return mDetailsReference.get() == null && mRoundMarkerReference.get() == null && mPositionMarkerReference.get() == null;
    }

    public void setDetailsMarkerView(DetailsMarkerView detailsMarkerView) {
        mDetailsReference = new WeakReference<>(detailsMarkerView);
    }

    public void setRoundMarker(RoundMarker roundMarker) {
        mRoundMarkerReference = new WeakReference<>(roundMarker);
    }

    public void setPositionMarker(PositionMarker positionMarker) {
        mPositionMarkerReference = new WeakReference<>(positionMarker);
    }


   /**
      复制父类的 drawMarkers方法,并且更换上自己的markerView
     * draws all MarkerViews on the highlighted positions
     */
    protected void drawMarkers(Canvas canvas) {
        DetailsMarkerView mDetailsMarkerView = mDetailsReference.get();
        RoundMarker mRoundMarker = mRoundMarkerReference.get();
        PositionMarker mPositionMarker = mPositionMarkerReference.get();

        // if there is no marker view or drawing marker is disabled
        if (mDetailsMarkerView == null || mRoundMarker == null || mPositionMarker == null || !isDrawMarkersEnabled() || !valuesToHighlight())
            return;

        for (int i = 0; i < mIndicesToHighlight.length; i++) {

            Highlight highlight = mIndicesToHighlight[i];

            IDataSet set = mData.getDataSetByIndex(highlight.getDataSetIndex());

            Entry e = mData.getEntryForHighlight(mIndicesToHighlight[i]);

            int entryIndex = set.getEntryIndex(e);

            // make sure entry not null
            if (e == null || entryIndex > set.getEntryCount() * mAnimator.getPhaseX())
                continue;

            float[] pos = getMarkerPosition(highlight);

            LineDataSet dataSetByIndex = (LineDataSet) getLineData().getDataSetByIndex(highlight.getDataSetIndex());

            // check bounds
            if (!mViewPortHandler.isInBounds(pos[0], pos[1]))
                continue;

            float circleRadius = dataSetByIndex.getCircleRadius();

            //pos[0], pos[1] x 和 y 
            // callbacks to update the content
            mDetailsMarkerView.refreshContent(e, highlight);

            mDetailsMarkerView.draw(canvas, pos[0], pos[1] - mPositionMarker.getHeight());


            mPositionMarker.refreshContent(e, highlight);
            mPositionMarker.draw(canvas, pos[0] - mPositionMarker.getWidth() / 2, pos[1] - mPositionMarkerl.getHeight());

            mRoundMarker.refreshContent(e, highlight);
            mRoundMarker.draw(canvas, pos[0] - mRoundMarker.getWidth() / 2, pos[1] + circleRadius - mRoundMarker.getHeight());
        }


设置覆盖物 activity主要代码

protected void onCreate(Bundle savedInstanceState) {
    ......
    //点击图表坐标监听
        mLineChart.setOnChartValueSelectedListener(new OnChartValueSelectedListener() {
            @Override
            public void onValueSelected(Entry e, Highlight h) {
                //查看覆盖物是否被回收
                if (mLineChart.isMarkerAllNull()) {
                    //重新绑定覆盖物
                    createMakerView();
                    //并且手动高亮覆盖物
                    mLineChart.highlightValue(h);
                }
            }

            @Override
            public void onNothingSelected() {

            }
        });
    ......
}


  /**
    * 创建覆盖物
    */
   public void createMakerView() {
       DetailsMarkerView detailsMarkerView = new DetailsMarkerView(this);
       detailsMarkerView.setChartView(mLineChart);
       mLineChart.setDetailsMarkerView(detailsMarkerView);
       mLineChart.setPositionMarker(new PositionMarker(this));
       mLineChart.setRoundMarker(new RoundMarker(this));
   }

这样就大功告成啦!!

默认显示覆盖物(Highlighting Values)


可以通过上面的方法,默认显示覆盖物,比如

   //默认显示第一个覆盖物
   mLineChart.highlightValue(0,0);
1
2
线条的隐藏以及显示(Highlighting Values)
可以通过LineChart对象获取到线条LineDataSet实体类。然后调用LineDataSet.setVisible(true或者false);,进行隐藏或显示

mLineChart.getLineData().getDataSets().get(0).setVisible(true);
1
左右滑动,并动态切换放大倍数


代码

//x放大5倍  1f代表不放大
mLineChart.zoomToCenter(5, 1f);


//切记如果要动态的更换倍数,或者还原倍数一定要调用下面的这个方法停止惯性滑动
//不然在拖动过程当中是无法更换倍数
BarLineChartTouchListener barLineChartTouchListener = (BarLineChartTouchListener) mLineChart.getOnTouchListener();
 barLineChartTouchListener.stopDeceleration();

更新数据
主要的逻辑: 
1. 准备要更新的数据源 
2. 检查是否有LineDataSet 存在 
3. 有,则通过LineDataSet 的setValues更换整个坐标,或者 data.addEntry(…) 添加一个或者 data.removeEntry(…)删除一个。 
4. 无,则创建LineDataSet ,重新构造数据源 
4. 调用mLineChart.invalidate();更新图表

代码实例:

 //1,准备要更换的数据
     List<Entry> entries = new ArrayList<>();
        for (int i = 0; i < 12; i++)
            entries.add(new Entry(i, new Random().nextInt(300)));

        //2. 获取LineDataSet线条数据集
        List<ILineDataSet> dataSets = mLineChart.getLineData().getDataSets();

       //是否存在
         if (dataSets != null && dataSets.size() > 0) {
             //直接更换数据源
             for (ILineDataSet set : dataSets) {
                 LineDataSet data = (LineDataSet) set;
                 data.setValues(entries);
             }
         } else {
             //重新生成LineDataSet线条数据集
             LineDataSet dataSet = new LineDataSet(entries, "Label"); // add entries to dataset
            dataSet.setDrawCircles(false);
               dataSet.setColor(Color.parseColor("#7d7d7d"));//线条颜色
               dataSet.setCircleColor(Color.parseColor("#7d7d7d"));//圆点颜色
               dataSet.setLineWidth(1f);//线条宽度
               LineData lineData = new LineData(dataSet);
               //是否绘制线条上的文字
               lineData.setDrawValues(false);
               mLineChart.setData(lineData);
           }
           //更新
           mLineChart.invalidate();


 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

MPAndroidChart LineChart 折线图 你要的都在这里了 的相关文章

  • MPAndroidChart PieChart如何设置标签文本?

    得到以下代码 Legend legend mChart getLegend legend setLabels new String aaaaa bbbbb ccccc 此设置不生效 还有其他方法设置文本吗 我在 v3 0 0 中找不到方法
  • 如何根据标签更改 Chart.js 点的颜色

    我有一个 Chart js 折线图 其中标签是星期几 我想根据具体日期 周一至周日 更改点背景 我可以根据数据值更改背景颜色 但这不是我需要的 相反 我想给每一天 标签 一个不同的色点 例如 这就是我如何根据数据值更改点 不是我需要的 ch
  • 在折线图上显示图像而不是圆圈

    我创建了一个LineChart使用 MPAndroidChart 库 一切都很好 现在我想做的是显示一个可绘制的 图像 而不是图表上每个条目的默认圆圈 我已经尝试了 API 中的很多选项 但没有成功 谁能告诉我我该怎么做 最后 在尝试了很多
  • 如何隐藏折线图中数据点的标签

    请参考下图 基本上我有一个 C 图表控件 上面有一些系列 我有一个带标签的系列 图中的红线 但我希望能够打开 关闭标签 那可能吗 我找不到任何可以这样做的属性 多谢 缺少清除文本labels一种简单的方法是使颜色透明 你可以为整体做到这一点
  • MPAndroidChart - 是否可以控制图表元素的 z-index?

    我希望在我的绘图中有以下顺序MPAndroid图表 从下到上 数据连接线 限制线 数据点 是否可以 我知道方法com github mikephil charting components AxisBase setDrawLimitLine
  • 为谷歌折线图动态透视数据

    我想在同一个折线图中显示多年来各个国家的 人口 显示的数据基于多选下拉列表 国家 地区 中的选择 基础数据表有 3 列 年份 国家 人口2012年 A国 332013年 A国 352014年 A国 402012年 B国 652013年 B国
  • MpChart 在条形图的 X 轴上绘制图标作为标签

    Hi I would like to draw icons in xaxis of the bar chart instead of values Like the chart below 您必须创建自己的自定义渲染器并将其应用到您的图表
  • MpAndroidChart - 如何填充组合图表上两条线之间的区域?

    This answer https stackoverflow com a 43452404类似的问题显示了如何填充 MpAndroidChart 上两行之间的区域折线图 但是 我没有使用 LineChart 而是使用组合图 因为我还有条形
  • Chartjs - pointColor 跟随渐变描边的当前颜色

    我刚刚使用创建折线图chartjs http www chartjs org 图书馆和我设法用渐变颜色进行描边 这里很简单fiddle http jsfiddle net jvmk5o6a 例如我到目前为止所做的事情 接下来我需要做的是po
  • 如何更改分类 x 轴的绘图顺序

    我得到了一个数据框 如下所示 df Time of Day Season value Day Shoulder 30 581606 Day Summer 25 865560 Day Winter 42 644530 Evening Shou
  • MPAndroidChart - 自 v2 以来删除顶部边框/轴

    我将 MPAndroidChart 从 v1 7 升级到 v2 并且必须更改一些内容 新的事情之一是我现在似乎有一个最大值的顶部边框 我试图隐藏所有边框的代码是这样的 LineChart graph LineChart connection
  • C# 折线图如何创建垂直线

    我有一个折线图 例如这样 我想画一条垂直线 其底部有一个标签 当我将标签拖动到这些 x 点上方时 我希望该标签以其垂直线穿过图表的 x 点 我将得到带标签的线与 Y 轴匹配的 y 点 例如 我怎样才能做到这一点 此解决方案允许您左右拖动注释
  • 有没有办法在 JavaFX LineChart 中断开串联的 2 个点?

    我在 LineChart 上有四个系列 每个系列都包含一定数量的按时间划分的图表 默认情况下 LineChart 连接这些图表 它看起来很难看并且在上下文中没有任何意义 所以我想将它们分开 但保留颜色和图例 换句话说 我想要的是删除两个特定
  • 从 Zingchart 中的 CSV 数据获取系列和值

    While creating mixed chart in Zingchart we can pass the type attribute values with values array But I m not sure when re
  • 我在我的项目中添加 MPAndroidChart 但在我的 xml 中找不到 LineChart

    我想在我的项目中使用 MPAndroidChart 我在我的 gradle 中添加了该库 但在我的 xml 布局文件中 我找不到 Chart 我的 build gradle Module 是这样的 dependencies implemen
  • Google Charts:折线图和柱形图之间的切换

    我有一个仪表板 其中包含一些 Google Analytics 指标 我想在每日 每月和每周图表上绘制这些指标 日线图为折线图 其他为柱形图 我能够将图表最初绘制为折线图或条形图 然后将其重新绘制为不同类型 但之后它不会再次重新绘制 这是我
  • MPAndroidChart StackedBarChart 显示值但不显示条形图

    我开始使用MPAndroidChart https github com PhilJay MPAndroidChart图书馆来建立一个StackedBarChart显示三个 y 值 这是代码 public class Plot final
  • 需要帮助绘制多元线之间的区域 - 而不是从轴到线

    我是 d3 js 的新手 我正在努力填充多元百分位数图中线条之间的区域 我不希望在最底线下方或最顶线上方填充任何区域 第一列始终位于图表的底部 第 5 个百分位 最后一列将始终位于图表的顶部 第 95 个百分位 我需要每条线之间单独的区域段
  • 具有可变颜色线的柔性折线图

    我有一个相当简单的需求来创建折线图 我想要绘制图表的数据基于单个每日数据点 xml数据示例
  • 如何不在折线图上绘制零值,同时在 MPAndroidChart 上显示 X 轴值?

    我正在使用 MPAndroidChart 以折线图显示我的数据 对于每个日期我都有自己的价值 这很好用 我现在想要做的是不绘制 0 值 而是在 2 个相邻非零值之间绘制线条 如趋势线 同时继续在 x 轴上显示零值的日期 My current

随机推荐

  • closehandle()函数

    引用自 百度百科 xff0c 用于解决今天遇到的close handle 后什么时候释放资源问题 xff1a 方法名称 xff1a CloseHandle 位置 xff1a Kernel32 dll BOOL CloseHandle HAN
  • c++清空串口缓冲区

    缓冲区控制 Win32通信API除了提供SetupComm 函数实现初始化的缓冲区控制外 xff0c 还提供了PurgeComm 函数和FlushFileBuffers 函数来进行缓冲区操作 PurgeComm 函数的声明如下 xff1a
  • C++ int与string的转化

    int本身也要用一串字符表示 xff0c 前后没有双引号 xff0c 告诉编译器把它当作一个数解释 缺省情况下 xff0c 是当成10进制 xff08 dec xff09 来解释 xff0c 如果想用8进制 xff0c 16进制 xff0c
  • c++中字符数组内存和指针问题示例解答

    char id 61 34 123456 34 char c 61 34 SN 61 34 unsigned char buffer 20 int j 61 0 for int i 61 0 i lt strlen c i 43 43 bu
  • c++中LPCTSTR,LPTSTR 解释

    char是C语言标准数据类型 xff0c 字符型 xff0c 至于由几个字节组成通常由编译器决定 xff0c 一般一个字节 Windows为了消除各编译器的差别 xff0c 重新定义了一些数据类型 xff0c 你提到了另外几个类型都是这样
  • Delphi 自定义事件的例子

    我们这个控件将演示控件的自定义事件的书写 这个控件有一个类型为string的SensitiveText属性 xff0c 当用户在输入框中输入的文字为InvalidText时就会触发OnSensitiveText事件 按照惯例 xff0c 我
  • c++中sscanf的用法

    sscanf 读取格式化的字符串中的数据 swscanf 是 sscanf 的宽字符版本 xff1b swscanf 的参数是宽字符串 swscanf不处理 Unicode 全角十六进制或 34 兼容性区 34 字符 除此以外 xff0c
  • c++内存测试

    void MemoryTest 内存测试 指针嵌套 char rr 栈中分配内存 系统自动分配释放 xff09 int ee 61 int amp rr 将rr的内存地址转换成整型数 char yy 61 amp rr 定义一个字符型指针y
  • C/C++串口通信原理及读写与操作

    http wangbaiyuan cn c serial communication write reading html 展开 文章目录 在工业控制中 xff0c 工控机 xff08 一般都基于Windows平台 xff09 经常需要与智
  • c# 调用c库dll ,char*转string的解决办法

    最近由于有个未知的设备需要用到modbus通讯协议 xff0c 底层需要与PLC通讯 xff0c 坤跌 xff0c PLC啥型号也不清楚封在里面不能拆 前人只留了几个不能运行的QT代码以及不完整的文档 用惯了C 想要重新学QT xff0c
  • C++多线程编程(入门实例)

    多线程在编程中有相当重要的地位 xff0c 我们在实际开发时或者找工作面试时总能遇到多线程的问题 xff0c 对多线程的理解程度从一个侧面反映了程序员的编程水平 其实C 43 43 语言本身并没有提供多线程机制 xff08 当然目前C 43
  • Android Studio 使用Log

    Android使用log来记录信息 xff0c 测试了下 xff0c 和system out println区别不大 xff0c 主要优势在于能使用过滤器过滤日志 本文记录基础的log使用方法 xff0c 来自 第一行代码 xff0c 以及
  • 指针强制转换问题

    void ff void abc 任意类型数据指针 xff08 指针即内存地址 xff09 int z 61 int abc 强制转换成int 指针变量 int zz 61 z 获取内存中的值
  • 新手git教程

    本文转载自 xff1a http igeekbar com igeekbar post 82 htm Git近些年的火爆程度非同一般 xff0c 这个版本控制系统被广泛地用在大型开源项目 xff08 比如Linux xff09 xff0c
  • 使用Project进行项目管理

    下面开始介绍Project的使用 1 从下列地址获取Project 2010的副本 版权问题 xff0c 已删除地址 2 安装 2 1 版权页 2 2 自定义安装页 2 3 安装完毕 3 使用该软件进行项目管理 3 1 打开Project
  • Marshal在C#中的应用(void *指针到IntPtr的转化)

    C 调用C语言的API时一般把void 指针转换成IntPtr xff0c 但这经常远远不够的 在C语言中void 是个万金油 xff0c 尤其是一些老的c语言程序 xff0c 所有的参数就一个void 指针 xff0c 里面包罗万象 xf
  • VS2012 2013 无法显示查找功能 无法具体定位 解决方法

    xfeff xfeff 问题的现象 通过使用 Ctrl 43 Shift 43 F 也就是Find In Files功能 xff0c 使用之后只能显示统计结果 不显示具体行 如下图 regedit 中在注册表中查找 xff1a HKEY C
  • C#中使用指针转换数据类型[C#/unsafe]

    今日因为一个同事说起 xff0c 在原来的旧系统中使用指针做数据转换很方便 xff0c 比如要把浮点数转化为数组 xff0c 也或者是字符串的相互转换 xff1b 当然 xff0c 大家都知道c 中实现指针只需要写入unsafe 编译选项把
  • c#指针的使用例程

    unsafe double value 61 888888 byte v1 61 BitConverter GetBytes value byte v2 61 new byte v1 Length double pv 61 amp valu
  • MPAndroidChart LineChart 折线图 你要的都在这里了

    前言 MPAndroidChart已经出了很长的一段时间 xff0c 相信大家也有所耳闻 xff0c 自己也使用了有一段时间 xff0c 固在此写下文章 xff0c 根据项目的需求 xff0c 记录一些见解与问题 xff0c 作为参考 望大