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 折线图 你要的都在这里了 的相关文章

  • Android图表年度最强总结,一篇文章从入门到精通!

    说到Android图表 不得不说一说MPAndroidChart这个强大的开源图表库 至于有多强大 先给你看看实现的效果图 如果效果图成功地引起了你的注意 那么 嘿嘿嘿 当然是把这篇文章看完 最后再点个赞 文章目录 添加依赖 创建视图以及简
  • MPAndroidChart:如何自定义条形值标签

    我在我的 Android 应用程序中使用 MPAndroidChart 我用一个BarChart由 组成的BarEntry 我还启用了 y 值显示在栏顶部的功能 我的问题是我希望条形顶部的值是整数 例如5 但目前这些值显示为5 00 那么我
  • 如何根据标签更改 Chart.js 点的颜色

    我有一个 Chart js 折线图 其中标签是星期几 我想根据具体日期 周一至周日 更改点背景 我可以根据数据值更改背景颜色 但这不是我需要的 相反 我想给每一天 标签 一个不同的色点 例如 这就是我如何根据数据值更改点 不是我需要的 ch
  • Echarts:绘制信号的方差

    我想在图表中绘制多个信号的方差 或者基本上填充上部信号和下部信号之间的空间 是否可以创建这样的图表 我看到了置信带示例 https echarts apache org examples en editor html c confidenc
  • 在python中向时间序列图添加水平限制线

    我想为温度时间序列图添加水平上限和下限线 假设上限线为 30 下限线为 10 df3 plot x Date y Temp PM Temp AM figsize 20 8 我认为这个解决方案可以帮助你 import matplotlib p
  • 使用 MPAndroidChart 在条形图中的时间轴轴上绘制数据值

    我想在类似于 iOS 的 健康 应用程序的 Android 应用程序中绘制条形图 Here are screenshots 我尝试使用绘图MPAndroid图表 我已经看到该库中给出的示例 但无法按照我的要求进行绘制 我能够显示 1 年图的
  • 在 JFreeChart 中将原点从 (0,0) 移动到 (0,50)

    As shown in image the graph is from line y 0 here i want to plot a graph from y 50 how can i specify this in JFreeChart
  • Chartjs - 如何在 x 轴标签上获取过去 7 天的数据?

    我试图在折线图的 x 轴上获取过去 7 天的信息 使用 Chartjs 做这个的最好方式是什么 Thanks 您可以使用以下代码实例化过去 7 天的图表 let start new Date end new Date start setDa
  • Android Afreechart - 更改点形状、线条粗细和颜色

    我正在尝试在我的应用程序中更改折线图点的形状 我在用afreechart s TimeSeriesChart 我想让这条线在粗细 颜色和点形状方面变得更漂亮 哪里可以改代码 将使用哪种方法 我现在拥有的 source googlecode
  • Chartjs - pointColor 跟随渐变描边的当前颜色

    我刚刚使用创建折线图chartjs http www chartjs org 图书馆和我设法用渐变颜色进行描边 这里很简单fiddle http jsfiddle net jvmk5o6a 例如我到目前为止所做的事情 接下来我需要做的是po
  • MPAndroidChart - 自 v2 以来删除顶部边框/轴

    我将 MPAndroidChart 从 v1 7 升级到 v2 并且必须更改一些内容 新的事情之一是我现在似乎有一个最大值的顶部边框 我试图隐藏所有边框的代码是这样的 LineChart graph LineChart connection
  • MpAndroidChart 在限制线之间设置背景

    I a using MpAndroidChart library I need to implement a design where I need to color the area between two limit lines I h
  • 如何在 Flutter 中使用 fl_chart 在折线图中水平滚动?

    我想用折线图显示列表中的数据 问题是宽度太小 所以我希望你可以水平滚动来查看所有内容 如何使用 fl chart 包执行此操作 这是我的代码 我从列表中构建点 override Widget build BuildContext conte
  • MPAndroidChart BarChart水平/垂直

    我正在使用MPAndroid图表 https github com PhilJay MPAndroidChart图书馆 在 BarChart 中 默认情况下所有条形都是垂直的 自下而上 如何将其水平显示 有两种类型条形图的方向 The no
  • 有没有办法在 JavaFX LineChart 中断开串联的 2 个点?

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

    我正在使用 MPchart 饼图 当我设置自定义图例数组时 它不接受该数组 我的代码 Legend l chart getLegend l setCustom ColorTemplate VORDIPLOM COLORS new Strin
  • 条形图/折线图 - 仅显示最后一个数据点的标签

    我无法获得条形图或折线图来显示 X 轴上的所有标签 正如您在提供的打印屏幕中看到的 只有最新的数据点显示其标签 这是使用场景生成器时的情况 我是否必须有一个带有用于 CategoriesAxis 的字符串的 ObservableList 我
  • 对数据进行分组并绘制多条线

    我想在 R 中为此数据集绘制多条线 x Year y Value School ID Year Value A 1998 5 B 1998 10 C 1999 15 A 2000 7 B 2005 15 每个学校都有不同年份的数据 我希望每
  • 重叠数据标签折线图高图

    How i can solve the problem of the values dataLabels below 我的 JSFiddle http jsfiddle net 3kVJS 3 http jsfiddle net 3kVJS
  • 如何使用 Mikephil 饼图从项目中删除小数位

    这是整数的静态值 private int upload 14 bill 15 unbill 85 total 100 unupload 12 sign 10 unsign 90 print 12 unprint 88 设置饼图条目 Arra

随机推荐

  • 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 作为参考 望大