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);
        }
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容

  • 公司项目使用RN实现的k线图效果不忍直视,调试过很多个版本都没有达到理想的效果,于是想用原生来实现。经过调研选用的...
    vachex阅读 3,285评论 1 2
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,084评论 1 32
  • 图表控件库 MPAndroidChart 的使用 使用方法 项目源码地址,包含了很多类型的图标 https://g...
    jinchuang阅读 813评论 0 0
  • ORA-00001: 违反唯一约束条件 (.) 错误说明:当在唯一索引所对应的列上键入重复值时,会触发此异常。 O...
    我想起个好名字阅读 5,159评论 0 9
  • “一个白日带走了一点青春, 日子虽不能毁坏我印象里你所给我的光明 却慢慢地使我不同了。” 的确不同了,一点一滴的青...
    花落寂寂hjr阅读 414评论 1 6