繪製一張圖,包含線形圖,餅圖,柱狀圖等。可以設置各類配置,如顏色,字體大小,軸樣式,數據格式化等等。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版本升級帶來的影響