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);
}
}