之前爲添加XY軸描述大傷腦筋,因爲如果用原生的TextView在圖表控件的上方和下方分別作爲XY軸加了Legend之後效果會差很多,如果繪製在圖表上方又不好控制位置。
之前用textView來左XY軸實現效果:
實現效果總的來講還是可以的,但是因爲控件是分離的,不好做到適配。
如果能在圖表裏面繪製一個就好了,然後發現了圖表有Legend的繪製,有得參考了,自己根據Legend寫了一個。
話不多說,開搞
分析Legend實現
Android Studio雙擊Shift找Legend,在框架的components文件夾下找到了Legend這個類。
Legend裏面配置了一堆Legend參數:位置,顏色,方向,顯示的數據等等…嗯,知道了這個就是用來配置Legend屬性的類。
components裏面的屬性類都繼承了ComponentBase這個類,看一下ComponentBase這個類做什麼的。
裏面是一些抽取出來的共用的配置,文字大小,顏色,是否可用間距等…
Legend在框架中的引用:LegendEntry,LegendRender,BarLineChartBase,Chart,BaseDataSet,IDataSet都有做引用
- LegendEntry:跟BarEntry等是一樣的,是Legend每個item的屬性。因爲Legend 有多個數據,所以需要一個Entry的列表的來做存儲。
- LegendRender:這個是重點,它用來根據屬性配置繪製Legend。
- BarLineChartBase:這個在這個類的onDraw方法找到了Legend和LegendRender的引用。繪製就在這裏了。
- Chart:聲明Legend和LegendRender,這個是公用的屬性。
- BaseDataSet和IDataSet:因爲Legend跟圖表的顏色和label有掛鉤,所以需要。
分析上面文件我們需要寫兩個類,XYDESC(屬性配置類),XYDESCRender(繪製類),並且在BarLineChartBase中聲明和繪製(只有這兩個圖有XY軸)
實現我們的XYDESC
在components文件夾下增加XYDESC類
import android.graphics.Paint;
import com.github.mikephil.charting.utils.Utils;
/**
* @author xiaolong
* @version v1.0
* @function <描述功能>
* @date 2016/10/12-14:09
*/
public class XYDesc extends ComponentBase {
private String xDesc;
private String yDesc;
private float yPadding;
public String getxDesc() {
return xDesc;
}
public void setxDesc(String xDesc) {
this.xDesc = xDesc;
}
public String getyDesc() {
return yDesc;
}
public void setyDesc(String yDesc) {
this.yDesc = yDesc;
}
public XYDesc() {
this.setEnabled(false);
}
/**
* returns the maximum length in pixels across all legend labels + formsize
* + formtotextspace
*
* @param p the paint object used for rendering the text
* @return
*/
public float getMaximumEntryWidth(Paint p, String desc) {
float length = (float) Utils.calcTextWidth(p, desc);
return length;
}
/**
* returns the maximum height in pixels across all legend labels
*
* @param p the paint object used for rendering the text
* @return
*/
public float getMaximumEntryHeight(Paint p, String desc) {
float length = (float) Utils.calcTextHeight(p, desc);
return length;
}
public float getyPadding() {
return yPadding;
}
public void setyPadding(float yPadding) {
this.yPadding = yPadding;
}
}
分別設置了X軸和Y軸描述,
padding是因爲我們是參照Legend寫的如果不做padding的話,會跟Legend繪製重疊,所以如果在左上角或右下角有Legend的時候加上padding。
//這兩個方法用來計算文字的寬度和高度(Render中需要)
getMaxinumEntryWidth(Paint p,String desc)
getMaximumEntryHeight(Paint p, String desc)
在renderer文件夾下增加XYDESCRender
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Typeface;
import com.github.mikephil.charting.components.XYDesc;
import com.github.mikephil.charting.utils.Utils;
import com.github.mikephil.charting.utils.ViewPortHandler;
/**
* @author xiaolong
* @version v1.0
* @function <描述功能>
* @date 2016/10/12-14:05
*/
public class XYDESCRenderer extends Renderer {
/**
* paint for the XYDESCRenderer labels
*/
protected Paint mXYDESCLabelPaint;
protected XYDesc mXYDesc;
public XYDESCRenderer(ViewPortHandler viewPortHandler, XYDesc xydesc) {
super(viewPortHandler);
mXYDesc = xydesc;
//初始化畫筆
mXYDESCLabelPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mXYDESCLabelPaint.setTextSize(Utils.convertDpToPixel(xydesc.getTextSize()));
mXYDESCLabelPaint.setTextAlign(Paint.Align.LEFT);
}
/**
* Returns the Paint object used for drawing the Legend labels.
*
* @return
*/
public Paint getLabelPaint() {
return mXYDESCLabelPaint;
}
/**
* 在畫布上繪製
* @param c
*/
public void renderLegend(Canvas c) {
if (!mXYDesc.isEnabled())
return;
Typeface tf = mXYDESCLabelPaint.getTypeface();
if (tf != null)
mXYDESCLabelPaint.setTypeface(tf);
mXYDESCLabelPaint.setTextSize(mXYDesc.getTextSize());
mXYDESCLabelPaint.setColor(mXYDesc.getTextColor());
//獲取行高
float labelLineHeight = Utils.getLineHeight(mXYDESCLabelPaint);
float yoffset = mXYDesc.getYOffset();
float xoffset = mXYDesc.getXOffset();
// drawLabel(c, mViewPortHandler.contentLeft() + mXYDesc.getMaximumEntryWidth(mXYDESCLabelPaint, mXYDesc.getyDesc()), yoffset + labelLineHeight + mXYDesc.getyPadding(), mXYDesc.getyDesc());
drawLabel(c, mViewPortHandler.contentLeft()
, yoffset + labelLineHeight + mXYDesc.getyPadding(), mXYDesc.getyDesc());
drawLabel(c, mViewPortHandler.contentRight() - mXYDesc.getMaximumEntryWidth(mXYDESCLabelPaint, mXYDesc.getxDesc())
, mViewPortHandler.getChartHeight() - yoffset - mXYDesc.getMaximumEntryHeight(mXYDESCLabelPaint, mXYDesc.getxDesc()) + 10, mXYDesc.getxDesc());
}
/**
* Draws the provided label at the given position.
*
* @param c canvas to draw with
* @param x
* @param y
* @param label the label to draw
*/
protected void drawLabel(Canvas c, float x, float y, String label) {
c.drawText(label, x, y, mXYDESCLabelPaint);
}
}
固定兩個drawLabel分別在左上角和右下角。
然後在BarLineChartBase聲明XYDESC和XYDESCRender
protected XYDesc mXYDesc;
protected XYDESCRenderer mXYDESCRenderer;
//在init方法中初始化
mXYDesc = new XYDesc();
mXYDESCRenderer = new XYDESCRenderer(mViewPortHandler, mXYDesc);
//在onDraw方法中繪製
找到 mLegendRenderer.renderLegend(canvas);下面加一句
mXYDESCRenderer.renderLegend(canvas);
增加屬性設置方法
public XYDesc getXYDesc() {
return mXYDesc;
}
public XYDESCRenderer getXYDESCRenderer() {
return mXYDESCRenderer;
}
public void setXYDesc(String xDesc, String yDesc) {
setXYDesc(xDesc, yDesc, 10f);
}
public void setXYDesc(String xDesc, String yDesc, float textSize) {
setXYDesc(xDesc, yDesc, textSize, Color.BLACK);
}
public void setXYDesc(String xDesc, String yDesc, float textSize, int textColor) {
mXYDesc.setxDesc(xDesc);
mXYDesc.setyDesc(yDesc);
mXYDesc.setTextSize(textSize);
mXYDesc.setTextColor(textColor);
mXYDesc.setEnabled(true);
if (mLegend.isEnabled() && (mLegend.getPosition() == Legend.LegendPosition.ABOVE_CHART_CENTER
|| mLegend.getPosition() == Legend.LegendPosition.ABOVE_CHART_LEFT
|| mLegend.getPosition() == Legend.LegendPosition.ABOVE_CHART_RIGHT)) {
mXYDesc.setyPadding(6 * mLegend.getFormSize());
}
this.setExtraBottomOffset(12f + textSize);
this.setExtraTopOffset(12f + textSize);
}
就可以參考第一篇的來使用了。
package cn.xiaolongonly.mpchartsample.ui;
import android.graphics.Color;
import com.github.mikephil.charting.charts.LineChart;
import com.github.mikephil.charting.components.AxisBase;
import com.github.mikephil.charting.components.Legend;
import com.github.mikephil.charting.components.XAxis;
import com.github.mikephil.charting.components.YAxis;
import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.data.LineData;
import com.github.mikephil.charting.data.LineDataSet;
import com.github.mikephil.charting.formatter.IAxisValueFormatter;
import com.github.mikephil.charting.interfaces.datasets.ILineDataSet;
import java.util.ArrayList;
import java.util.List;
import cn.xiaolongonly.mpchartsample.R;
import cn.xiaolongonly.mpchartsample.base.BaseActivity;
import cn.xiaolongonly.mpchartsample.chart.markview.DataMarkView;
import cn.xiaolongonly.mpchartsample.chart.util.ColorTemplate;
/**
* @author xiaolong
* @version v1.0
* @function <描述功能>
* @date 2016/12/6-9:10
*/
public class LineChartActivity1 extends BaseActivity {
private LineChart chart;
@Override
protected int getLayoutId() {
return R.layout.list_item_linechart;
}
@Override
protected void initView() {
chart = findView(R.id.chart);
ChartConfig();
//XY軸配置
XAxis xAxis = chart.getXAxis();
xAxis.setPosition(XAxis.XAxisPosition.BOTTOM); //定製X軸是在圖表上方還是下方。
xAxis.setDrawGridLines(false);
xAxis.setGranularity(1);
xAxis.setValueFormatter(new IAxisValueFormatter() {
@Override
public String getFormattedValue(float value, AxisBase axis) {
return (int) value + "年";
}
});
YAxis yAxisRight = chart.getAxisRight();
yAxisRight.setEnabled(false);
YAxis yAxisLeft = chart.getAxisLeft();
yAxisLeft.setAxisMinValue(0);
//動畫效果
chart.animateX(750);
chart.animateY(750);
}
/**
* 圖表的配置 一些提示和Legend
*/
private void ChartConfig() {
//設置覆蓋物
DataMarkView dataMarkView = new DataMarkView(this, 0, "");//自定義覆蓋物
chart.setMarkerView(dataMarkView);
//背景設置
chart.setDrawGridBackground(false);//表格背景繪製
chart.setBackgroundColor(getResources().getColor(R.color.chart_bg));
//Legend定製
chart.getLegend().setPosition(Legend.LegendPosition.ABOVE_CHART_LEFT);
chart.getLegend().setForm(Legend.LegendForm.CIRCLE);//Legend樣式
//圖表描述
chart.setDescription(null);
// 設置無數據文本提示
chart.setNoDataText(getResources().getString(R.string.chart_no_data));
//XY軸描述
chart.setXYDesc("年份", "總金額(元)");
//設置單方向和雙方向縮放 true x,y方向可以同時控制,false只能控制x方向的縮小放大或者Y方向的縮小放大
chart.setPinchZoom(true);
//填充數據
chart.setData(new LineData(generateLineDataSet()));
}
@Override
protected void setListener() {
}
private ILineDataSet generateLineDataSet() {
int color = ColorTemplate.PIE_COLORS[0];
LineDataSet dataSet = new LineDataSet(generateData(), "年度營業額曲線");
dataSet.setLineWidth(2.0f);
dataSet.setCircleRadius(3.5f);
dataSet.setDrawCircleHole(true);//填充圓
dataSet.setValueTextSize(9f);
dataSet.setHighlightLineWidth(2.0f);
dataSet.setDrawFilled(true);//區域顏色
dataSet.setFillAlpha(51);
dataSet.setFillColor(color); //填充色
dataSet.setHighLightColor(color); //選中十字線色
dataSet.setColor(color); //線條顏色
dataSet.setCircleColor(color); //圓點顏色
dataSet.setCircleColorHole(Color.WHITE);
dataSet.setCircleHoleRadius(2.0f);
dataSet.setDrawValues(false);
return dataSet;
}
private List<Entry> generateData() {
List<Entry> entryList = new ArrayList<>();
entryList.add(new Entry(2013, 1000));
entryList.add(new Entry(2014, 2000));
entryList.add(new Entry(2015, 3000));
entryList.add(new Entry(2016, 4000));
return entryList;
}
}
效果如下
將描述寫到了圖表框架中了。感覺明顯就好用多了。
在很多需求中,大部分圖表都是大同小異。我們並不想要將同種類型的圖表都按需求配置一遍,那樣代碼會顯得多且雜,所以有必要將這些配置寫成通用的配置。
下一篇會介紹圖表框架的簡單封裝。