美文网首页
MPAndroidChart绘制K线图(二)

MPAndroidChart绘制K线图(二)

作者: vachex | 来源:发表于2019-08-16 15:10 被阅读0次

    MPAndroidChart绘制K线图(一)高亮线自定义
    MPAndroidChart绘制K线图(二)动态时间格式+高亮时底部滑动时间刻度
    MPAndroidChart绘制K线图(三)长按高亮,双击事件,缩放中心点变换,图表联动,跨表缩放失效

    更新GitHub地址
    自定义股线图StockChart

    二、时间刻度

    1.动态的时间格式

    需求是这样的:底部的时间刻度会自动的根据时间跨度变化显示格式,比如60分钟的K线图,当双指缩放到可见范围较小时(小于2天)就显示HH:mm; 缩放至可见范围大于2天小于1年时,显示月日MM-dd; 缩放至大于1年的跨度时就显示年月yyyy-MM。而后端返回的时间有2种"yyyy-MM-dd HH:mm", "yyyy-MM-dd"(竟然不是时间戳。。。)

    初步分析:x轴刻度是由Chart的横轴XAxis通过ValueFormatter来确定,

    // ValueFormatter.java
      public String getFormattedValue(float value) {
            return String.valueOf(value);
        }
    

    getFormattedValue方法返回值即是要显示的内容,默认直接返回x轴的刻度value值,也就是数据源Entry中设置x值,需要重写该方法。同时在绘制刻度label前就需要确认页面内显示的范围,用以确认时间格式。于是需要在x轴渲染器XAxisRenderer渲染方法renderAxisLabels(Canvas c)中处理。

    由于ValueFormatter中是拿不到源数据和chart对象的,无法获取图表显示范围和时间字符串,需要从其他地方传过来,维护变量mDisplayTimeFormat来确定到底使用哪种格式来格式化时间,每次更新数据时重新new实例设置给XAxis即可,needUpdateValueRange表示图表范围变更了,这时候需要更新时间格式化的格式。代码如下

    // BarAXisValueFormatter类
    public class BarAXisValueFormatter extends ValueFormatter {
        private List<BarEntry> mBarEntries;
        private IValueFormatterCallback mCallback;
        //横轴显示的时间格式:3种
        // "HH:mm","MM-dd","yyyy-MM"
        public String mDisplayTimeFormat = Constant.TIME_SHARING_YY_MM;
    
        public BarAXisValueFormatter(List<BarEntry> entries, IValueFormatterCallback callback) {
            mBarEntries = entries;
            mCallback = callback;
        }
    
        @Override
        public String getFormattedValue(float value) {
            int index = (int) value;
            if (index >= mBarEntries.size()) {
                return "";
            }
            BarEntry barEntry = mBarEntries.get(index);
            Date time = ((KLineData) barEntry.getData()).getDate();
            // 拿到时间, 格式化为指定的字符串
            return new SimpleDateFormat(mDisplayTimeFormat).format(time);
        }
    
        public void needUpdateValueRange() {
            if (mCallback != null) {
                int highestVisibleX = (int) mCallback.getHighestVisibleX();
                int lowestVisibleX = (int) mCallback.getLowestVisibleX();
                // 根据可见范围确认当前时间格式
                mDisplayTimeFormat = getRangeTimeFormat(highestVisibleX, lowestVisibleX);
            }
        }
    
        // 根据可见范围计算对应的时间格式
        private String getRangeTimeFormat(int highestVisibleX, int lowestVisibleX) {
            if (lowestVisibleX < 0) {
                lowestVisibleX = 0;
            }
            if (highestVisibleX >= mBarEntries.size()) {
                highestVisibleX = mBarEntries.size() - 1;
            }
    
            Date dateMin = ((KLineData) mBarEntries.get(lowestVisibleX).getData()).getDate();
            Date dateMax = ((KLineData) mBarEntries.get(highestVisibleX).getData()).getDate();
            long diffTime = dateMax.getTime() - dateMin.getTime();
            String displayTimeFormat;
            if (diffTime < Constant.MILLI_SECOND_2_DAY) {
                displayTimeFormat = Constant.TIME_SHARING_HH_MM;
            } else if (diffTime < Constant.MILLI_SECOND_1_YEAR) {
                displayTimeFormat = Constant.TIME_SHARING_MM_DD;
            } else {
                displayTimeFormat = Constant.TIME_SHARING_YY_MM;
            }
            return displayTimeFormat;
        }
    
        public interface IValueFormatterCallback {
            float getHighestVisibleX();
    
            float getLowestVisibleX();
        }
    }
    
    //每次数据准备好,或者更新之后需要重新给x轴设置时间格式器(也可以和getHighestVisibleX方法一样用回调的方式,不用每次都重新创建实例了)
    XAxis barXAxis = mBarChart.getXAxis();
    barXAxis.setValueFormatter(new BarAXisValueFormatter(barEntries, this));
    
    // 更新数据的类实现了回调接口, 用chart的api来获取可见最大最小值
       @Override
        public float getHighestVisibleX() {
            return mBarChart.getHighestVisibleX();
        }
    
        @Override
        public float getLowestVisibleX() {
            return mBarChart.getLowestVisibleX();
        }
    

    BarAXisValueFormatter类中needUpdateValueRange是在哪里调用呢,当然是每次渲染坐标轴之前了,简单写个XAxisRenderer的子类,重写renderAxisLabels方法,在super.renderAxisLabels(c)之前拿到valueFormatter让其更新范围即可,chart初始化时实例化BarXAxisRenderer设置给chart。

    public class BarXAxisRenderer extends XAxisRenderer {
        public BarXAxisRenderer(ViewPortHandler viewPortHandler, XAxis xAxis, Transformer trans) {
            super(viewPortHandler, xAxis, trans);
        }
    
        @Override
        public void renderAxisLabels(Canvas c) {
            ValueFormatter valueFormatter = mXAxis.getValueFormatter();
            if (valueFormatter instanceof BarAXisValueFormatter) {
                ((BarAXisValueFormatter) valueFormatter).needUpdateValueRange();
            }
            super.renderAxisLabels(c);
        }
    }
    
     BarXAxisRenderer xAxisRenderer = new BarXAxisRenderer(mViewPortHandler, mXAxis, mLeftAxisTransformer);
     setXAxisRenderer(xAxisRenderer);
    
    2.高亮时底部滑动时间刻度

    需求如下图,高亮时底部会显示一个时间来覆盖时间刻度,会随着高亮线变化显示在不同的位置。


    image.png

    这里能想到的有2个思路,一个就是绘制高亮线的drawHighlighted()方法中,同时在底部绘制日期文本,但是一看前面一节分析就知道,drawHighlighted方法是在被剪裁过的区域内执行的,Canvas不包含底部刻度区域了,放弃。 其二就是绘制底部刻度时,获取高亮值来计算绘制,既能拿到轴线刻度的paint,位置,又有高亮值,再合适不过了。还是在BarXAxisRenderer中处理, 这个类变成了这样子:

    public class BarXAxisRenderer extends XAxisRenderer {
        private Paint mMarkLabelPaint;
        private IXAxisRendererCallback mCallback;
        private MPPointF mPointF;
    
        public BarXAxisRenderer(ViewPortHandler viewPortHandler, XAxis xAxis, Transformer trans) {
            super(viewPortHandler, xAxis, trans);
        }
    
        public BarXAxisRenderer(ViewPortHandler viewPortHandler, XAxis xAxis, Transformer trans, IXAxisRendererCallback callback) {
            super(viewPortHandler, xAxis, trans);
            mCallback = callback;
        }
    
        public void drawMarkLabels(Canvas c) {
            if (mCallback != null) {
                Highlight highlighted = mCallback.getHighlightDef();
                // callback其实就是chart, chart中提供了api :getHighlighted() ,getHeight(),只需要我们实现getDateForHighlight拿到标签显示文本即可
                if (highlighted == null) {
                    return;
                }
                String text = mCallback.getDateForHighlight(highlighted);
                if (TextUtils.isEmpty(text)) {
                    return;
                }
                float drawX = highlighted.getDrawX();
                float labelY = mViewPortHandler.contentBottom();
                Paint markPaint = getMarkLabelPaint();
                markPaint.setColor(ResourceUtils.getThemeColorReverse());
                float width = markPaint.measureText(text);
                Paint paint = new Paint();
                paint.setColor(ResourceUtils.getThemeColor());
                paint.setStyle(Paint.Style.FILL);
                c.drawRect(drawX - width / 2, labelY + 1, drawX + width / 2, mCallback.getHeight(), paint);
                // Utils.drawXAxisValue是MPAndroidChart的API,而pointF是定值调用不到,在这里也copy了一份
                MPPointF pointF = getMPPointF();
                Utils.drawXAxisValue(c, text, drawX, labelY + mAxis.getYOffset(), markPaint, pointF, mXAxis.getLabelRotationAngle());
            }
        }
    
        private MPPointF getMPPointF() {
            if (mPointF == null) {
                mPointF = MPPointF.getInstance(0, 0);
                mPointF.x = 0.5f;
                mPointF.y = 0.0f;
            }
            return mPointF;
        }
    
        private Paint getMarkLabelPaint() {
            if (mMarkLabelPaint == null) {
                mMarkLabelPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
                mMarkLabelPaint.setTextSize(mAxisLabelPaint.getTextSize());
                mMarkLabelPaint.setTextAlign(Paint.Align.CENTER);
            }
            return mMarkLabelPaint;
        }
    
        @Override
        public void renderAxisLabels(Canvas c) {
            ValueFormatter valueFormatter = mXAxis.getValueFormatter();
            if (valueFormatter instanceof BarAXisValueFormatter) {
                ((BarAXisValueFormatter) valueFormatter).needUpdateValueRange();
            }
            super.renderAxisLabels(c);
            // 绘制高亮标签
            drawMarkLabels(c);
        }
    
        public interface IXAxisRendererCallback {
            // 这里只是使用简单的图表的话直接使用chart的getHighlighted()是可以的,但是后续需求原因,自己又维护了一个highlightDef。
            Highlight getHighlightDef();
    
            int getHeight();
    
            String getDateForHighlight(Highlight highlight);
        }
    }
    

    接下来是这个标签文本怎么获取, 刚刚定义了标签格式器BarAXisValueFormatter,当然是从它那里取了。

      @Override
        public String getDateForHighlight(Highlight highlight) {
            // Highlight中包含高亮点的xy位置信息
            if (mhighlighted != null) {
                IAxisValueFormatter valueFormatter = mXAxis.getValueFormatter();
                if (valueFormatter instanceof BarAXisValueFormatter) {
          // 通过Highlight索引可以计算出对应点的数据对象,(mHighlightData是我在后续的同步高亮时单独维护的对象,目的是一样的,从data中拿到时间信息)
                    return ((BarAXisValueFormatter) valueFormatter).formatLabelTime((int) mhighlighted.getX(), mHighlightData);
                }
            }
            return "";
        }
    
        // 格式器中需要显示什么格式的文本就对应返回即可
        public String formatLabelTime(int index, @NonNull KLineData kline) {
            String formatTime = "";
            Date date = kline.getDate();
            if (!TextUtils.isEmpty(kline.getTime())) {
                switch (mDisplayTimeFormat) {
                    case Constant.TIME_SHARING_YY_MM:
                        formatTime = FormatUtils.changedDateFormat(date, Constant.SOURCE_TIME_STRING[1]);
                        break;
                    case Constant.TIME_SHARING_HH_MM:
                        formatTime = FormatUtils.changedDateFormat(date, Constant.TIME_LABEL_MARK_TIME);
                        break;
                    case Constant.TIME_SHARING_MM_DD:
                        int formatDateType = FormatUtils.getFormatDateType(kline.getTime());
                        formatTime = FormatUtils.changedDateFormat(date, formatDateType == 1 ? Constant.SOURCE_TIME_STRING[1] : Constant.TIME_LABEL_MARK_TIME);
                        break;
                }
            }
            // 计算不出来直接使用x刻度值。。
            if (TextUtils.isEmpty(formatTime)) {
                formatTime = getFormattedValue(index);
            }
            return formatTime;
        }
    

    (写得比较简单,估计使用过MPAndroidChart的童鞋才能知道我写的啥吧-。-, 后面有空了我将源码整理开源出来吧。)

    相关文章

      网友评论

          本文标题:MPAndroidChart绘制K线图(二)

          本文链接:https://www.haomeiwen.com/subject/yjlfsctx.html