绘制一张图,包含线形图,饼图,柱状图等。可以设置各类配置,如颜色,字体大小,轴样式,数据格式化等等。canvas
能够经过继承和组合的方式选择具体的图表类型进行使用。若是将图表抽象成一个View来看,整个流程会比较清晰。大体分为如下几个步骤bash
若是对MP的Api使用有必定的了解后,经过以上的步骤就能轻松的实现一张图的绘制。架构
代码示例:ide
Step1:
private void initChart() {
mLineChart.setTouchEnabled(false);
mLineChart.setDragEnabled(false);
mLineChart.setScaleEnabled(false);
YAxis rightAxis = mLineChart.getAxisRight();
rightAxis.setEnabled(false);
XAxis xAxis = mLineChart.getXAxis();
xAxis.setDrawGridLines(false);
xAxis.setPosition(XAxis.XAxisPosition.BOTTOM);
xAxis.setDrawLabels(true);
xAxis.setAvoidFirstLastClipping(false);
xAxis.setGranularityEnabled(true);
xAxis.setValueFormatter(xAxisFormatter);
xAxis.setYOffset(4f);
.....
}
Step2:
private LineData generateLineData(XXModel model) {
List<List<Entry>> lists = model.getLineDatas();
List<ILineDataSet> lineDataSets = new ArrayList<>();
for (int i = 0; i < lists.size(); i++) {
LineDataSet dataSet = new LineDataSet(lists.get(i), "LineDataSet Num:" + i);
dataSet.setAxisDependency(YAxis.AxisDependency.LEFT);
dataSet.setDrawValues(false);
dataSet.setDrawCircles(true);
dataSet.setDrawCircleHole(false);
dataSet.setCircleRadius(lineCircleRadius);
dataSet.setLineWidth(lineWidth);
dataSet.setValueTextSize(lineTextSize);
lineDataSets.add(dataSet);
}
return new LineData(lineDataSets);
}
Step3:
LineData lineData = generateLineData(model);
mLineChart.setData(lineData);
mLineChart.postInvalidate();
复制代码
经过以上流程咱们可使用MP作出一张图表。可是若是可能咱们的数据不是一成不变的。可能随着时间推移,数据量增大。或者随着手势滑动,须要加载新的数据。咱们能够经过两种方式动态更新数据(仅仅是据我所知):post
说到底就是就是让Chart从新绘制了一次。具体怎样根据需求增长/更新数据才是重点。对于Chart来讲,仅仅是重绘操做。学习
关于图表具体的配置选项主要能够分为三类:字体
了解相关具体的配置须要深刻其中查看详细的源码。而对Api的源码有必定的了解后,会为后面对MP的扩展打好基础。由于图表最终的绘制过程,都须要结合各类配置条件进行相应的绘制。关于MP中经常使用的属性设置和方式作了一份整理,详细状况能够了解这里ui
知道了如何使用MP,以及大体结构后。接下来即是进一步分析MP的绘制l流程。在次以前,咱们先思考几个问题,而后带着这几个问题继续往下研:this
首先咱们不要被“图表”这个词误导,说到底也就是一个View视图。Chart自己只是一个ViewGroup。只不过是由许多部分一一组成的。粗略的画了张原型图:spa
从原型图来看,Chart自己是个大容器或者说是一个组合体。大体由组件和内容两部分组成。组件主要包括XY轴、Legend、限制线等。组件能够单独进行设置。内容主要包括图表自己和数据的渲染。Chart包含一个或者多个组件,Chart会在适当的时机(实际计算/绘制的时候)通知组件作相应的操做。从前面的代码使用示例中也能够看出这一点。
其次要明确一个概念,Chart自己“不作任何计算和绘制的操做”。这里之因此用双引号的缘由是由于在面向对象的思想上Chart只作事件执行的分发者,具体的数据计算、数据与像素位置转换、内容绘制等操做都是由另外的对象执行(对应MP中的各类Renderer)。有了以上的概念之后,来分析从setData到Chart到呈现到视图上之间的整个过程,直接上图:
图中的BarLineChartBase是Chart的直接实现类。看图可能比较懵逼,我大体的梳理成几个流程:
根据时序图和以上的流程咱们再来看开始提出的几个问题
其实最后的绘制操做仍是调用系统Canvas提供的一系列绘制方法完成(主要图中蓝色流程线),因此对于这个问题的疑惑点更应该是,从接口拿到一堆数据,图表是怎么知道要绘制在哪里的,对应到手机坐标系中的哪一个位置的。 注意图中的两条绿色流程线(ps:不一样类型的操做特地作了颜色区分,良心吧)。calcMinMax()的实际做用上是通知XY轴从新计算起最大最小值和区间range。
@Override
protected void calcMinMax() {
mXAxis.calculate(mData.getXMin(), mData.getXMax());
// calculate axis range (min / max) according to provided data
mAxisLeft.calculate(mData.getYMin(AxisDependency.LEFT), mData.getYMax(AxisDependency.LEFT));
mAxisRight.calculate(mData.getYMin(AxisDependency.RIGHT), mData.getYMax(AxisDependency
.RIGHT));
}
public void calculate(float dataMin, float dataMax) {
// if custom, use value as is, else use data value
float min = mCustomAxisMin ? mAxisMinimum : (dataMin - mSpaceMin);
float max = mCustomAxisMax ? mAxisMaximum : (dataMax + mSpaceMax);
// temporary range (before calculations)
float range = Math.abs(max - min);
// in case all values are equal
if (range == 0f) {
max = max + 1f;
min = min - 1f;
}
this.mAxisMinimum = min;
this.mAxisMaximum = max;
// actual range
this.mAxisRange = Math.abs(max - min);
}
复制代码
而calculateOffsets()作了两件事:
说到这里不得不提MP中一个十分重要的类ViewPortHandler.咱们能够将ViewPortHandler理解为一个内存区域。Chart将自身一个属性,好比高宽、大小、缩放比等,存在这个“内存”中。其余对象想获取这些属性,经过ViewPortHandler就能够获取到。
为何要画蛇添足使用ViewPortHandler来存储这些信息呢?还记得前面说过的MP是一个组合体吗,可能多个地方都须要使用到这些属性,而ViewPortHandler正好保证了多个对象获取到的Chart属性是一致的。
回过头来继续看calculateOffsets()作了那些事。直接看代码:
@Override
public void calculateOffsets() {
if (!mCustomViewPortEnabled) {
float offsetLeft = 0f, offsetRight = 0f, offsetTop = 0f, offsetBottom = 0f;
calculateLegendOffsets(mOffsetsBuffer);
offsetLeft += mOffsetsBuffer.left;
offsetTop += mOffsetsBuffer.top;
offsetRight += mOffsetsBuffer.right;
offsetBottom += mOffsetsBuffer.bottom;
// offsets for y-labels
if (mAxisLeft.needsOffset()) {
offsetLeft += mAxisLeft.getRequiredWidthSpace(mAxisRendererLeft
.getPaintAxisLabels());
}
if (mAxisRight.needsOffset()) {
offsetRight += mAxisRight.getRequiredWidthSpace(mAxisRendererRight
.getPaintAxisLabels());
}
if (mXAxis.isEnabled() && mXAxis.isDrawLabelsEnabled()) {
float xLabelHeight = mXAxis.mLabelRotatedHeight + mXAxis.getYOffset();
// offsets for x-labels
if (mXAxis.getPosition() == XAxisPosition.BOTTOM) {
offsetBottom += xLabelHeight;
} else if (mXAxis.getPosition() == XAxisPosition.TOP) {
offsetTop += xLabelHeight;
} else if (mXAxis.getPosition() == XAxisPosition.BOTH_SIDED) {
offsetBottom += xLabelHeight;
offsetTop += xLabelHeight;
}
}
offsetTop += getExtraTopOffset();
offsetRight += getExtraRightOffset();
offsetBottom += getExtraBottomOffset();
offsetLeft += getExtraLeftOffset();
float minOffset = Utils.convertDpToPixel(mMinOffset);
mViewPortHandler.restrainViewPort(
Math.max(minOffset, offsetLeft),
Math.max(minOffset, offsetTop),
Math.max(minOffset, offsetRight),
Math.max(minOffset, offsetBottom));
if (mLogEnabled) {
Log.i(LOG_TAG, "offsetLeft: " + offsetLeft + ", offsetTop: " + offsetTop
+ ", offsetRight: " + offsetRight + ", offsetBottom: " + offsetBottom);
Log.i(LOG_TAG, "Content: " + mViewPortHandler.getContentRect().toString());
}
}
prepareOffsetMatrix();
prepareValuePxMatrix();
}
复制代码
经过一系列的操做计算出Chart内容区域的offset,而后经过ViewPortHandler.restrainViewPort()重置Chart内容区域大小。 这是作的第一件事(从新计算Chart的内容大小)。而接下来的prepareOffsetMatrix和prepareValuePxMatrix则作了第二件事(计算数据和像素的缩放比例)。一样整理一张图来辅助理解这个过程:
在notifyDataSetChange的时候经过调用到Transformer的prepareMatrixXXX()方法设置好Transformer.Matrix的平移缩放比。而后在真正执行绘制操做的时候,再使用Transformer计算出实际的绘制坐标区域。
Transform.使用Matrix完成 "value-touch-offset" 过程。也就是数据值到像素值的映射关系。
经过阅读源码可知。在Renderer中具体执行绘制操做的时候。会根据咱们以前设置的属性执行相关的操做。好比若是设置了dataSet.isDrawFilledEnabled为true,则会执行drawLinearFill方法。在使用canvas.drawLines时会使用咱们经过的dataSet.setColors使用的颜色等等。具体的操做能够根据须要深刻源码了解。ps:下一次Draw生效
对我而言,评论一个第三方库到底好很差的原则不彻底在于它的功能是否完美。而在于它的设计以及他的扩展到底好很差。做为第三库被应用的场景是多种多样的,若是可以作到尽量的“适合”运用到项目中,并可以自由的给使用者进行扩展,这样的设计和架构才是真正最具备学习异议的。相信经过以上分析,对于这个问题应该有了属于本身的看法了。
MP支持对拖拽,缩放,平移等操做。内部已经实现了具体的细节,并提供了相应的“开关”供使用者选择。并提供了相应的接口回调具体的细节到外层,外层只需提供具体的回调接口便可。整理了下BarLineChartTouchListener类的onTouch方法流程以下:
MP原本提供了许多功能和Api接口。总体的功能很是丰富和完成。可是大多数状况下,实际需求须要咱们进一步的对MP进行扩展和疯子。好在MP的可扩展性很是良好,咱们日常对MP扩展主要分为三种方式:
在实际的使用场景中,根据具体的业务逻辑选择一到多种的扩展方式进行结合达到咱们的需求。可是总体均保持不动MP源码的基础上进行扩展和封装。以便于之后兼任MP版本升级带来的影响