专栏首页木溪知识加油站MPAndroidChart 之实现底部显示的自定义MarkerView

MPAndroidChart 之实现底部显示的自定义MarkerView

接到一个需求需要折线图显示数据,权衡利弊后没有自己手绘哦,毕竟怕耽搁时间(或许也是怕写一半写不出来 哈哈哈),所以首选当然是之前接触过的MPAndroidChart,毕竟它很强很强很强。。。

添加依赖,之前用还是在eclipse时代的2.0:

 implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0'

GitHub网址:https://github.com/PhilJay/MPAndroidChart

首先上一下效果图,有相同需求的小伙伴可以借鉴的,那就往下看

linechart1.png

linechart2.png

需求如下:显示一个平滑的曲线,并且点击的时候要显示底部的一个小标标,选中的值还要改变选中的圆球颜色,并且蛋疼的还要第一次数据加载好就要显示出来,每次点击根据圆球位置显示marker三角形和球球。

我这里采用的是欺骗的手法,想着有marker可以显示数据,是不是也可以改动呢。小球球和三角形下标是静态不变大小的,而中间一根灰色竖立的小线是随球球坐标动态改变的长度,线根据需求可以设置颜色,骗过用户不就ok吗,透明的,红的,白的,绿的都行。下面看一下我的item_marker.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:gravity="center_horizontal"
    android:orientation="vertical">

    <ImageView
        android:id="@+id/iv_marker_top"
        android:layout_width="10dp"
        android:layout_height="10dp"
        android:layout_centerHorizontal="true"
        android:background="@drawable/shape_study_history_chart_circle" />

    <View
        android:id="@+id/line_marker_h"
        android:layout_width="1dp"
        android:layout_height="20dp"
        android:layout_below="@+id/iv_marker_top"
        android:layout_centerHorizontal="true"
        android:background="@color/base_text_color_light" />

    <TextView
        android:id="@+id/tv_marker_Content"
        android:layout_width="@dimen/dp_24"
        android:layout_height="@dimen/dp_16"
        android:layout_below="@+id/line_marker_h"
        android:layout_centerHorizontal="true"
        android:includeFontPadding="false"
        android:background="@drawable/ic_marker_bottom_san"
        android:textAppearance="?android:attr/textAppearanceSmall"
        android:textSize="12dp" />

</LinearLayout>

布局就是这么简单,三角形本想用drawable画的,没成功,劳烦善良的美工切了一个。 HistoryMarkerView第一版如下:

public class MyMarkerView extends MarkerView
{

    private TextView tvContent;
    private DecimalFormat format = new DecimalFormat("##0");

    public MyMarkerView(Context context) {
        super(context, R.layout.item_marker);//这个布局自己定义
        tvContent = (TextView) findViewById(R.id.tvContent);
    }

    //显示的内容
    @Override
    public void refreshContent(Entry e, Highlight highlight) {
        tvContent.setText(format(e.getX())+"\n"+format.format(e.getY())+"辆");
        super.refreshContent(e, highlight);
    }

    //标记相对于折线图的偏移量
    @Override
    public MPPointF getOffset() {
        return new MPPointF(-(getWidth() / 2), -getHeight());
    }

    //时间格式化(显示今日往前30天的每一天日期)
    public String  format(float x)
    {
        CharSequence format = DateFormat.format("MM月dd日",
                System.currentTimeMillis()-(long) (30-(int)x)*24*60*60*1000);
        return format.toString();
    }
}

第一版效果图,作为解说参考:

linechart3.png

很明显这段是我度娘来的,也是在这基础改进现在。

最终第二版:

/**
 * Author:jianbo
 * <p>
 * Create Time:2020/6/18 11:11
 * <p>
 * Email:muxi_bobo@163.com
 * <p>
 * Describe:图表的指示器
 */
public class HistoryMarkerView extends MarkerView {
    private TextView tvContent;
    private DecimalFormat format = new DecimalFormat("##0");
    private float lineheight;
    private View lineH;
    private Context mContext;
    private ImageView ivTopCircle;

    public HistoryMarkerView(Context context, float lineheight) {
        super(context, R.layout.item_marker);//这个布局自己定义
        this.lineheight = lineheight;
        this.mContext = context;
        tvContent = (TextView) findViewById(R.id.tv_marker_Content);
        lineH = (View) findViewById(R.id.line_marker_h);
        ivTopCircle = (ImageView) findViewById(R.id.iv_marker_top);
    }

    //显示的内容
    @Override
    public void refreshContent(Entry e, Highlight highlight) {
        LogUtils.e("-----lineX:" + lineheight + "===>", highlight.getX() + "\n" + highlight.getXPx() + "\n" + highlight.getDrawX());
        LogUtils.e("-----lineY:" + lineheight + "===>", highlight.getY() + "\n" + highlight.getYPx() + "\n" + highlight.getDrawY());
        LogUtils.e("-----lineHighlight:" + lineheight + "===>", "getYPx:" + highlight.getYPx() + "getDrawY:" + highlight.getDrawY() + "getHeight:" + getHeight());
        //实际细线高度
        float lineHight = lineheight - highlight.getYPx() - tvContent.getHeight() - ivTopCircle.getHeight() / 2;
        //为了效果,让细线高一点点
        LinearLayout.LayoutParams lineparams = new LinearLayout.LayoutParams(lineH.getWidth(), (int) lineHight + 2);
        lineH.setLayoutParams(lineparams);
        super.refreshContent(e, highlight);
    }

    //标记相对于折线图的偏移量
    @Override
    public MPPointF getOffset() {
   //偏移量(x,y),y的话又看到我xml布局中圆球球是10dp的,这里就网上偏移5dp也就是半径
        return new MPPointF(-(getWidth() / 2), -DisplayUtils.dip2px(mContext, 5));
    }

    private MPPointF mOffset2 = new MPPointF();

    @Override
    public MPPointF getOffsetForDrawingAtPoint(float posX, float posY) {

        MPPointF offset = getOffset();
        mOffset2.x = offset.x;
        mOffset2.y = offset.y;

        Chart chart = getChartView();

        float width = getWidth();
        float height = getHeight();

        if (posX + mOffset2.x < 0) {
            //第一个数据的时候,超出屏幕了,实现父类方法改一改
            mOffset2.x = offset.x;
        } else if (chart != null && posX + width + mOffset2.x > chart.getWidth()) {
            mOffset2.x = chart.getWidth() - posX - width;
        }

        if (posY + mOffset2.y < 0) {
            mOffset2.y = -posY;
        } else if (chart != null && posY + height + mOffset2.y > chart.getHeight()) {
            mOffset2.y = chart.getHeight() - posY - height;
        }

        return mOffset2;
    }
}

刚开始也不知道refreshContent(Entry e, Highlight highlight) ,getOffset() ,getOffsetForDrawingAtPoint(float posX, float posY) 这几个方法的用处那就打印,百度呗。 refreshContent:回调显示的时候会调用 getOffset:如方法名的意思就是偏移量 getOffsetForDrawingAtPoint:绘制的时候回调用,不是很清楚,之所以会改写它,也是应为在我点击第一个数据的时候,marker被他强制的往右偏了,无论我getOffset返回多少,所以就跟着getOffset进了源码,看看我的x偏移值在哪被人改了,就是在getOffsetForDrawingAtPoint判断后修改的原句是

       if (posX + mOffset2.x < 0) {
            mOffset2.x = - posX;------------------->>改动这里
        } else if (chart != null && posX + width + mOffset2.x > chart.getWidth()) {
            mOffset2.x = chart.getWidth() - posX - width;
        }

看到没,内部判断后没有用我传递给他的值,现在改回来,ok.

好了接下来看看LineChart完整的java控制代码和xml

布局
        <com.github.mikephil.charting.charts.LineChart
            android:id="@+id/lineChart"
            android:layout_width="match_parent"
            android:layout_height="220dp"
            android:padding="0dp"
            android:layout_centerInParent="true"
            android:background="#00000000" />
折线图实现
 //悬浮窗
    private HistoryMarkerView myMarkerView;
    //上一次的高亮线
    private Highlight[] highlightsOld = new Highlight[1];

    private void initLineChart() {
        List<Integer> listY = new ArrayList<>();
        for (int i = 0; i < 7; i++) {
            if (i % 2 == 0)
                listY.add(i);
            else
                listY.add((int) (i * 1.6));
        }

        //显示边界
        mLineChartStudy.setDrawBorders(false);
        mLineChartStudy.setDragEnabled(false);//:启用/禁用拖动(平移)图表。
        mLineChartStudy.setScaleEnabled(false);//:启用/禁用缩放图表上的两个轴。
        //设置数据
        List<Entry> yEntries = new ArrayList<>();
        for (int i = 0; i < listY.size(); i++) {
            yEntries.add(new Entry(i, (float) listY.get(i)));
        }
        //---------线条设置》》一个LineDataSet对象就是一条线
        LineDataSet lineDataSet = new LineDataSet(yEntries, "YYY");
        //线宽度
        lineDataSet.setLineWidth(1.5f);
        // 是否显示高亮的线
        lineDataSet.setHighlightEnabled(true);
        lineDataSet.setHighlightLineWidth(0.1f);
        lineDataSet.setHighLightColor(Color.parseColor("#00000000"));
        //线颜色
        lineDataSet.setColor(Color.parseColor("#ffffff"));
        //外圈半径
        lineDataSet.setCircleRadius(3f);
        //外圈的颜色
        lineDataSet.setCircleColor(Color.parseColor("#ffffff"));
        // 内圈的颜色
        lineDataSet.setCircleHoleColor(Color.parseColor("#ffffff"));
        //内圈半径
        lineDataSet.setCircleHoleRadius(1.5f);
        //不显示圆点
//        lineDataSet.setDrawCircles(false);
        //线条平滑,曲线
        lineDataSet.setMode(LineDataSet.Mode.CUBIC_BEZIER);
        //设置折线图填充
        lineDataSet.setDrawFilled(true);
        lineDataSet.setFillDrawable(getResources().getDrawable(R.drawable.shape_chart_filled_history_bg));
        LineData linedata = new LineData(lineDataSet);
        //无数据时显示的文字
        mLineChartStudy.setNoDataText("暂无数据");
        //折线图是否显示数值
        linedata.setDrawValues(true);

        //-----------------得到X轴----------------
        XAxis xAxis = mLineChartStudy.getXAxis();
        //设置X轴颜色
        xAxis.setAxisLineColor(Color.parseColor("#00000000"));
        //设置X轴的位置(默认在上方)
        xAxis.setPosition(XAxis.XAxisPosition.TOP);
        //设置X轴坐标之间的最小间隔
        xAxis.setGranularity(1f);
        //设置X轴的刻度数量,第二个参数为true,将会画出明确数量(带有小数点),但是可能值导致不均匀,默认(6,false)
        xAxis.setLabelCount(listY.size(), false);
        //设置X轴的值(最小值、最大值、然后会根据设置的刻度数量自动分配刻度显示)
//除非你的x轴显示不全还是别动它
//        xAxis.setAxisMinimum(1f);
////        //x轴刻度值
//        xAxis.setAxisMaximum((float) listY.size() - 1);
        //不显示网格线
        xAxis.setDrawGridLines(true);
        //x轴网格线颜色
        xAxis.setGridColor(Color.parseColor("#33FFFFFF"));
        //网格线宽
        xAxis.setGridLineWidth(0.4f);
        // 标签倾斜
//        xAxis.setLabelRotationAngle(45);
        xAxis.setTextColor(Color.parseColor("#88FFFFFF"));
//        xAxis.setTextSize(12f);
        //设置X轴值为字符串
        xAxis.setValueFormatter(new ValueFormatter() {
            @Override
            public String getFormattedValue(float value) {
                int IValue = (int) value;
                CharSequence format = DateFormat.format("M.dd",
                        System.currentTimeMillis() - (long) (listY.size() - 1 - IValue) * 24 * 60 * 60 * 1000);
                return format.toString();
            }
        });
        //-----------------得到Y轴-----------------
        YAxis yAxis = mLineChartStudy.getAxisLeft();
        YAxis rightYAxis = mLineChartStudy.getAxisRight();
        //设置Y轴是否显示
        //右侧Y轴不显示
        rightYAxis.setEnabled(false);
        //左侧Y轴不显示
        YAxis leftYAxis = mLineChartStudy.getAxisLeft();
        //设置左侧Y轴是否显示
        leftYAxis.setEnabled(false);
        //设置y轴坐标之间的最小间隔
        //不显示网格线
        yAxis.setDrawGridLines(false);
        //设置Y轴坐标之间的最小间隔
        yAxis.setGranularity(1);
        //图例:得到Lengend
        Legend legend = mLineChartStudy.getLegend();
        //隐藏Lengend,数据图标如:正方形,圆形等
        legend.setEnabled(false);
        //隐藏描述
        Description description = new Description();
        description.setEnabled(false);
        mLineChartStudy.setDescription(description);
        //设置数据
        mLineChartStudy.setData(linedata);
//        mLineChartStudy.setOnTouchListener(new ChartTouchListener(mLineChartStudy) {
//            @Override
//            public boolean onTouch(View v, MotionEvent event) {
//                return true;
//            }
//        });
//开启值转高亮线
        mLineChartStudy.valuesToHighlight();
        mLineChartStudy.post(new Runnable() {
            @Override
            public void run() {
                //折线图点的标记
                myMarkerView = new HistoryMarkerView(MyStudyHistoryActivity.this, mLineChartStudy.getHeight());
                mLineChartStudy.setMarker(myMarkerView);
                //通过触摸生成高亮线
//                Highlight h = mLineChartStudy.getHighlightByTouchPoint(mLineChartStudy.getCenter().x,mLineChartStudy.getCenter().y);
                Highlight h = mLineChartStudy.getHighlightByTouchPoint(mLineChartStudy.getRight(), mLineChartStudy.getTop());
                mLineChartStudy.highlightValue(h, true);
            }
        });
        //图标刷新
        mLineChartStudy.invalidate();
        mLineChartStudy.setOnChartValueSelectedListener(new OnChartValueSelectedListener() {
            @Override
            public void onValueSelected(Entry e, Highlight h) {
                CharSequence dateformat = DateFormat.format("MM月dd日 学习时长",
                        System.currentTimeMillis() - (long) (listY.size() - 1 - (int) e.getX()) * 24 * 60 * 60 * 1000);
                mTvStudyHistoryDate.setText(dateformat);
                mTvStudyHistoryTime.setText(e.getY() + "");
                highlightsOld[0] = h;
            }

            @Override
            public void onNothingSelected() {
                //高亮线非选中状态
                mLineChartStudy.highlightValues(highlightsOld);
            }
        });
    }

难得是蛋疼的数据加载完好就显示marker,因为他没有这个api方法。那我只能一点点跟源码喽。

linechart4.png

从mLineChartStudy.highlightValues(highlightsOld);开始跟,知道要先显示出来,除非要先给他一条高亮线,不然会显示的。

linechart5.png

linechart6.png

没办法既然如此那我就创建一条,那就要知道高亮线需要哪些属性值,找到这个实体类里面有很多x,y,mDrawX,mDrawY。。。

这些我从哪里知道,晕了。然后想到marker每次点击都能在setOnChartValueSelectedListener(new OnChartValueSelectedListener() )与refreshContent(Entry e, Highlight highlight) 拿到一个Highlight,那这个Highlight哪里来的呢。接着就跟着 OnChartValueSelectedListener的回调方法找到了

linechart7.png

linechart8.png

最后找到了下面两个方法,踏实了

linechart9.png

最后

linechart10.png

是不是完全踏实下来了,通过触摸拿到一个点,将点的x,y给他生成一个高亮的点。 于是想了一下,我点击折线图空白处的时候也是能够判断我点的最近的高亮点的,来显示高亮线(当然具体里面怎么实现判断的,我不深究了,已经够条件实现我要的需求了),我要显示最后一个数据点,也就是当天数据,那我把折线图view最右边的一个角的坐标值给他就完了,当然你也可以写最左边,中间值等。

linechart11.png

收工!!!

忘提了,之后看到它还可以设置每个圆圈圈的颜色,后面一想其实marker的圆圈圈也可以通过选择高亮后的回调监听动态重新给他们赋值不一样的颜色,来实现。

附上一些不错的借鉴文章:他的实现方式相对较优雅赶脚。。。 MPAndroidChart之LineChart(2)MarkerView

linechart12.gif

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • CoordinatorLayout打造折叠式的顶部标题栏

    如果以上简单操作无法满足你的需求,最后附上参考博客文章 Android开发之CoordinatorLayout打造滑动越界弹性放大图片效果 使用Coordi...

    紫兮木溪
  • [异常专栏]AAPT2error:checklogsfordetails解决方法(转载)

    转载地址http://blog.sina.com.cn/s/blog_5de73d0b0102yffd.html

    紫兮木溪
  • 自定义View学习——粗略实现商品图片内容描述

    前段时间从朋友那听说一个需求,实现一个商品展示并且添加商品的描述,闲暇时间试着自己实现了一下。 下面看一下效果图:

    紫兮木溪
  • Android Layput布局

    一个Android视图有很多控件,那么怎么来控制它们的位置排列呢?我们需要容器来存放这些控件并控制它们的位置排列,就像HTML中div,table一样,Andr...

    lzugis
  • android之WIFI小车编程详述

    有了前几篇wifi模块eps8266的使用,单片机设置eps8266程序,android TCP客户端,现在就做一个wifi小车 先上图 ? ? 小车是四个轮子...

    杨奉武
  • ImageButton与Button

    1.Button控件 Butotn控件,主要用来实现一些命令操作,通过注册监听事件来实现。首先需要在xml文档中放入一个button按钮。 1 <But...

    水击三千
  • 听说谷歌Baba更新了 Material UI ...

    Material Design,是谷歌在14年的IO大会上提出的一种新的理念,也被称为新的设计语言(也被称为“原材料设计”),称它为设计语言不为过,但是实际上,...

    HLQ_Struggle
  • 一线开发者本周复盘2

    clipOrientation 你可以认为是裁剪的对齐方向,这里设置为 vertical,说明我们想保留的是竖直方向。而我们上面的需求确实是这样的,两个色块,在...

    AndroidTraveler
  • A019-布局之GridLayout

    网格布局,是Android4.0之后的API才提供的,算是一个相对新的布局容器,它的用法也很简单,类似LinearLayout可以指定方向,也可以指定控件占用多...

    用户1130025
  • 很多人不知道还有这个——搜索框组件SearchView

    一、SearchView概述 SearchView是搜索框组件,它可以让用户在文本框内输入文字,并允许通过监听器监控用户输入,当用户输入完成后提交搜索时...

    分享达人秀

扫码关注云+社区

领取腾讯云代金券