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

MPAndroidChart绘制K线图(三)

作者: vachex | 来源:发表于2019-08-19 13:55 被阅读0次

    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线图(三)

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