Android 简单/简易折线图的实现

效果图:

  1. 显示数据
    在这里插入图片描述

  2. 隐藏数据
    在这里插入图片描述

具体实现

  1. **创建视图View:LineChart **

public class LineChart extends View {
    private Paint XPaint; // X轴画笔
    private Paint YPaint; // Y轴画笔
    private Paint pointPaint; // 点画笔
    private Paint circlePaint; // 圆画笔
    private Paint shapePaint; // 形状画笔
    private Paint effectPaint, effectPaint1; // 效果画笔
    private float yandianx, yuandiany, height, wigth; // 原点坐标、高度、宽度
    private Context context; // 上下文
    private String ysplit[]; // Y轴刻度分割值
    private String unitY; // 单位
    private String unitX; // 单位
    private float max; // 最大值
    private int textSize = 7; // 文字大小
    private int margin = 20; // 边距
    private float gao; // 高度比例
    private boolean start = false; // 是否开始绘制
    private List<Map<String, Float>> mapList; // 多组数据
    private List<Integer> colorList; // 每条折线所对应的颜色
    public boolean showPointText = true;

    public LineChart(Context context) {
        super(context);
        this.context = context;
    }

    public LineChart(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.context = context;
    }

    public LineChart(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.context = context;
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public LineChart(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        this.context = context;
    }

    // 初始化视图
    private void initView() {
        XPaint = new Paint(Paint.ANTI_ALIAS_FLAG); // X轴画笔
        XPaint.setAntiAlias(true);
        XPaint.setStrokeWidth(dp2px(1));
        YPaint = new Paint(Paint.ANTI_ALIAS_FLAG); // Y轴画笔
        YPaint.setAntiAlias(true);
        YPaint.setStrokeWidth(dp2px(2));
        /*
        * ANTI_ALIAS_FLAG:表示开启抗锯齿功能,让画出来的点更加平滑。
            setAntiAlias(true):再次开启抗锯齿功能,确保画出来的点更加平滑。
            setStyle(Paint.Style.STROKE):设置画笔的样式为描边,即只画出图形的轮廓线。
            setStrokeWidth(dp2px(1)):设置画笔的宽度为1dp,使用dp2px()方法将1dp转换为对应的像素值,确保在不同分辨率下画笔的宽度保持一致。*/
        pointPaint = new Paint(Paint.ANTI_ALIAS_FLAG); // 点画笔
        pointPaint.setAntiAlias(true);
        pointPaint.setStyle(Paint.Style.STROKE);//Paint.Style.FILL:填充整个图形内部。Paint.Style.FILL_AND_STROKE
        pointPaint.setStrokeWidth(dp2px(1));

        circlePaint = new Paint(Paint.ANTI_ALIAS_FLAG); // 创建一个圆画笔,设置抗锯齿标志
        circlePaint.setAntiAlias(true); // 开启画笔的抗锯齿功能
        circlePaint.setStyle(Paint.Style.FILL); // 设置画笔样式为填充
        circlePaint.setStrokeWidth(1); // 设置画笔宽度为1

        shapePaint = new Paint(Paint.ANTI_ALIAS_FLAG); // 创建一个形状画笔,设置抗锯齿标志
        shapePaint.setAntiAlias(true); // 开启画笔的抗锯齿功能
        shapePaint.setStyle(Paint.Style.FILL); // 设置画笔样式为填充
        shapePaint.setStrokeWidth(1); // 设置画笔宽度为1

        effectPaint = new Paint(Paint.ANTI_ALIAS_FLAG); // 创建一个效果画笔1,设置抗锯齿标志
        effectPaint.setAntiAlias(true); // 开启画笔的抗锯齿功能
        effectPaint.setStyle(Paint.Style.FILL); // 设置画笔样式为填充
        effectPaint.setStrokeWidth(1); // 设置画笔宽度为1
        effectPaint.setTextSize(sp2px(textSize)); // 设置画笔的文字大小,可以调整为其他值

        effectPaint1 = new Paint(Paint.ANTI_ALIAS_FLAG); // 创建一个效果画笔2,设置抗锯齿标志
        effectPaint1.setAntiAlias(true); // 开启画笔的抗锯齿功能
        effectPaint1.setStyle(Paint.Style.FILL); // 设置画笔样式为填充
        effectPaint1.setStrokeWidth(1); // 设置画笔宽度为1
        effectPaint1.setTextSize(sp2px(9)); // 设置画笔的文字大小,
    }

    @Override
    protected void onDraw(Canvas canvas) {
        if (start) {
            yandianx = dp2px(margin); // 原点x坐标
            yuandiany = getMeasuredHeight() - dp2px(margin); // 原点y坐标
            wigth = getMeasuredWidth() - dp2px(margin * 2); // 宽度
            height = getMeasuredHeight() - dp2px(margin * 2); // 高度

            // 绘制Y轴的线条和箭头
            float point[] = new float[]{yandianx, yuandiany, yandianx, yandianx, yandianx - dp2px(3), yandianx + dp2px(textSize / 2)};
            canvas.drawLines(point, 0, 4, YPaint);
            canvas.drawLines(point, 2, 4, YPaint);
            canvas.drawLine(yandianx, yandianx, yandianx + dp2px(3), yandianx + dp2px(textSize / 2), YPaint);

            // 绘制X轴的线条和箭头
            canvas.drawLine(yandianx, yuandiany, yandianx + wigth, yuandiany, YPaint);
            canvas.drawLine(yandianx + wigth, yuandiany, yandianx + wigth - dp2px(textSize / 2), yuandiany - dp2px(3), YPaint);
//            canvas.drawLine(yandianx + wigth, yuandiany, yandianx + wigth - dp2px(textSize / 2), yuandiany + dp2px(3), YPaint);

            // 绘制Y轴0刻度值
            canvas.drawText("0", yandianx - sp2px(textSize) - dp2px(2), yuandiany + sp2px(textSize) + dp2px(2), effectPaint);

            if (null != unitY) {
                // 绘制Y轴单位
                canvas.drawText(unitY, yandianx - sp2px(textSize) * unitY.length() / 2, yuandiany - height - dp2px(3), effectPaint);
            }
            if (null != unitX) {
                float xUnitTextWidth = sp2px(textSize) * unitX.length();
                // 绘制X轴单位
                canvas.drawText(unitX, yandianx + wigth + dp2px(5) - xUnitTextWidth / 2, yuandiany + sp2px(textSize) + dp2px(2), effectPaint);
            }


            if (null != ysplit && ysplit.length > 0) {
                gao = height / (ysplit.length + 1);
                // 绘制Y轴刻度线和刻度值
                for (int i = 0; i < ysplit.length; i++) {
                    float a = Float.parseFloat(ysplit[i]);
                    if (max < a) {
                        max = a;
                    }
//                    canvas.drawLine(yandianx, yuandiany - (i + 1) * gao, yandianx + dp2px(3), yuandiany - (i + 1) * gao, YPaint);
                    canvas.drawText(ysplit[i], yandianx - (sp2px(textSize) * (ysplit[i].length())), yuandiany - (i + 1) * gao + sp2px(textSize / 2), effectPaint);
                }
            }

            if (mapList.size() > 0) {
                float kuan = wigth / (mapList.get(0).size() + 1);
                for (int j = 0; j < mapList.size(); j++) {
                    // 设置点的文字
                    Map<String, Float> mapx = mapList.get(j);
                    Object o[] = mapx.keySet().toArray();//获取map里所有的key
                    XPaint.setColor(colorList.get(j)); // 设置折线的颜色
                    pointPaint.setColor(colorList.get(j));//设置 点颜色 为 线颜色
                    // 绘制X轴刻度线、刻度值和曲线
                    for (int i = 0; i < o.length; i++) {
                        String text = mapx.get(o[i]).toString();
                        if (text.equals("-1.0")) {
                            break;
                        }
                        String s = o[i].toString();//转换为字符串(日期)
                        float x = yandianx + (i + 1) * kuan;
                        float y = yuandiany - (height - gao) / max * mapx.get(o[i]);
                        //画X轴
                        canvas.drawLine(x, yuandiany, x, yuandiany - dp2px(3), YPaint);
                        //写X轴下的文字(修改距离中心的长度:x越大,离得越远)
                        canvas.drawText(s, x - (sp2px(textSize) * (s.length() / 4)), yuandiany + sp2px(textSize) + dp2px(3), effectPaint);

                        if (i > 0) {
                            canvas.drawLine(yandianx + i * kuan, yuandiany - (height - gao) / max * mapx.get(o[i - 1]), x, y, XPaint);
                        }
                        int size = dp2px(3);

                        // 绘制曲线上的形状(左与下 虚线)
//                        for (int i1 = 0; i1 < (x - yandianx) / size; i1++) {
//                            if (i1 % 2 == 0)
//                                canvas.drawLine(i1 * size + yandianx, y, (i1 + 1) * size + yandianx, y, shapePaint);
//                        }
//
//                        for (int i1 = 0; i1 < (yuandiany - y) / size; i1++) {
//                            if (i1 % 2 == 0)
//                                canvas.drawLine(x, yuandiany - i1 * size, x, yuandiany - (i1 + 1) * size, shapePaint);
//                        }

                        if (showPointText) {
                            // Y减值点的字体上移,X减左移
                            canvas.drawText(text, x - text.length() * sp2px(textSize / 4) + 10, y - dp2px(3) - 10, effectPaint1);
                        }

                    }

                    // 绘制曲线上的点
                    for (int i = 0; i < o.length; i++) {
                        String text = mapx.get(o[i]).toString();
                        if (text.equals("-1.0")) {
                            break;
                        }
                        float x = yandianx + (i + 1) * kuan;
                        float y = yuandiany - (height - gao) / max * mapx.get(o[i]);
                        canvas.drawCircle(x, y, dp2px((float) 1), circlePaint);
                        canvas.drawCircle(x, y, dp2px(1), pointPaint);
                    }

                }
            }
        }
    }

    public void startDraw(List<Map<String, Float>> mapList, String[] ysplit, String unitY,
                          String unitX, int margin, int textSize, List<Integer> colorList) {
        start = true;
        this.mapList = mapList;
        this.ysplit = ysplit;
        this.unitY = unitY;
        this.unitX = unitX;
        this.textSize = textSize;
        this.margin = margin;
        this.colorList = colorList;
        initView();
        // 修改点的颜色为红色
        circlePaint.setColor(Color.WHITE);
        invalidate();
    }

    /**
     * sp转换成px
     */
    private int sp2px(float spValue) {
        float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
        return (int) (spValue * fontScale + 0.5f);
    }

    /**
     * dp转换成px
     */
    private int dp2px(float dpValue) {
        float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }
}

  1. 创建lineChart的XML布局放进layout目录里,名称fragment_zt_fragment
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

<!--    注意,这里要是自己的包名-->
    <com.example.test.LineChart
        android:id="@+id/lineChart"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
  1. 封装进入Fragment,方便使用(创建Zt_fragment java类继承Fragment)

public class Zt_fragment extends Fragment {
    public List<Integer> colorList; // 每条折线所对应的颜色

    public List<Map<String, Float>> mapList;
    private boolean notXY;// LineChart对象用于显示线形图
//    private LineChartNotXY mLineChartNotXY;// LineChart对象用于显示线形图
    private LineChart mLineChart;
    private String unitY;
    private String unitX;
    private boolean showPointText = true;//默认不显示点文字

    public Zt_fragment(List<Map<String, Float>> mapList, boolean notXY, String unitY, String unitX) {
        this.mapList = mapList;
        this.notXY = notXY;
        this.unitY = unitY;
        this.unitX = unitX;
        colorList = fillColorList();
    }

    public Zt_fragment(List<Map<String, Float>> mapList, boolean notXY, String unitY, String unitX, boolean showPointText) {
        this.mapList = mapList;
        this.notXY = notXY;
        this.unitY = unitY;
        this.unitX = unitX;
        this.showPointText = showPointText;
        colorList = fillColorList();
    }

    private List<Integer> fillColorList() {
        List<Integer> colorList = new ArrayList<>();
        for (int i = 0; i < mapList.size(); i++) {
            //这里,如果是从自定义的文件color.xml里拿数据会失效,可能是局域限制的问题
            if (i == 0) {
                colorList.add(Color.rgb(64, 224, 208));
            } else if (i == 1) {
                colorList.add(Color.rgb(253, 83, 23));
            } else {//2个之后,颜色随机
                Random random = new Random();
                colorList.add(Color.rgb(random.nextInt(255), random.nextInt(255), random.nextInt(255)));
            }
        }
        return colorList;
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        // Inflate the layout for this fragmen
//        if (notXY)
        // return inflater.inflate(R.layout.fragment_zt_fragment_notxy, container, false); // 加载布局文件
//        else
        return inflater.inflate(R.layout.fragment_zt_fragment, container, false); // 加载布局文件
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        String[] y = new String[]{"20", "40", "60", "80"};

        if (notXY) {
//            mLineChartNotXY = getActivity().findViewById(R.id.lineChartNotXY); // 初始化LineChartNotXY对象
//            mLineChartNotXY.startDraw(mapList, y, 0, 0, colorList);
//            if (!showPointText) {// 不显示点文字
//                mLineChartNotXY.showPointText = false;
//            }
        } else {
            mLineChart = getActivity().findViewById(R.id.lineChart); // 初始化LineChart对象
            if (!showPointText) {// 不显示点文字
                mLineChart.showPointText = false;
            }
            mLineChart.startDraw(mapList, y, unitY, unitX, 30, 10, colorList);
        }

    }

}
  1. 具体使用
public class MainActivity extends AppCompatActivity{
        private final int LINE_NUM = 4;//自定义线条数量
        private Zt_fragment zt_fragment;
        private boolean hidePointTextFlag = true;
        private TextView hidePointText;
        List<Map<String, Float>> data = new ArrayList<>(); // 创建多组数据的List对象
    private String unitX="时间";

    @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            // 注:data变量的数据早自己填充,其中List的size表示有多少条线,
            // 里面的map的key为x轴数值,value为y轴的数值,一个键值对对应一个点,一个完整的map对应一条线
            Random random = new Random();
            for (int i = 0; i < 3; i++) {
                Map<String, Float> map = new HashMap<>();
                for (int j = 0; j < 9; j++) {
                    // 生成0-100之间的随机浮点数
                    float randomFloat = random.nextFloat() * 100;
                    // 创建键,这里假设键是"Key"加上当前的索引
                    String key = "Key" + j;
                    map.put(key, randomFloat);
                }
                data.add(map);
            }

            zt_fragment = showLine(data);
            hidePointText = (TextView) findViewById(R.id.hidePointText);
            findViewById(R.id.hidePointText).setOnClickListener(v -> {
                if (hidePointTextFlag) {
                    hidePointTextExace(true);
                    hidePointText.setText("显示\n数值");
                    hidePointTextFlag = false;
                } else {
                    hidePointTextExace(false);
                    hidePointText.setText("隐藏\n数值");
                    hidePointTextFlag = true;
                }
            });


        }

        /**
         * 折线图
         *
         * @param pointData 折线图的数据点
         * @return 折线图对象
         */
        private Zt_fragment showLine(List<Map<String, Float>> pointData) {

            // 创建折线图对象
            Zt_fragment mZt_fragment = new Zt_fragment(pointData, false, "患病率/%", unitX);

            // 将折线图对象添加到布局中
            FragmentTransaction fT = getSupportFragmentManager().beginTransaction();
            fT.replace(R.id.frameLayout, mZt_fragment).commit();

            // 返回折线图对象
            return mZt_fragment;
        }

        /**
         * 取消显示线
         *
         * @param zt_fragment 线的Fragment对象
         */
        private void cancelLine(Zt_fragment zt_fragment) {
            // 获取FragmentManager对象
            FragmentManager fragmentManager = getSupportFragmentManager();
            // 创建FragmentTransaction对象
            FragmentTransaction fT = fragmentManager.beginTransaction();
            // 从FragmentTransaction对象中移除线的Fragment对象
            fT.remove(zt_fragment);
            // 提交FragmentTransaction对象
            fT.commit();
        }

        private void hidePointTextExace(boolean b) {

            // 取消上一页的线
            cancelLine(zt_fragment);
            Zt_fragment mZt_fragmentTemp;
            if (b)
                mZt_fragmentTemp = new Zt_fragment(data, false, "患病率/%", unitX, false);
            else
                mZt_fragmentTemp = new Zt_fragment(data, false, "患病率/%", unitX);

            // 将折线图对象添加到布局中
            FragmentTransaction fT = getSupportFragmentManager().beginTransaction();
            fT.replace(R.id.frameLayout, mZt_fragmentTemp).commit();
            // 显示当前页的线
            zt_fragment = mZt_fragmentTemp;
        }
}


  1. ** activity对应的布局**
<?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"
    android:padding="2dp"
    tools:context=".MainActivity">
    <FrameLayout
        android:id="@+id/frameLayout"
        android:layout_width="match_parent"
        android:layout_height="300dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" >
        <TextView
            android:layout_marginRight="30sp"
            android:layout_marginTop="20sp"
            android:layout_gravity="top|right"
            android:padding="5dp"
            android:id="@+id/hidePointText"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="@drawable/radius_background04"
            android:text="隐藏\n数值"
            android:textColor="#E91E63"
            android:textSize="10sp" />
    </FrameLayout>
</LinearLayout>
  1. ** drawable资源,名称:radius_background04.xml**
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <corners android:radius="5dp"/>
    <solid android:color="#1FBB86FC"/>

</shape>

结束留言

这个东西的二次开发性蛮高的,可以根据自己的项目要求进行更改,另外通过取消线条然后再添加线条(数据跟随改变)还可以实现动态效果。
它也可以取消XY轴的显示,仅仅显示线条。由于这个使用的不多,我也仅用过一次,但有需要的可以自己研究,上面关于我写的部分不显示XY轴的代码还在,只是注释掉了。

最后,看到这条博客的友人,祝君所愿皆如愿,所求皆成,康乐无疆。

  • 9
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要在Android Studio中实现折线图,你可以使用开源图表库MPAndroidChart。下面是实现折线图的基本步骤: 1. 在项目的build.gradle文件中的dependencies块中添加以下依赖项: ```groovy implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0' ``` 2. 在你的布局文件中添加一个`LineChart`视图: ```xml <com.github.mikephil.charting.charts.LineChart android:id="@+id/chart" android:layout_width="match_parent" android:layout_height="match_parent" /> ``` 3. 在你的Activity或Fragment中,找到`LineChart`视图并对其进行配置和设置数据: ```java LineChart lineChart = findViewById(R.id.chart); // 创建数据集合 ArrayList<Entry> entries = new ArrayList<>(); entries.add(new Entry(0, 4)); entries.add(new Entry(1, 6)); entries.add(new Entry(2, 2)); // 添加更多数据... // 创建数据集 LineDataSet dataSet = new LineDataSet(entries, "折线图"); // 设置数据集的样式 dataSet.setColor(Color.BLUE); dataSet.setValueTextColor(Color.RED); // 创建数据集合的集合 ArrayList<ILineDataSet> dataSets = new ArrayList<>(); dataSets.add(dataSet); // 创建折线图数据对象 LineData lineData = new LineData(dataSets); // 配置折线图 lineChart.setData(lineData); lineChart.getDescription().setEnabled(false); lineChart.invalidate(); ``` 这里只是一个简单的示例,你可以根据自己的需求进行进一步的配置和定制。你可以通过修改数据集合、样式、坐标轴等来创建不同类型的折线图
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值