之前为添加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;
}
}
效果如下
将描述写到了图表框架中了。感觉明显就好用多了。
在很多需求中,大部分图表都是大同小异。我们并不想要将同种类型的图表都按需求配置一遍,那样代码会显得多且杂,所以有必要将这些配置写成通用的配置。
下一篇会介绍图表框架的简单封装。