MPAndroidChart绘制K线图(三)

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

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

三、触控事件处理
1.长按高亮,双击监听

只需要监听Chart的触控事件,处理长按事件和双击事件即可;触控事件是优先处理OnTouchListener的,所以自定义OnTouchListener设置给Chart。

public class FingerTouchListener implements View.OnTouchListener {
    private BarLineChartBase mChart;
    private GestureDetector mDetector;
    private TouchCallback mListener;
    private boolean mIsLongPress = false;

    public FingerTouchListener(BarLineChartBase chart, TouchCallback listener) {
        mChart = chart;
        mListener = listener;
//   android自带的GestureDetector可以满足双击和长按监听
        mDetector = new GestureDetector(mChart.getContext(), new GestureDetector.SimpleOnGestureListener() {
            @Override
            public void onLongPress(MotionEvent e) {
                super.onLongPress(e);
                mIsLongPress = true;
                if (mListener != null) {
                    mListener.enableHighlight();
                }
                Highlight h = mChart.getHighlightByTouchPoint(e.getX(), e.getY());
                if (h != null && h.getDataIndex() >= 0) {
                    h.setDraw(e.getX(), e.getY());
//   长按时触发该chart高亮
                    mChart.highlightValue(h, true);
                    mChart.disableScroll();
                }
            }

            @Override
            public boolean onDoubleTap(MotionEvent e) {
                if (mListener != null) {
//  双击事件回调
                  mListener.onDoubleTap();
                }
                return super.onDoubleTap(e);
            }
        });
    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {
//  优先使用mDetector处理onTouchEvent
        mDetector.onTouchEvent(event);
        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            mIsLongPress = false;
        }
        if (event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_CANCEL) {
            mIsLongPress = false;
//  长按事件结束了,需要取消高亮
            mChart.highlightValue(null, true);
            if (mListener != null) {
                mListener.disableHighlight();
            }
            mChart.enableScroll();
        }
        if (mIsLongPress && event.getAction() == MotionEvent.ACTION_MOVE) {
            if (mListener != null) {
                mListener.enableHighlight();
            }
            Highlight h = mChart.getHighlightByTouchPoint(event.getX(), event.getY());
            if (h != null && h.getDataIndex() >= 0) {
                h.setDraw(event.getX(), event.getY());
                mChart.highlightValue(h, true);
                mChart.disableScroll();
            }
            return true;
        }
        return false;
    }

    public interface TouchCallback {
        void enableHighlight();

        void disableHighlight();

        void onDoubleTap();
    }
}

注意里面的 mChart.highlightValue(h, true)的第二个参数,代表是否需要回调高亮监听,如果设置false,那么Chart中设置的setOnChartValueSelectedListener就不会调用(后面说到高亮同步时会说到),这里需要设置为true。

2.基于y轴缩放和基于触控点缩放

默认情况下双指缩放时是基于双指中心点缩放,但是需求希望:当数据较少,不满一屏幕时,双指缩放基于左侧y轴。查看源码看了一下其缩放策略,在BarLineChartTouchListener中

     if (event.getPointerCount() >= 2) { // two finger zoom
                ......
                // 通过这个getTrans将触控点转换为缩放中心点
                MPPointF t = getTrans(mTouchPointCenter.x, mTouchPointCenter.y);
                ViewPortHandler h = mChart.getViewPortHandler();
                ......
                    if (canZoomMoreY || canZoomMoreX) {

                        mMatrix.set(mSavedMatrix);
                        mMatrix.postScale(scaleX, scaleY, t.x, t.y);
                ......

// 查看getTrans方法
    public MPPointF getTrans(float x, float y) {

        ViewPortHandler vph = mChart.getViewPortHandler();

        float xTrans = x - vph.offsetLeft();
        float yTrans = 0f;

        // check if axis is inverted
        if (inverted()) {
            yTrans = -(y - vph.offsetTop());
        } else {
            yTrans = -(mChart.getMeasuredHeight() - y - vph.offsetBottom());
        }

        return MPPointF.getInstance(xTrans, yTrans);
    }

确认确实是getTrans方法中处理的,要实现基于y轴缩放只需要将getTrans中的x值置为0即可,同理需要基于右侧y轴,将x值强制设置为最大值。这里需要继承BarLineChartTouchListener,重写的getTrans方法,然后在初始化时实例化设置给chart。

    @Override
    public MPPointF getTrans(float x, float y) {
        if (mListener != null) {
            int transEdge = mListener.getTransEdge();
            if (transEdge == TRANS_EDGE_LEFT) {
                x = 0;
            } else if (transEdge == TRANS_EDGE_RIGHT) {
                x = mChart.getWidth();
            }
        }
        return super.getTrans(x, y);
    }
3.图表联动

chart提供了public void setOnChartGestureListener(OnChartGestureListener l)用于监听各种手势事件(缩放 平移 触摸开始 触摸结束 长按 双击 单击 滑动等),同步两个chart只需要监听chart的缩放平移,同步设置给另外一张图表。

public class SyncChartGestureListener implements OnChartGestureListener {
    private Chart srcChart;
    private Chart[] dstCharts;

    public SyncChartGestureListener(Chart srcChart, Chart[] dstCharts) {
        this.srcChart = srcChart;
        this.dstCharts = dstCharts;
    }

    @Override
    public void onChartGestureStart(MotionEvent me, ChartTouchListener.ChartGesture lastPerformedGesture) { }

    @Override
    public void onChartGestureEnd(MotionEvent me, ChartTouchListener.ChartGesture lastPerformedGesture) {}

    @Override
    public void onChartLongPressed(MotionEvent me) {}

    @Override
    public void onChartDoubleTapped(MotionEvent me) {}

    @Override
    public void onChartSingleTapped(MotionEvent me) {}

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

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

    @Override
    public void onChartTranslate(MotionEvent me, float dX, float dY) {
        syncCharts();
    }

    /**
     * 同步目标chart和当前chart的显示效果
     */
    public void syncCharts() {
        Matrix srcMatrix;
        float[] srcVals = new float[9];
        Matrix dstMatrix;
        float[] dstVals = new float[9];

        srcMatrix = srcChart.getViewPortHandler().getMatrixTouch();
        srcMatrix.getValues(srcVals);

        for (Chart dstChart : dstCharts) {
            if (dstChart.getVisibility() == View.VISIBLE) {
                dstMatrix = dstChart.getViewPortHandler().getMatrixTouch();
                dstMatrix.getValues(dstVals);
                dstVals[Matrix.MSCALE_X] = srcVals[Matrix.MSCALE_X];
                dstVals[Matrix.MTRANS_X] = srcVals[Matrix.MTRANS_X];
                dstMatrix.setValues(dstVals);
                dstChart.getViewPortHandler().refresh(dstMatrix, dstChart, true);
            }
        }
    }
}
4.高亮联动

其中一个图表高亮时另一个图表也在相同的位置高亮,有可用的api:setOnChartValueSelectedListener用于监听高亮事件,比如给主表设置监听,同步设置给副表

 setOnChartValueSelectedListener(new OnChartValueSelectedListener() {
            @Override
            public void onValueSelected(Entry e, Highlight h) {
                syncChart.highlightValue(h);
// highlightValue(h)调用的时highlightValue(highlight, false), 即syncChart不会再重复回调高亮事件,
//否则可能导致两个图标的高亮事件会无休止的循环调用
            }

            @Override
            public void onNothingSelected() {
                syncChart.highlightValue(null);
            }
        });

这样写在主表和副表都只有一条数据线时是没问题的,但是主表或者副表有多条指标线时就会发现同步高亮有时会失效。debug一下就会发现其实Highlight对象中封装了不光是高亮点的x,y值,还有高亮的数据dataSet在Data中的索引,例如主表高亮数据索引为1,而在副表中只有一条数据线,最大索引为0,那就会出现同步高亮失败。
这里处理办法就多了, 可以这样子,根据自己的需求,重新new Highlight,给他们设置对应的dataIndex(有时候还需要设置DataSetIndex,new对象时最后一个参数就是DataSetIndex),让多个数据都高亮即可。

                Highlight highlight = new Highlight(h.getX(), Float.NaN, 0);
                highlight.setDataIndex(0);
                Highlight highlight1 = new Highlight(h.getX(), Float.NaN, 0);
                highlight1.setDataIndex(1);
                syncChart.highlightValues(new Highlight[]{highlight, highlight1});

也可以改造一下highlightValues相关的方法,不考虑index即可(没测试过)。(而我的项目中由于底部标签的需要,不使用库的highlight[], 单独维护了一个highlight,重载了highlightValues方法)

5. 跨表缩放失效

再说一个有的童鞋可能遇到的问题,就是主表和副表上下排列时,需要同时给两个表都设置联动,相应的高亮同步时也是需要给两个表都设置。但是可能会担心两个表都设置联动会引起相互循环调用问题,确实高亮联动可能会,setOnChartValueSelectedListener上面那段代码里已经说明了原因,使用时注意即可。但是还有一个我个人认为不好的地方是:双指缩放时,一个手指在主表一个手指在副表,那么缩放就会失效,因为缩放事件只能在同一个表中处理。
我这里说说我个人的处理方式:直接让主表占满整个空间覆盖住副表(不设背景 两个表其实都可以看到),想办法让主表的绘制区域刚好高于副表。这里设置了副表25%高度,而主表初始化时拿到其高度直接*0.75就可以让主表底部空出25%的区域,看上去就和主副表上下排列没什么区别。更重要的时这样操作时,所有的触控操作只要需在主表中进行就可以了,联动和高亮也只需要由主表同步给副表,不需要处理副表的任何触控事件了。

  @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        if (w > 0 && h > 0 && w < 10000 && h < 10000) {
            mViewPortHandler.setChartDimens(w, h * 0.75f);
        }
    }

你可能感兴趣的:(MPAndroidChart绘制K线图(三))