MPAndroidChart繪制K線圖(一)高亮線自定義
MPAndroidChart繪制K線圖(二)動(dòng)態(tài)時(shí)間格式+高亮?xí)r底部滑動(dòng)時(shí)間刻度
MPAndroidChart繪制K線圖(三)長(zhǎng)按高亮,雙擊事件绑榴,縮放中心點(diǎn)變換样勃,圖表聯(lián)動(dòng)窖逗,跨表縮放失效
更新GitHub地址
自定義股線圖StockChart
三假哎、觸控事件處理
1.長(zhǎng)按高亮荸型,雙擊監(jiān)聽(tīng)
只需要監(jiān)聽(tīng)Chart的觸控事件险绘,處理長(zhǎng)按事件和雙擊事件即可换可;觸控事件是優(yōu)先處理OnTouchListener的,所以自定義OnTouchListener設(shè)置給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可以滿足雙擊和長(zhǎng)按監(jiān)聽(tīng)
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());
// 長(zhǎng)按時(shí)觸發(fā)該chart高亮
mChart.highlightValue(h, true);
mChart.disableScroll();
}
}
@Override
public boolean onDoubleTap(MotionEvent e) {
if (mListener != null) {
// 雙擊事件回調(diào)
mListener.onDoubleTap();
}
return super.onDoubleTap(e);
}
});
}
@Override
public boolean onTouch(View v, MotionEvent event) {
// 優(yōu)先使用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;
// 長(zhǎng)按事件結(jié)束了蜘澜,需要取消高亮
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)的第二個(gè)參數(shù),代表是否需要回調(diào)高亮監(jiān)聽(tīng)响疚,如果設(shè)置false鄙信,那么Chart中設(shè)置的setOnChartValueSelectedListener就不會(huì)調(diào)用(后面說(shuō)到高亮同步時(shí)會(huì)說(shuō)到),這里需要設(shè)置為true忿晕。
2.基于y軸縮放和基于觸控點(diǎn)縮放
默認(rèn)情況下雙指縮放時(shí)是基于雙指中心點(diǎn)縮放装诡,但是需求希望:當(dāng)數(shù)據(jù)較少,不滿一屏幕時(shí)践盼,雙指縮放基于左側(cè)y軸鸦采。查看源碼看了一下其縮放策略,在BarLineChartTouchListener中
if (event.getPointerCount() >= 2) { // two finger zoom
......
// 通過(guò)這個(gè)getTrans將觸控點(diǎn)轉(zhuǎn)換為縮放中心點(diǎn)
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);
}
確認(rèn)確實(shí)是getTrans方法中處理的咕幻,要實(shí)現(xiàn)基于y軸縮放只需要將getTrans中的x值置為0即可渔伯,同理需要基于右側(cè)y軸,將x值強(qiáng)制設(shè)置為最大值肄程。這里需要繼承BarLineChartTouchListener锣吼,重寫的getTrans方法,然后在初始化時(shí)實(shí)例化設(shè)置給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.圖表聯(lián)動(dòng)
chart提供了public void setOnChartGestureListener(OnChartGestureListener l)用于監(jiān)聽(tīng)各種手勢(shì)事件(縮放 平移 觸摸開(kāi)始 觸摸結(jié)束 長(zhǎng)按 雙擊 單擊 滑動(dòng)等)玄叠,同步兩個(gè)chart只需要監(jiān)聽(tīng)chart的縮放平移,同步設(shè)置給另外一張圖表拓提。
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();
}
/**
* 同步目標(biāo)chart和當(dāng)前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.高亮聯(lián)動(dòng)
其中一個(gè)圖表高亮?xí)r另一個(gè)圖表也在相同的位置高亮读恃,有可用的api:setOnChartValueSelectedListener用于監(jiān)聽(tīng)高亮事件,比如給主表設(shè)置監(jiān)聽(tīng),同步設(shè)置給副表
setOnChartValueSelectedListener(new OnChartValueSelectedListener() {
@Override
public void onValueSelected(Entry e, Highlight h) {
syncChart.highlightValue(h);
// highlightValue(h)調(diào)用的時(shí)highlightValue(highlight, false)寺惫, 即syncChart不會(huì)再重復(fù)回調(diào)高亮事件疹吃,
//否則可能導(dǎo)致兩個(gè)圖標(biāo)的高亮事件會(huì)無(wú)休止的循環(huán)調(diào)用
}
@Override
public void onNothingSelected() {
syncChart.highlightValue(null);
}
});
這樣寫在主表和副表都只有一條數(shù)據(jù)線時(shí)是沒(méi)問(wèn)題的,但是主表或者副表有多條指標(biāo)線時(shí)就會(huì)發(fā)現(xiàn)同步高亮有時(shí)會(huì)失效肌蜻。debug一下就會(huì)發(fā)現(xiàn)其實(shí)Highlight對(duì)象中封裝了不光是高亮點(diǎn)的x,y值必尼,還有高亮的數(shù)據(jù)dataSet在Data中的索引蒋搜,例如主表高亮數(shù)據(jù)索引為1,而在副表中只有一條數(shù)據(jù)線判莉,最大索引為0豆挽,那就會(huì)出現(xiàn)同步高亮失敗。
這里處理辦法就多了, 可以這樣子券盅,根據(jù)自己的需求帮哈,重新new Highlight,給他們?cè)O(shè)置對(duì)應(yīng)的dataIndex(有時(shí)候還需要設(shè)置DataSetIndex锰镀,new對(duì)象時(shí)最后一個(gè)參數(shù)就是DataSetIndex)娘侍,讓多個(gè)數(shù)據(jù)都高亮即可。
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相關(guān)的方法泳炉,不考慮index即可(沒(méi)測(cè)試過(guò))憾筏。(而我的項(xiàng)目中由于底部標(biāo)簽的需要,不使用庫(kù)的highlight[], 單獨(dú)維護(hù)了一個(gè)highlight花鹅,重載了highlightValues方法)
5. 跨表縮放失效
再說(shuō)一個(gè)有的童鞋可能遇到的問(wèn)題氧腰,就是主表和副表上下排列時(shí),需要同時(shí)給兩個(gè)表都設(shè)置聯(lián)動(dòng)刨肃,相應(yīng)的高亮同步時(shí)也是需要給兩個(gè)表都設(shè)置古拴。但是可能會(huì)擔(dān)心兩個(gè)表都設(shè)置聯(lián)動(dòng)會(huì)引起相互循環(huán)調(diào)用問(wèn)題,確實(shí)高亮聯(lián)動(dòng)可能會(huì)真友,setOnChartValueSelectedListener上面那段代碼里已經(jīng)說(shuō)明了原因黄痪,使用時(shí)注意即可。但是還有一個(gè)我個(gè)人認(rèn)為不好的地方是:雙指縮放時(shí)盔然,一個(gè)手指在主表一個(gè)手指在副表满力,那么縮放就會(huì)失效,因?yàn)榭s放事件只能在同一個(gè)表中處理轻纪。
我這里說(shuō)說(shuō)我個(gè)人的處理方式:直接讓主表占滿整個(gè)空間覆蓋住副表(不設(shè)背景 兩個(gè)表其實(shí)都可以看到)油额,想辦法讓主表的繪制區(qū)域剛好高于副表。這里設(shè)置了副表25%高度刻帚,而主表初始化時(shí)拿到其高度直接*0.75就可以讓主表底部空出25%的區(qū)域潦嘶,看上去就和主副表上下排列沒(méi)什么區(qū)別。更重要的時(shí)這樣操作時(shí)崇众,所有的觸控操作只要需在主表中進(jìn)行就可以了掂僵,聯(lián)動(dòng)和高亮也只需要由主表同步給副表航厚,不需要處理副表的任何觸控事件了。
@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);
}
}