使用MPAndroidChart實現K線圖(5)——高亮聯動、橫豎屏切換
目錄
由於前面畫出的K線圖已經預設實現了左右手勢滑動圖表,此時再滑動高亮的話,會造成衝突。因此把高亮的邏輯設定成只有長按才可以觸發高亮,高亮時,其他手勢無效,此時高亮跟隨手指滑動,手勢結束時高亮結束;未觸發高亮時僅執行預設邏輯。由於Chart的手勢監聽器OnChartGestureListener只會觸發部分手勢效果,且滯後於View的OnTouchListener,因此需要自定義監聽器ChartFingerTouchListener,繼承View.OnTouchListener。
public class ChartFingerTouchListener implements View.OnTouchListener { private BarLineChartBase mChart; private GestureDetector mDetector; private HighlightListener mListener; private boolean mIsLongPress = false; public ChartFingerTouchListener(BarLineChartBase chart, HighlightListener listener) { mChart = chart; mListener = listener; 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.setDraw(e.getX(), e.getY()); mChart.highlightValue(h, true); mChart.disableScroll(); } } }); } @Override public boolean onTouch(View v, MotionEvent event) { mDetector.onTouchEvent(event); 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.setDraw(event.getX(), event.getY()); mChart.highlightValue(h, true); mChart.disableScroll(); } return true; } return false; } public interface HighlightListener { void enableHighlight(); void disableHighlight(); } }
其中,設定回撥介面HighlightListener,當高亮有效和關閉高亮時回撥到外部。高亮線跟隨手指滑動,需要在繪製高亮線之前把手指接觸點的座標儲存下來,高亮物件Highlight已經使用元素mX、mY、mXPx、mYPx儲存了高亮點的座標(蠟燭圖的高亮點為接觸蠟燭的中心點),不方便直接改動,而其中還有不太重要的元素mDrawX、mDrawY,使用其setDraw(float x, float y)方法,可以用來儲存接觸點的座標。
由於ChartFingerTouchListener僅處理一個圖表(稱為圖A)的手勢互動,並觸發高亮,此時還需要在圖A有高亮變化時,主動去通知相關聯的圖表(稱為圖B)去處理相應的高亮。因此需要自定義高亮監聽器CoupleChartValueSelectedListener
public class CoupleChartValueSelectedListener implements OnChartValueSelectedListener { private BarLineChartBase srcChart; private BarLineChartBase[] dstCharts; private ValueSelectedListener mListener; public CoupleChartValueSelectedListener(BarLineChartBase srcChart, BarLineChartBase... dstCharts) { this(null, srcChart, dstCharts); } public CoupleChartValueSelectedListener(ValueSelectedListener mListener, BarLineChartBase srcChart, BarLineChartBase... dstCharts) { this.mListener = mListener; this.srcChart = srcChart; this.dstCharts = dstCharts; } @Override public void onValueSelected(Entry e, Highlight h) { if (dstCharts != null) { for (BarLineChartBase chart : dstCharts) { float touchY = h.getDrawY();//手指接觸點在srcChart上的Y座標,即手勢監聽器中儲存資料 float y = h.getY(); if (chart instanceof BarChart) { y = touchY - srcChart.getHeight(); } else if (chart instanceof CombinedChart) { y = touchY + chart.getHeight(); } Highlight hl = new Highlight(h.getX(), Float.NaN, h.getDataSetIndex()); hl.setDraw(h.getX(), y); chart.highlightValues(new Highlight[]{hl}); } } if (mListener != null) { mListener.valueSelected(e); } } @Override public void onNothingSelected() { if (dstCharts != null) { for (BarLineChartBase chart : dstCharts) { chart.highlightValues(null); } } if (mListener != null) { mListener.nothingSelected(); } } public interface ValueSelectedListener { void valueSelected(Entry e); void nothingSelected(); } }
關於手指接觸點的Y座標,在圖表範圍內,從上邊緣到下邊緣,從0開始依次遞增,最大為圖表的高度;在圖表以外的上面區域,其值為負,越往上越小;在圖表以外的下面區域,從圖表下邊緣開始,越往下越大。對於同一個點,它在圖A和在圖B中的Y座標是不同的(圖A和圖B不重疊),因此圖A通知圖B高亮時,需要計算接觸點在圖B中的座標,然後再使圖B高亮。我們這裡的K線圖,CombinedChart和BarChart是上下相連且無間隙的,如果長按觸發高亮的點在CombinedChart中,接觸點在CombinedChart中的Y座標減去CombinedChart的高度,即為接觸點在BarChart中的Y座標;如果觸發高亮的點在BarChart中,接觸點在BarChart中的Y座標加上CombinedChart的高度,即為接觸點在CombinedChart中的Y座標。另外,新建Highlight時把y值設定為Float.NaN可保證Highlight不出現異常(繪製高亮時並不使用其y值)。
把監聽器設定給CombinedChart和BarChart,並實現其介面中的方法。
//初始化方法中設定
cc.setOnChartValueSelectedListener(new CoupleChartValueSelectedListener(this, cc, bc));//高亮聯動監聽
bc.setOnChartValueSelectedListener(new CoupleChartValueSelectedListener(this, bc, cc));
cc.setOnTouchListener(new ChartFingerTouchListener(cc, this));//手指長按滑動高亮
bc.setOnTouchListener(new ChartFingerTouchListener(bc, this));
@Override
public void valueSelected(Entry e) {
//高亮時邏輯
}
@Override
public void nothingSelected() {
//高亮關閉時邏輯
}
@Override
public void enableHighlight() {
if (!barSet.isHighlightEnabled()) {
candleSet.setHighlightEnabled(true);
lineSetMin.setHighlightEnabled(true);
barSet.setHighlightEnabled(true);
}
}
@Override
public void disableHighlight() {
if (barSet.isHighlightEnabled()) {
candleSet.setHighlightEnabled(false);
lineSetMin.setHighlightEnabled(false);
barSet.setHighlightEnabled(false);
if (ccGesture != null) {
ccGesture.setHighlight(true);
}
if (bcGesture != null) {
bcGesture.setHighlight(true);
}
}
}
在互動結束時,Chart在回撥ChartFingerTouchListener後,會繼續回撥CoupleChartGestureListener。也就是說,當圖表停留在邊緣時長按觸發高亮,手勢結束高亮關閉後會回撥CoupleChartGestureListener,繼續執行滑到邊緣載入更多的邏輯。應該避免這種情況,由於只有在手勢結束的時候才會觸發載入更多,且在此之前會執行ChartFingerTouchListener的disableHighlight()方法,所以可以在這裡使CoupleChartGestureListener停止執行載入更多。給CoupleChartGestureListener新增isHighlight元素,並修改以下方法:
private boolean isHighlight;//是否高亮 -- 高亮時不再載入更多
@Override
public void onChartGestureEnd(MotionEvent me, ChartTouchListener.ChartGesture lastPerformedGesture) {
if (isHighlight) {
isHighlight = false;
} else {
if (isLoadMore) {
float leftX = srcChart.getLowestVisibleX();
float rightX = srcChart.getHighestVisibleX();
if (leftX == srcChart.getXChartMin()) {//滑到最左端
canLoad = false;
if (edgeListener != null) {
edgeListener.edgeLoad(leftX, true);
}
} else if (rightX == srcChart.getXChartMax()) {//滑到最右端
canLoad = false;
if (edgeListener != null) {
edgeListener.edgeLoad(rightX, false);
}
} else {
canLoad = true;
}
}
}
syncCharts();
chartGestureEnd(me, lastPerformedGesture);
}
public void setHighlight(boolean highlight) {
isHighlight = highlight;
}
對於高亮,預想的效果是:在手指接觸點畫一根豎線、一根橫線,在豎線的最上端(CombinedChart內)顯示時間,在橫線的最右端顯示接觸點的Y值,如果接觸點在圖表區域外,則不畫橫線。對CombinedChart和BarChart分開來考慮的話,BarChart只畫豎線,不畫豎線上的文字,有橫線時畫橫線和文字;CombinedChart需要同時畫豎線和文字,有橫線時畫橫線和文字。
先自定義繪製BarChart的高亮效果,在之前自定義過的渲染器OffsetBarRenderer中重寫繪製高亮的方法drawHighlighted(),實現高亮效果。
protected float highlightWidth, highlightSize;//高亮線寬度 單位:dp / 高亮文字大小 單位:px
private DecimalFormat format = new DecimalFormat("0.0000");//保留小數點後四位
public OffsetBarRenderer setHighlightWidthSize(float width, float textSize) {
highlightWidth = Utils.convertDpToPixel(width);
highlightSize = textSize;
return this;
}
@Override
public void drawHighlighted(Canvas c, Highlight[] indices) {
BarData barData = mChart.getBarData();
for (Highlight high : indices) {
IBarDataSet set = barData.getDataSetByIndex(high.getDataSetIndex());
if (set == null || !set.isHighlightEnabled())
continue;
BarEntry e = set.getEntryForXValue(high.getX(), high.getY());
if (!isInBoundsX(e, set))
continue;
mHighlightPaint.setColor(set.getHighLightColor());
mHighlightPaint.setStrokeWidth(highlightWidth);
mHighlightPaint.setTextSize(highlightSize);
//畫豎線
float barWidth = barData.getBarWidth();
Transformer trans = mChart.getTransformer(set.getAxisDependency());
prepareBarHighlight(e.getX() + barOffset, 0, 0, barWidth / 2, trans);
float xp = mBarRect.centerX();
c.drawLine(xp, mViewPortHandler.getContentRect().bottom, xp, 0, mHighlightPaint);
//判斷是否畫橫線
float y = high.getDrawY();
float yMaxValue = mChart.getYChartMax();
float yMin = getYPixelForValues(xp, yMaxValue);
float yMax = getYPixelForValues(xp, 0);
if (y >= 0 && y <= yMax) {//在區域內即繪製橫線
float xMax = mChart.getWidth();
int halfPaddingVer = 5;//豎向半個padding
int halfPaddingHor = 8;
//先繪製文字框
mHighlightPaint.setStyle(Paint.Style.STROKE);
float yValue = (yMax - y) / (yMax - yMin) * yMaxValue;
String text = format.format(yValue);
int width = Utils.calcTextWidth(mHighlightPaint, text);
int height = Utils.calcTextHeight(mHighlightPaint, text);
float top = Math.max(0, y - height / 2F - halfPaddingVer);//考慮間隙
float bottom = top + height + halfPaddingVer * 2;
if (bottom > yMax) {
bottom = yMax;
top = bottom - height - halfPaddingVer * 2;
}
RectF rect = new RectF(xMax - width - halfPaddingHor * 2, top, xMax, bottom);
c.drawRect(rect, mHighlightPaint);
//再繪製文字
mHighlightPaint.setStyle(Paint.Style.FILL);
Paint.FontMetrics metrics = mHighlightPaint.getFontMetrics();
float baseY = (top + bottom - metrics.top - metrics.bottom) / 2;
c.drawText(text, xMax - width - halfPaddingHor, baseY, mHighlightPaint);
//繪製橫線
c.drawLine(0, y, xMax - width - halfPaddingHor * 2, y, mHighlightPaint);
}
}
}
protected float getYPixelForValues(float x, float y) {
MPPointD pixels = mChart.getTransformer(YAxis.AxisDependency.LEFT).getPixelForValues(x, y);
return (float) pixels.y;
}
一個點在圖表上有兩種座標,一種是MPAndroidChart定義的座標軸上的座標,一種是Android系統定義的在手機螢幕內的座標(控制元件內的座標是相對於控制元件而言的)。MPAndroidChart提供了方法,把座標軸座標轉換成螢幕座標,即getYPixelForValues(float x, float y)方法中的語句。對於點的Y座標,在座標軸內是從下往上遞增的,在手機螢幕內是從上往下遞增的。實際情況中,座標軸上的可見Y值範圍,和螢幕座標的可見範圍不是完全對應的,可能出現座標軸上Y最大值對應的螢幕座標是不可見的。在前面選中高亮時所儲存的座標是螢幕座標,而高亮的橫線上顯示的數字是座標軸座標,因此要把Highlight中的mDrawY轉換成座標軸座標,這裡使用的是按比例計算的方式。
給OffsetBarRenderer設定高亮線的寬度,以及文字大小,執行程式,效果圖如下:
上部分CombinedChart中的高亮線是其中的CandleStickChart預設的高亮效果。
接下來就是自定義CandleStickChart的渲染器HighlightCandleRenderer,繼承CandleStickChartRenderer,重寫高亮方法。
public class HighlightCandleRenderer extends CandleStickChartRenderer {
private float highlightSize;//圖表高亮文字大小 單位:px
private DecimalFormat format = new DecimalFormat("0.0000");
public HighlightCandleRenderer(CandleDataProvider chart, ChartAnimator animator,
ViewPortHandler viewPortHandler) {
super(chart, animator, viewPortHandler);
}
public HighlightCandleRenderer setHighlightSize(float textSize) {
highlightSize = textSize;
return this;
}
@Override
public void drawHighlighted(Canvas c, Highlight[] indices) {
CandleData candleData = mChart.getCandleData();
for (Highlight high : indices) {
ICandleDataSet set = candleData.getDataSetByIndex(high.getDataSetIndex());
if (set == null || !set.isHighlightEnabled())
continue;
CandleEntry e = set.getEntryForXValue(high.getX(), high.getY());
if (!isInBoundsX(e, set))
continue;
float lowValue = e.getLow() * mAnimator.getPhaseY();
float highValue = e.getHigh() * mAnimator.getPhaseY();
MPPointD pix = mChart.getTransformer(set.getAxisDependency())
.getPixelForValues(e.getX(), (lowValue + highValue) / 2f);
float xp = (float) pix.x;
mHighlightPaint.setColor(set.getHighLightColor());
mHighlightPaint.setStrokeWidth(set.getHighlightLineWidth());
mHighlightPaint.setTextSize(highlightSize);
float xMin = mViewPortHandler.contentLeft();
float xMax = mViewPortHandler.contentRight();
float contentBottom = mViewPortHandler.contentBottom();
//畫豎線
int halfPaddingVer = 5;//豎向半個padding
int halfPaddingHor = 8;
float textXHeight = 0;
String textX;//高亮點的X顯示文字
Object data = e.getData();
if (data != null && data instanceof String) {
textX = (String) data;
} else {
textX = e.getX() + "";
}
if (!TextUtils.isEmpty(textX)) {//繪製x的值
//先繪製文字框
mHighlightPaint.setStyle(Paint.Style.STROKE);
int width = Utils.calcTextWidth(mHighlightPaint, textX);
int height = Utils.calcTextHeight(mHighlightPaint, textX);
float left = Math.max(xMin, xp - width / 2F - halfPaddingVer);//考慮間隙
float right = left + width + halfPaddingHor * 2;
if (right > xMax) {
right = xMax;
left = right - width - halfPaddingHor * 2;
}
textXHeight = height + halfPaddingVer * 2;
RectF rect = new RectF(left, 0, right, textXHeight);
c.drawRect(rect, mHighlightPaint);
//再繪製文字
mHighlightPaint.setStyle(Paint.Style.FILL);
Paint.FontMetrics metrics = mHighlightPaint.getFontMetrics();
float baseY = (height + halfPaddingVer * 2 - metrics.top - metrics.bottom) / 2;
c.drawText(textX, left + halfPaddingHor, baseY, mHighlightPaint);
}
//繪製豎線
c.drawLine(xp, textXHeight, xp, contentBottom, mHighlightPaint);
//判斷是否畫橫線
float y = high.getDrawY();
float yMaxValue = mChart.getYChartMax();
float yMinValue = mChart.getYChartMin();
float yMin = getYPixelForValues(xp, yMaxValue);
float yMax = getYPixelForValues(xp, yMinValue);
if (y >= 0 && y <= contentBottom) {//在區域內即繪製橫線
//先繪製文字框
mHighlightPaint.setStyle(Paint.Style.STROKE);
float yValue = (yMax - y) / (yMax - yMin) * (yMaxValue - yMinValue) + yMinValue;
String text = format.format(yValue);
int width = Utils.calcTextWidth(mHighlightPaint, text);
int height = Utils.calcTextHeight(mHighlightPaint, text);
float top = Math.max(0, y - height / 2F - halfPaddingVer);//考慮間隙
float bottom = top + height + halfPaddingVer * 2;
if (bottom > contentBottom) {
bottom = contentBottom;
top = bottom - height - halfPaddingVer * 2;
}
RectF rect = new RectF(xMax - width - halfPaddingHor * 2, top, xMax, bottom);
c.drawRect(rect, mHighlightPaint);
//再繪製文字
mHighlightPaint.setStyle(Paint.Style.FILL);
Paint.FontMetrics metrics = mHighlightPaint.getFontMetrics();
float baseY = (top + bottom - metrics.top - metrics.bottom) / 2;
c.drawText(text, xMax - width - halfPaddingHor, baseY, mHighlightPaint);
//繪製橫線
c.drawLine(0, y, xMax - width - halfPaddingHor * 2, y, mHighlightPaint);
}
}
}
protected float getYPixelForValues(float x, float y) {
MPPointD pixels = mChart.getTransformer(YAxis.AxisDependency.LEFT).getPixelForValues(x, y);
return (float) pixels.y;
}
}
豎線上的日期不可以由x值直接轉換,因為對於同一個點,在追加資料前後的x值是不同的,所以這個日期應在新建CandleEntry時作為附加物件傳入。在繪製方法中,橫線的繪製和OffsetBarRenderer中的橫線繪製幾乎一樣。
由於CandleStickChart是被包含在CombinedChart中的,因此應將CandleStickChart的渲染器替換為自定義渲染器HighlightCandleRenderer。自定義CombinedChart的渲染器HighlightCombinedRenderer,繼承CombinedChartRenderer。
public class HighlightCombinedRenderer extends CombinedChartRenderer {
private float highlightSize;//圖表高亮文字大小 單位:px
public HighlightCombinedRenderer(CombinedChart chart, ChartAnimator animator,
ViewPortHandler viewPortHandler, float highlightSize) {
super(chart, animator, viewPortHandler);
this.highlightSize = highlightSize;
}
@Override
public void createRenderers() {
mRenderers.clear();
CombinedChart chart = (CombinedChart)mChart.get();
if (chart == null)
return;
DrawOrder[] orders = chart.getDrawOrder();
for (DrawOrder order : orders) {
switch (order) {
case BAR:
if (chart.getBarData() != null)
mRenderers.add(new BarChartRenderer(chart, mAnimator, mViewPortHandler));
break;
case BUBBLE:
if (chart.getBubbleData() != null)
mRenderers.add(new BubbleChartRenderer(chart, mAnimator, mViewPortHandler));
break;
case LINE:
if (chart.getLineData() != null)
mRenderers.add(new LineChartRenderer(chart, mAnimator, mViewPortHandler));
break;
case CANDLE:
if (chart.getCandleData() != null)
mRenderers.add(new HighlightCandleRenderer(chart, mAnimator, mViewPortHandler)
.setHighlightSize(highlightSize));
break;
case SCATTER:
if (chart.getScatterData() != null)
mRenderers.add(new ScatterChartRenderer(chart, mAnimator, mViewPortHandler));
break;
}
}
}
}
給CombinedChart設定此渲染器,並在新建CandleEntry時傳入日期。
//初始化方法中設定
cc.setRenderer(new HighlightCombinedRenderer(cc, cc.getAnimator(), cc.getViewPortHandler(), sp8));
//配置圖表方法中設定
candleValues.add(new CandleEntry(i, Float.parseFloat(k.get(2)),
Float.parseFloat(k.get(3)), open, close, x));
效果圖如下:
同樣,時間間隔為1m的分時圖也需要自定義高亮。分時圖是LineChart,自定義其渲染器HighlightLineRenderer,繼承LineChartRenderer,重寫高亮方法。
public class HighlightLineRenderer extends LineChartRenderer {
private float highlightSize;//圖表高亮文字大小 單位:px
private DecimalFormat format = new DecimalFormat("0.0000");
public HighlightLineRenderer(LineDataProvider chart, ChartAnimator animator,
ViewPortHandler viewPortHandler) {
super(chart, animator, viewPortHandler);
}
public HighlightLineRenderer setHighlightSize(float textSize) {
highlightSize = textSize;
return this;
}
@Override
public void drawHighlighted(Canvas c, Highlight[] indices) {
LineData lineData = mChart.getLineData();
for (Highlight high : indices) {
ILineDataSet set = lineData.getDataSetByIndex(high.getDataSetIndex());
if (set == null || !set.isHighlightEnabled())
continue;
Entry e = set.getEntryForXValue(high.getX(), high.getY());
if (!isInBoundsX(e, set))
continue;
MPPointD pix = mChart.getTransformer(set.getAxisDependency()).getPixelForValues(e.getX(),
e.getY() * mAnimator.getPhaseY());
float xp = (float) pix.x;
mHighlightPaint.setColor(set.getHighLightColor());
mHighlightPaint.setStrokeWidth(set.getHighlightLineWidth());
mHighlightPaint.setTextSize(highlightSize);
float xMin = mViewPortHandler.contentLeft();
float xMax = mViewPortHandler.contentRight();
float contentBottom = mViewPortHandler.contentBottom();
//畫豎線
int halfPaddingVer = 5;//豎向半個padding
int halfPaddingHor = 8;
float textXHeight = 0;
String textX;//高亮點的X顯示文字
Object data = e.getData();
if (data != null && data instanceof String) {
textX = (String) data;
} else {
textX = e.getX() + "";
}
if (!TextUtils.isEmpty(textX)) {//繪製x的值
//先繪製文字框
mHighlightPaint.setStyle(Paint.Style.STROKE);
int width = Utils.calcTextWidth(mHighlightPaint, textX);
int height = Utils.calcTextHeight(mHighlightPaint, textX);
float left = Math.max(xMin, xp - width / 2F - halfPaddingVer);//考慮間隙
float right = left + width + halfPaddingHor * 2;
if (right > xMax) {
right = xMax;
left = right - width - halfPaddingHor * 2;
}
textXHeight = height + halfPaddingVer * 2;
RectF rect = new RectF(left, 0, right, textXHeight);
c.drawRect(rect, mHighlightPaint);
//再繪製文字
mHighlightPaint.setStyle(Paint.Style.FILL);
Paint.FontMetrics metrics = mHighlightPaint.getFontMetrics();
float baseY = (height + halfPaddingVer * 2 - metrics.top - metrics.bottom) / 2;
c.drawText(textX, left + halfPaddingHor, baseY, mHighlightPaint);
}
//繪製豎線
c.drawLine(xp, textXHeight, xp, contentBottom, mHighlightPaint);
//判斷是否畫橫線
float y = high.getDrawY();
float yMaxValue = mChart.getYChartMax();
float yMinValue = mChart.getYChartMin();
float yMin = getYPixelForValues(xp, yMaxValue);
float yMax = getYPixelForValues(xp, yMinValue);
if (y >= 0 && y <= contentBottom) {//在區域內即繪製橫線
//先繪製文字框
mHighlightPaint.setStyle(Paint.Style.STROKE);
float yValue = (yMax - y) / (yMax - yMin) * (yMaxValue - yMinValue) + yMinValue;
String text = format.format(yValue);
int width = Utils.calcTextWidth(mHighlightPaint, text);
int height = Utils.calcTextHeight(mHighlightPaint, text);
float top = Math.max(0, y - height / 2F - halfPaddingVer);//考慮間隙
float bottom = top + height + halfPaddingVer * 2;
if (bottom > contentBottom) {
bottom = contentBottom;
top = bottom - height - halfPaddingVer * 2;
}
RectF rect = new RectF(xMax - width - halfPaddingHor * 2, top, xMax, bottom);
c.drawRect(rect, mHighlightPaint);
//再繪製文字
mHighlightPaint.setStyle(Paint.Style.FILL);
Paint.FontMetrics metrics = mHighlightPaint.getFontMetrics();
float baseY = (top + bottom - metrics.top - metrics.bottom) / 2;
c.drawText(text, xMax - width - halfPaddingHor, baseY, mHighlightPaint);
//繪製橫線
c.drawLine(0, y, xMax - width - halfPaddingHor * 2, y, mHighlightPaint);
}
}
}
protected float getYPixelForValues(float x, float y) {
MPPointD pixels = mChart.getTransformer(YAxis.AxisDependency.LEFT).getPixelForValues(x, y);
return (float) pixels.y;
}
}
方式和HighlightCandleRenderer一樣。接著把HighlightCombinedRenderer中的LineChartRenderer替換成HighlightLineRenderer。
//createRenderers()方法中
case LINE:
if (chart.getLineData() != null)
mRenderers.add(new HighlightLineRenderer(chart, mAnimator, mViewPortHandler)
.setHighlightSize(highlightSize));
break;
在新建分時線的Entry時傳入日期。
//配置圖表方法中設定
minValues.add(new Entry(i, close, x));
效果圖如下:
另外,當頁面重啟時,如果圖表處於最右端,則向右獲取最新資料。
@Override
protected void onRestart() {
super.onRestart();
float rightX = cc.getHighestVisibleX();
if (rightX == cc.getXChartMax()) {//停留在最右端
edgeLoad(rightX, false);
}
}
接下來是橫豎屏切換效果,其基本邏輯是:預設情況下豎屏,此時雙擊圖表或點選切換按鈕,則切換成橫屏;橫屏時,按後退鍵或點選切換按鈕,則切換成豎屏;切換前後,最右端為同一個資料。
通常情況下,橫屏和豎屏的佈局類似但不完全相同,需要分別設定佈局。將豎屏的佈局放在res下的layout-port資料夾中,橫屏的佈局放在layout-land資料夾中,注意將佈局名設定成相同,相同控制元件的id設定成相同。橫豎屏切換時不需要重新執行生命週期,需將AndroidManifest.xml中的當前Activity進行如下設定:
<activity android:name=".activity.home.KLineActivity"
android:configChanges="orientation|keyboardHidden|screenSize" />
豎屏時雙擊圖表切換成橫屏,則需要在手勢監聽器中處理雙擊事件。
//初始化方法中設定
ccGesture = new CoupleChartGestureListener(this, cc, bc) {
@Override
public void chartDoubleTapped(MotionEvent me) {
doubleTapped();
}
};
cc.setOnChartGestureListener(ccGesture);//設定手勢聯動監聽
bcGesture = new CoupleChartGestureListener(this, bc, cc) {
@Override
public void chartDoubleTapped(MotionEvent me) {
doubleTapped();
}
};
bc.setOnChartGestureListener(bcGesture);
private void doubleTapped() {
if (isPort()) {
highVisX = cc.getHighestVisibleX();//記錄切換前最右端的X
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
}
}
/**
* 當前是否是豎屏
*/
public boolean isPort() {
return getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
}
還有切換按鈕的監聽,以及後退鍵處理。
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.iv_klBack://後退鍵
onBackPressed();
break;
case R.id.iv_klOrientation://切換橫豎屏
highVisX = cc.getHighestVisibleX();
setRequestedOrientation(isPort() ? ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE :
ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
break;
}
}
@Override
public void onBackPressed() {
if (isPort()) {
super.onBackPressed();
} else {
highVisX = cc.getHighestVisibleX();
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}
}
最後是切換後的處理。
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
setContentView(R.layout.activity_main);
initView();
range = newConfig.orientation == Configuration.ORIENTATION_PORTRAIT ? 52 : 86;//豎屏顯示52個 橫屏顯示86個
configData();
if (xValues.size() > 0) {
cc.post(new Runnable() {
@Override
public void run() {
float x = highVisX - range;
cc.moveViewToX(x);
bc.moveViewToX(x + barOffset);
cc.notifyDataSetChanged();
bc.notifyDataSetChanged();
}
});
}
}
橫屏效果圖:
至此,預定的效果均已實現。
雖然效果實現了,但還有一個小問題:CombinedChart的高亮線不能繪製在X軸上,即使把線的長度設定的足夠大。如果真有這種需求,也是有辦法解決的。首先是找到造成這種情況的原因。通常,View的繪製語句都是在其onDraw(Canvas canvas)方法中,MPAndroidChart也不例外。檢視CombinedChart父類BarLineChartBase的原始碼,在onDraw()方法中有以下語句:
// make sure the data cannot be drawn outside the content-rect
int clipRestoreCount = canvas.save();
canvas.clipRect(mViewPortHandler.getContentRect());
mRenderer.drawData(canvas);
// if highlighting is enabled
if (valuesToHighlight())
mRenderer.drawHighlighted(canvas, mIndicesToHighlight);
// Removes clipping rectangle
canvas.restoreToCount(clipRestoreCount);
mRenderer.drawExtras(canvas);
可見在Renderer繪製高亮之前,對畫布canvas進行了裁剪,只保留了中間的內容區域,繪製完高亮之後又恢復了畫布。因此在HighlightCandleRenderer的drawHighlighted()方法中是不可能把高亮繪製在X軸上的。
但是在恢復畫布後,Renderer又呼叫了drawExtras(Canvas c)方法,因此可以在這裡繪製高亮。由於這兩個方法是依次呼叫的,所以可以在drawHighlighted()中把mIndicesToHighlight儲存起來。在drawExtras()中繪製時,把豎線的長度設定成直到圖表底部即可。
//繪製豎線
c.drawLine(xp, textXHeight, xp, mChart.getHeight(), mHighlightPaint);
同樣,HighlightLineRenderer中也可以進行相同的操作。