使用MPAndroidChart实现K线图(4)——图表联动、加载更多

目录

使用MPAndroidChart实现K线图(1)——基本用法

使用MPAndroidChart实现K线图(2)——自定义XY轴

使用MPAndroidChart实现K线图(3)——自定义柱状图

使用MPAndroidChart实现K线图(4)——图表联动、加载更多

使用MPAndroidChart实现K线图(5)——高亮联动、横竖屏切换


首先说一下图表联动和加载更多流程逻辑。图表联动是指,当滑动上部分的K线图时,成交量图会跟随滑动;当滑动成交量图时,K线图会跟随滑动。而显示和加载的逻辑相对复杂一点,默认情况下,K线左右边缘的两个只会显示一半,数据的时间是从左向右的,右侧数据的时间比左侧数据的时间更新,也就是从右向左滑可以滑到没有数据,而从左向右滑可以有足够多的数据,因此始终使最右端数据显示完整,最左端不考虑。在设置完数据后,再给Chart的X轴设置最大值即可使最右端显示完整。

        float xMax = xValues.size() - 0.5F;//默认X轴最大值是 xValues.size() - 1
        cc.getXAxis().setAxisMaximum(xMax);//使最后一个显示完整
        
        bc.getXAxis().setAxisMaximum(xMax + barOffset);//保持边缘对齐

接着往下,初次获取数据后,设置好图表后,把图表平移到最右端,显示最新数据;滑动到边缘加载更多后,如果加载的是右侧的数据,则平移到最右端,如果加载的是左侧的数据,则平移到加载之前的位置。加载更多后的图表绘制,最初的想法是追加到图表的原有数据上(因为有向左追加的缘故,X的值会取负数且越来越小),但经过试验,发现向右追加有效,但是向左追加后不会接着绘制(想不明白这里的原因)。最后的实现方式是,把原始数据存放在dataList中,有新数据就插入进去,每次获取数据后,不论是初次获取,还是追加加载,都对图表进行清空数据并重绘,绘制后平移到对应的位置。

 

自定义手势监听器OnChartGestureListener

联动滑动时会回调图表手势监听,因此要自定义OnChartGestureListener,命名为CoupleChartGestureListener

public class CoupleChartGestureListener implements OnChartGestureListener {

    private BarLineChartBase srcChart;
    private Chart[] dstCharts;

    private OnEdgeListener edgeListener;//滑动到边缘的监听器
    private boolean isLoadMore;//是否加载更多
    private boolean canLoad;//K线图手指交互已停止,正在惯性滑动

    public CoupleChartGestureListener(BarLineChartBase srcChart, Chart... dstCharts) {
        this.srcChart = srcChart;
        this.dstCharts = dstCharts;
        isLoadMore = false;
    }

    public CoupleChartGestureListener(OnEdgeListener edgeListener, BarLineChartBase srcChart,
                                      Chart... dstCharts) {
        this.edgeListener = edgeListener;
        this.srcChart = srcChart;
        this.dstCharts = dstCharts;
        isLoadMore = true;
    }

    @Override
    public void onChartGestureStart(MotionEvent me, ChartTouchListener.ChartGesture lastPerformedGesture) {
        canLoad = false;
        syncCharts();
        chartGestureStart(me, lastPerformedGesture);
    }

    @Override
    public void onChartGestureEnd(MotionEvent me, ChartTouchListener.ChartGesture lastPerformedGesture) {
        if (isLoadMore) {
            float leftX = srcChart.getLowestVisibleX();
            float rightX = srcChart.getHighestVisibleX();
            if (leftX == srcChart.getXChartMin()) {//滑到最左端
                canLoad = false;
                if (edgeListener != null) {
                    edgeListener.edgeLoad(leftX, true);
                }
            } else if (rightX == srcChart.getXChartMax()) {//滑到最右端
                canLoad = false;
                if (edgeListener != null) {
                    edgeListener.edgeLoad(rightX, false);
                }
            } else {
                canLoad = true;
            }
        }
        syncCharts();
        chartGestureEnd(me, lastPerformedGesture);
    }

    @Override
    public void onChartLongPressed(MotionEvent me) {
        syncCharts();
        chartLongPressed(me);
    }

    @Override
    public void onChartDoubleTapped(MotionEvent me) {
        syncCharts();
        chartDoubleTapped(me);
    }

    @Override
    public void onChartSingleTapped(MotionEvent me) {
        syncCharts();
        chartSingleTapped(me);
    }

    @Override
    public void onChartFling(MotionEvent me1, MotionEvent me2, float velocityX, float velocityY) {
        syncCharts();
    }

    @Override
    public void onChartScale(MotionEvent me, float scaleX, float scaleY) {
        syncCharts();
    }

    /**
     * 由于在外部设置了禁止惯性甩动(因为和Chart的move方法有冲突),
     * if中的语句实际上不会执行(整个手势交互结束后,最后回调的方法是onChartGestureEnd,而不是onChartTranslate),
     * 这样写是为了统一允许惯性甩动的情况
     */
    @Override
    public void onChartTranslate(MotionEvent me, float dX, float dY) {
        if (canLoad) {
            float leftX = srcChart.getLowestVisibleX();
            float rightX = srcChart.getHighestVisibleX();
            if (leftX == srcChart.getXChartMin()) {//滑到最左端
                canLoad = false;
                if (edgeListener != null) {
                    edgeListener.edgeLoad(leftX, true);
                }
            } else if (rightX == srcChart.getXChartMax()) {//滑到最右端
                canLoad = false;
                if (edgeListener != null) {
                    edgeListener.edgeLoad(rightX, false);
                }
            }
        }
        syncCharts();
        chartTranslate(me, dX, dY);
    }

    //以下6个方法仅为了:方便在外部根据需要自行重写
    public void chartGestureStart(MotionEvent me, ChartTouchListener.ChartGesture lastPerformedGesture) {}
    public void chartGestureEnd(MotionEvent me, ChartTouchListener.ChartGesture lastPerformedGesture) {}
    public void chartLongPressed(MotionEvent me) {}
    public void chartDoubleTapped(MotionEvent me) {}
    public void chartSingleTapped(MotionEvent me) {}
    public void chartTranslate(MotionEvent me, float dX, float dY) {}

    private void syncCharts() {
        Matrix srcMatrix;
        float[] srcVals = new float[9];
        Matrix dstMatrix;
        float[] dstVals = new float[9];
        // get src chart translation matrix:
        srcMatrix = srcChart.getViewPortHandler().getMatrixTouch();
        srcMatrix.getValues(srcVals);
        // apply X axis scaling and position to dst charts:
        for (Chart dstChart : dstCharts) {
            dstMatrix = dstChart.getViewPortHandler().getMatrixTouch();
            dstMatrix.getValues(dstVals);

            dstVals[Matrix.MSCALE_X] = srcVals[Matrix.MSCALE_X];
            dstVals[Matrix.MSKEW_X] = srcVals[Matrix.MSKEW_X];
            dstVals[Matrix.MTRANS_X] = srcVals[Matrix.MTRANS_X];
            dstVals[Matrix.MSKEW_Y] = srcVals[Matrix.MSKEW_Y];
            dstVals[Matrix.MSCALE_Y] = srcVals[Matrix.MSCALE_Y];
            dstVals[Matrix.MTRANS_Y] = srcVals[Matrix.MTRANS_Y];
            dstVals[Matrix.MPERSP_0] = srcVals[Matrix.MPERSP_0];
            dstVals[Matrix.MPERSP_1] = srcVals[Matrix.MPERSP_1];
            dstVals[Matrix.MPERSP_2] = srcVals[Matrix.MPERSP_2];

            dstMatrix.setValues(dstVals);
            dstChart.getViewPortHandler().refresh(dstMatrix, dstChart, true);
        }
    }

    public interface OnEdgeListener {
        void edgeLoad(float x, boolean left);
    }
}

给CombinedChart和BarChart设置手势监听,并实现CoupleChartGestureListener.OnEdgeListener接口,在回调时请求数据加载更多。

    private CoupleChartGestureListener ccGesture;
    private CoupleChartGestureListener bcGesture;
    private int[] KL_INTERVAL = {1, 5, 15, 30, 60, 1440};//单位: Min
    private final long M1 = 60 * 1000L;//1 Min的毫秒数

    ccGesture = new CoupleChartGestureListener(this, cc, bc);//设置成全局变量,后续要用到
    cc.setOnChartGestureListener(ccGesture);//设置手势联动监听
    bcGesture = new CoupleChartGestureListener(this, bc, cc);
    bc.setOnChartGestureListener(bcGesture);

    /**
     * 滑动到边缘后加载更多
     */
    @Override
    public void edgeLoad(float x, boolean left) {
        int v = (int) x;
        if (!left && !xValues.containsKey(v) && xValues.containsKey(v - 1)) {
            v = v - 1;
        }
        String time = xValues.get(v);
        if (!TextUtils.isEmpty(time)) {
            try {
                long t = sdf.parse(time).getTime();
                if (!left) {//向右获取数据时判断时间间隔
                    long interval = KL_INTERVAL[tabLayout.getSelectedTabPosition()] * M1;
                    if (System.currentTimeMillis() - t < interval) {//不会有新数据
                        return;
                    }
                }
                loadingDialog = LoadingDialog.newInstance();
                loadingDialog.show(this);
                toLeft = left;
                getData(t * 1000000L + "");
            } catch (ParseException e) {
                e.printStackTrace();
            }
        }
    }

这样就实现了滑动联动,以及滑动边缘加载更多。以下是带有时间间隔的效果图:

使用MPAndroidChart实现K线图(4)——图表联动、加载更多_第1张图片

 当时间间隔为1m时,K线图也就成了分时图,取每分钟的收盘价来绘制分时图,此时不再绘制蜡烛图和均线图。

在初始化图表时,初始化分时线的LineDataSet:

    private LineDataSet lineSetMin;//分时线

    //在初始化图表方法initChart()中新增分时线的初始化
    lineSetMin = new LineDataSet(new ArrayList(), "Minutes");
    lineSetMin.setAxisDependency(YAxis.AxisDependency.LEFT);
    lineSetMin.setColor(Color.WHITE);
    lineSetMin.setDrawCircles(false);
    lineSetMin.setDrawValues(false);
    lineSetMin.setDrawFilled(true);
    lineSetMin.setHighlightEnabled(false);
    lineSetMin.setFillColor(gray);
    lineSetMin.setFillAlpha(60);

配置数据方法也修改如下:

    /**
     * size是指追加数据之前,已有的数据个数
     */
    private void handleData(List> lists, int size) {
        if (toLeft) {
            dataList.addAll(0, lists);//添加到左侧
        } else {
            dataList.addAll(lists);
        }

        configData();
        if (xValues.size() > 0) {
            int x = xValues.size() - (toLeft ? size : 0);
            //如果设置了惯性甩动 move方法将会无效
            if (!toLeft && size > 0) {
                cc.moveViewToAnimated(x, 0, YAxis.AxisDependency.LEFT, 200);
                bc.moveViewToAnimated(x + barOffset, 0, YAxis.AxisDependency.LEFT, 200);
            } else {
                cc.moveViewToX(x);
                bc.moveViewToX(x + barOffset);
            }
            cc.notifyDataSetChanged();
            bc.notifyDataSetChanged();
        }
    }

    private void configData() {
        if (dataList.size() == 0) {
            cc.setNoDataText("暂无相关数据");
            cc.clear();
            bc.setNoDataText("暂无相关数据");
            bc.clear();
        } else {
            if (combinedData == null) {
                combinedData = new CombinedData();
            }
            xValues.clear();
            List candleValues = candleSet.getValues();
            candleValues.clear();
            List ma5Values = lineSet5.getValues();
            ma5Values.clear();
            List ma10Values = lineSet10.getValues();
            ma10Values.clear();
            List minValues = lineSetMin.getValues();
            minValues.clear();
            List barValues = barSet.getValues();
            barValues.clear();
            for (int i = 0; i < dataList.size(); i++) {
                List k = dataList.get(i);
                Date d = new Date(Long.parseLong(k.get(6)) * 1000);//毫秒
                String x = sdf.format(d);//显示日期
                if (xValues.containsValue(x)) {//x重复
                    dataList.remove(i);
                    i--;
                } else {
                    xValues.put(i, x);
                    float open = Float.parseFloat(k.get(4));
                    float close = Float.parseFloat(k.get(1));
                    candleValues.add(new CandleEntry(i, Float.parseFloat(k.get(2)),
                            Float.parseFloat(k.get(3)), open, close));
                    minValues.add(new Entry(i, close));
                    barValues.add(new BarEntry(i, Float.parseFloat(k.get(8)), close >= open ? 0 : 1));
                    if (i >=4) {
                        ma5Values.add(new Entry(i, getMA(i, 5)));
                        if (i >= 9) {
                            ma10Values.add(new Entry(i, getMA(i, 10)));
                        }
                    }
                }
            }
            candleSet.setValues(candleValues);
            lineSet5.setValues(ma5Values);
            lineSet10.setValues(ma10Values);
            lineSetMin.setValues(minValues);
            if (tabLayout.getSelectedTabPosition() == 0) {
                combinedData.removeDataSet(candleSet);//分时图时移除蜡烛图
                combinedData.setData(new LineData(lineSetMin));
            } else {
                combinedData.setData(new CandleData(candleSet));
                combinedData.setData(new LineData(lineSet5, lineSet10));
            }

            cc.setData(combinedData);
            float xMax = xValues.size() - 0.5F;//默认X轴最大值是 xValues.size() - 1
            cc.getXAxis().setAxisMaximum(xMax);//使最后一个显示完整

            barSet.setValues(barValues);
            BarData barData = new BarData(barSet);
            barData.setBarWidth(1 - candleSet.getBarSpace() * 2);//使Candle和Bar宽度一致
            bc.setData(barData);
            bc.getXAxis().setAxisMaximum(xMax + barOffset);//保持边缘对齐

            cc.setVisibleXRange(range, range);//设置显示X轴个数的上下限,竖屏固定52个
            bc.setVisibleXRange(range, range);
        }
    }

配置数据之后,执行开头提到的显示逻辑,即向右加载更多时,图表移动到最右端,向左加载更多时,图表移动到加载之前的位置。因为每次加载都是重新给图表设置数据,加载之前的位置(即最左端)是0,而加载之后左端被填充了(xValues.size() -  size),因此需要从0(每次设置数据后都是在0)移动到(xValues.size() -  size)。图表移动后调用notifyDataSetChanged()方法,可以避免图表闪动的问题。

最后,监听TabLayout的选中变化,每次选中(以及重复选中时)都重新加载。以下是分时图效果:

使用MPAndroidChart实现K线图(4)——图表联动、加载更多_第2张图片

 不同时间间隔显示图表不同的效果已经实现。

下一步要做的是:

长按触发高亮,以及高亮效果、横竖屏效果。

 

完整demo地址:https://github.com/ShallowBillow/KlineDemo

你可能感兴趣的:(MPAndroidChart)