編輯:關於Android編程
一.概述
MPAndroidChart是一款基於Android的開源圖表庫,MPAndroidChart不僅可以在Android設備上繪制各種統計圖表,而且可以對圖表進行拖動和縮放操作,應用起來非常靈活。MPAndroidChart同樣擁有常用的圖表類型:線型圖、餅圖、柱狀圖和散點圖。
GitHub地址:
https://github.com/PhilJay/MPAndroidChart
二.實例講解
下面先以BarChart為例講解一下在Chart類的基礎上,開發者為BarChart准備的實例化好的組成部分。
經過前兩篇文章的分析,我們知道一個基本的Chart實例應該是是有這麼幾部分組成的:
(1)DataRenderer(數據渲染器)
(2)Legend(圖例)
(3)Axis(坐標軸)
(4)Listener(一開始我們分析,Chart類的監聽器有兩個,但是經過源碼分析,我們得知Chart的基本監聽器有三個)
(5)Animator:動畫顯示 (6)Data:圖表數據(根源的數據怎麼能忘記呢) (7)MarkerView (8)HighLighter(高亮覆蓋顯示) (9)ViewPortHandler(暫時理解為繪圖區域) 下面呢,我將以BarChart為例講解Chart的這些基本組成部分都是怎麼被具像化,怎麼樣工作的: 第一步我們來講一下Chart的DataRenderer(數據渲染器) 首先我們看一下DataRenderer的主要成員變量和主要方法,DataRenderer是一個抽象類,他表明相對應於BarChart的BarChartDataRenderer需要繼承復寫他的方法,並且持有他的成員變量。public abstract class DataRenderer extends Renderer { protected ChartAnimator mAnimator; protected Paint mRenderPaint; protected Paint mHighlightPaint; protected Paint mDrawPaint; protected Paint mValuePaint; //聲明了圖表動畫,渲染器畫筆,高粱畫筆,數值畫筆等成員變量 public DataRenderer(ChartAnimator animator, ViewPortHandler viewPortHandler) { super(viewPortHandler); this.mAnimator = animator; //動畫對象和繪圖區域對象,並且將viewPortHandler和動畫對象設定 mRenderPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mRenderPaint.setStyle(Style.FILL); mDrawPaint = new Paint(Paint.DITHER_FLAG); mValuePaint = new Paint(Paint.ANTI_ALIAS_FLAG); mValuePaint.setColor(Color.rgb(63, 63, 63)); mValuePaint.setTextAlign(Align.CENTER); mValuePaint.setTextSize(Utils.convertDpToPixel(9f)); mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mHighlightPaint.setStyle(Paint.Style.STROKE); mHighlightPaint.setStrokeWidth(2f); mHighlightPaint.setColor(Color.rgb(255, 187, 115));//初始化了幾個畫筆 } protected void applyValueTextStyle(IDataSet set) { mValuePaint.setTypeface(set.getValueTypeface()); mValuePaint.setTextSize(set.getValueTextSize()); } /** 初始化Buffers,buffer是用來進行尺寸變換的一個類,他和transformer類配合生成實際的尺寸 */ public abstract void initBuffers(); /**一個用來繪制數據顯示的抽象方法,具體的實現用它的子類來決定 *例如我現在講解的子類是BarChart,那麼它對應的BarChartDataRenderer 實現該方法用來繪制對應高度的柱形 */ public abstract void drawData(Canvas c); /**繪制所有實體的數值用來顯示 */ public abstract void drawValues(Canvas c); /** *繪制指定實體的數值用來顯示,該實體的數值可以被Formatter進行轉化 * */ public void drawValue(Canvas c, ValueFormatter formatter, float value, Entry entry, int dataSetIndex, float x, float y, int color) { mValuePaint.setColor(color); c.drawText(formatter.getFormattedValue(value, entry, dataSetIndex, mViewPortHandler), x, y, mValuePaint); } /** *繪制圖表的獨有的顯示,根據方法名可以很容易的理解,例如在餅狀圖中繪制中間的圓形和在中間圓形中顯示的文字 */ public abstract void drawExtras(Canvas c); /*@param indices the highlighted values */ public abstract void drawHighlighted(Canvas c, Highlight[] indices); }
沒見過比CSDN還傻的編輯器,講究著看吧 DataRenderer類的基本成員變量和基本抽象方法已經分析完了,在DataRenderer類中繪制的任務主要分為三部分drawData(),drawValues(),drawExtras(),另外還有一個定制的任務就是,如果圖表想在自己一初始化的時候就顯示一個高亮的item,那麼它就要復寫drawHighlighted()這個方法。 然後我們再去看BarChartDataRenderer做一個沒有子類繼承的具像化的類,他是怎麼具體去繪制我們的圖表的呢?
protected BarDataProvider mChart; /** the rect object that is used for drawing the bars */ protected RectF mBarRect = new RectF(); protected BarBuffer[] mBarBuffers;//有自己的buffer實體,這裡面主要存儲的是item的相對位置數據,也就是說裡面的數據不是實際的需要和transformer//類結合才能生成真正的位置數據 protected Paint mShadowPaint; protected Paint mBarBorderPaint;//自己用新建了兩個畫筆 public BarChartRenderer(BarDataProvider chart, ChartAnimator animator, ViewPortHandler viewPortHandler) { super(animator, viewPortHandler); this.mChart = chart; mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mHighlightPaint.setStyle(Paint.Style.FILL); mHighlightPaint.setColor(Color.rgb(0, 0, 0)); // set alpha after color mHighlightPaint.setAlpha(120); mShadowPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mShadowPaint.setStyle(Paint.Style.FILL); mBarBorderPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mBarBorderPaint.setStyle(Paint.Style.STROKE);//初始化的這個地方沒有什麼新奇的,主要是初始化了自己特別需要的幾個花臂,傳入的動畫和繪圖區//域 } @Override public void initBuffers() {//初始化buffer數據 BarData barData = mChart.getBarData(); mBarBuffers = new BarBuffer[barData.getDataSetCount()];//根據barData的DataSet的數量設置 //mBarBuffers的size,也就是說一個buffer對應一個DataSet
for (int i = 0; i < mBarBuffers.length; i++) { IBarDataSet set = barData.getDataSetByIndex(i); mBarBuffers[i] = new BarBuffer(set.getEntryCount() * 4 * (set.isStacked() ? set.getStackSize() : 1), barData.getGroupSpace(), barData.getDataSetCount(), set.isStacked()); //輸入的第一個參數這個是buffer的size,buffer中存儲的是每一個item的位置數據 //所以說每一個item的上下左右四個位置數據都會存儲在buffer內置的數組中,所以說buffer的size是DataSet中Data的四倍,當然如果set中有Stack的話,buffer的size還可能會更大。第二個參數值得是barData數據中存儲的每一個item之間的位置間距。第三個數據值得是barData中DataSet中數量,被傳入buffer中用來計算偏移 //的數值。第四個數值傳入的是這個DataSet是否存在Stack。通過初始化buffer,我們為計算出數據中每一個item的相對位置數據做好了准備。 } }
然後我們進入到BarBuffer,來看一下它的初始化:
public class BarBuffer extends AbstractBuffer沒有什麼特殊的方法,但是有一個比較重要的方法AddBar(),他傳入的是上下左右的位置信息,並且將它加入到內置的buffer數組中,buffer數組是BarBuffer的父抽象類AbstractBuffer的內置對象,BarBuffer繼承並實現了AbstractBuffer。{ protected float mBarSpace = 0f; protected float mGroupSpace = 0f; protected int mDataSetIndex = 0; protected int mDataSetCount = 1; protected boolean mContainsStacks = false; protected boolean mInverted = false; public BarBuffer(int size, float groupspace, int dataSetCount, boolean containsStacks) { super(size); this.mGroupSpace = groupspace; this.mDataSetCount = dataSetCount; this.mContainsStacks = containsStacks; } public void setBarSpace(float barspace) { this.mBarSpace = barspace; } public void setDataSet(int index) { this.mDataSetIndex = index; } public void setInverted(boolean inverted) { this.mInverted = inverted; } protected void addBar(float left, float top, float right, float bottom) { buffer[index++] = left; buffer[index++] = top; buffer[index++] = right; buffer[index++] = bottom; }
@Override public void drawData(Canvas c) {//在這個方法裡,BarDataRenderer根據DataSet的數量進入一個for循環,對每一個DataSet的數據進行繪制 BarData barData = mChart.getBarData(); for (int i = 0; i < barData.getDataSetCount(); i++) { IBarDataSet set = barData.getDataSetByIndex(i); if (set.isVisible() && set.getEntryCount() > 0) { drawDataSet(c, set, i);//這裡才是真的是繪制Dataset的地方,屬於核心方法。 } } } protected void drawDataSet(Canvas c, IBarDataSet dataSet, int index) { Transformer trans = mChart.getTransformer(dataSet.getAxisDependency());//首先聲明了一個Transformer的對象,此對象之後和BarBuffer對象//配合計算出item的實際位置數據 mShadowPaint.setColor(dataSet.getBarShadowColor()); mBarBorderPaint.setColor(dataSet.getBarBorderColor()); mBarBorderPaint.setStrokeWidth(Utils.convertDpToPixel(dataSet.getBarBorderWidth())); final boolean drawBorder = dataSet.getBarBorderWidth() > 0.f; float phaseX = mAnimator.getPhaseX(); float phaseY = mAnimator.getPhaseY(); // initialize the buffer BarBuffer buffer = mBarBuffers[index]; buffer.setPhases(phaseX, phaseY); buffer.setBarSpace(dataSet.getBarSpace()); buffer.setDataSet(index); buffer.setInverted(mChart.isInverted(dataSet.getAxisDependency())); buffer.feed(dataSet);//將DataSet的數據傳入到buffer中去 trans.pointValuesToPixel(buffer.buffer);//通過Transformer對象將相對位置數據轉換為真實位置數據 // draw the bar shadow before the values if (mChart.isDrawBarShadowEnabled()) {//判斷條價位是否可以繪制item陰影 for (int j = 0; j < buffer.size(); j += 4) { if (!mViewPortHandler.isInBoundsLeft(buffer.buffer[j + 2]))//位置處於繪圖區域mViewPortHandler的左邊界 continue; if (!mViewPortHandler.isInBoundsRight(buffer.buffer[j]))//位置處於繪圖區域mViewPortHandler的右邊界 break; c.drawRect(buffer.buffer[j], mViewPortHandler.contentTop(), buffer.buffer[j + 2], mViewPortHandler.contentBottom(), mShadowPaint);//根據實際位置數據繪制item所在柱形圖的陰影 } } // if multiple colors if (dataSet.getColors().size() > 1) {//如果item的顯示顏色有多種 for (int j = 0; j < buffer.size(); j += 4) { if (!mViewPortHandler.isInBoundsLeft(buffer.buffer[j + 2]))//位置處於繪圖區域mViewPortHandler的左邊界 continue; if (!mViewPortHandler.isInBoundsRight(buffer.buffer[j]))//位置處於繪圖區域mViewPortHandler的右邊界 break; // Set the color for the currently drawn value. If the index // is out of bounds, reuse colors. mRenderPaint.setColor(dataSet.getColor(j / 4));//根據存儲的顏色信息,設置渲染器畫筆的顏色 c.drawRect(buffer.buffer[j], buffer.buffer[j + 1], buffer.buffer[j + 2], buffer.buffer[j + 3], mRenderPaint);//繪制item所在的矩形 if (drawBorder) { c.drawRect(buffer.buffer[j], buffer.buffer[j + 1], buffer.buffer[j + 2], buffer.buffer[j + 3], mBarBorderPaint);//如果屬性標記還需要繪制描邊,則使用秒變畫筆繪制描邊 } } } else {//如果只有一種顏色,那麼進行繪制 mRenderPaint.setColor(dataSet.getColor()); for (int j = 0; j < buffer.size(); j += 4) { if (!mViewPortHandler.isInBoundsLeft(buffer.buffer[j + 2])) continue; if (!mViewPortHandler.isInBoundsRight(buffer.buffer[j])) break; c.drawRect(buffer.buffer[j], buffer.buffer[j + 1], buffer.buffer[j + 2], buffer.buffer[j + 3], mRenderPaint); if (drawBorder) { c.drawRect(buffer.buffer[j], buffer.buffer[j + 1], buffer.buffer[j + 2], buffer.buffer[j + 3], mBarBorderPaint); } } } }
buffer.feed(dataSet);//將DataSet的數據傳入到buffer中去這是在DrawDataSet()方法中將DataSet數據通過feed方法傳入到BarBuffer中,然後DataSet數據在BarBuffer中進行了下面的處理
@Override public void feed(IBarDataSet data) { float size = data.getEntryCount() * phaseX;//首先計算出DataSet中數據的個數 int dataSetOffset = (mDataSetCount - 1); float barSpaceHalf = mBarSpace / 2f;//得到每一個柱狀圖之間的間距的一半 float groupSpaceHalf = mGroupSpace / 2f;//每一個DataSet數據分組之間的間隔的一半 float barWidth = 0.5f;//設置柱狀圖item的寬度 for (int i = 0; i < size; i++) { BarEntry e = data.getEntryForIndex(i); // calculate the x-position, depending on datasetcount float x = e.getXIndex() + e.getXIndex() * dataSetOffset + mDataSetIndex + mGroupSpace * e.getXIndex() + groupSpaceHalf;//item的所在的X軸方向上的位置,計算的方式是首先得到這個實體在所在DataSet//中的位置,然後加上每個Data實體之間的偏移量,再加上DataSet的位置數據,再加上每個DataSet之間的間隔,再加上每個DataSet的之間間距的一半,從而得到的數據//是每個item所在的中間位置,你會發現其一個實體的繪制位置與後一個實體繪制位置的差是1+DataSetOffset+mGroupSpace,這樣的位置足以使每一個實體item不會重//疊,在這個實例中每一個item的寬度為0.68,在dataSetoffset ,mGroupSpace ,groupSpaceHalf的值都為零的情況下,每個item中間位置的相差為1 float y = e.getVal();//Y軸方向上的數據就是實體的值 float [] vals = e.getVals(); if (!mContainsStacks || vals == null) { float left = x - barWidth + barSpaceHalf; float right = x + barWidth - barSpaceHalf;//右端減去左端的結果是barWidth*2 - barSpace float bottom, top; if (mInverted) { bottom = y >= 0 ? y : 0; top = y <= 0 ? y : 0; } else { top = y >= 0 ? y : 0; bottom = y <= 0 ? y : 0; } // multiply the height of the rect with the phase if (top > 0) top *= phaseY; else bottom *= phaseY; addBar(left, top, right, bottom);//將計算的位置數據加入到buffer數組中,你要知到的是barWidth是0.5,計算的位置之間相差頁大約不//足2的數量,這些全部都是相對數據。根據真實的數據,每一個item的寬度是0.68,每一個item中間位置的差距是1 } else {//這種情況也就是說是有stack數據的時候,一個是實體中包含有多個數據時所執行的繪制邏輯 float posY = 0f; float negY = -e.getNegativeSum(); float yStart = 0f; // fill the stack for (int k = 0; k < vals.length; k++) { float value = vals[k]; if(value >= 0f) { y = posY; yStart = posY + value; posY = yStart; } else { y = negY; yStart = negY + Math.abs(value); negY += Math.abs(value); } float left = x - barWidth + barSpaceHalf; float right = x + barWidth - barSpaceHalf; float bottom, top; if (mInverted) { bottom = y >= yStart ? y : yStart; top = y <= yStart ? y : yStart; } else { top = y >= yStart ? y : yStart; bottom = y <= yStart ? y : yStart; } // multiply the height of the rect with the phase top *= phaseY; bottom *= phaseY; addBar(left, top, right, bottom); } } } reset(); }
public void pointValuesToPixel(float[] pts) { mMatrixValueToPx.mapPoints(pts); mViewPortHandler.getMatrixTouch().mapPoints(pts); mMatrixOffset.mapPoints(pts); }在這裡我們看到傳入進此方法的的buffer數組經歷了Matrix位置變化,兩個Matrix是在transformer中的,另外一個是在viewport handler中的Matrix,通過這三個Matrix的變化,我們將相對位置數據變成了實際位置數據。那我們來看一下這三個Matrix的內容都是怎麼初始化的吧。 首先是mMatrixValueToPx這個數組:
public void prepareMatrixValuePx(float xChartMin, float deltaX, float deltaY, float yChartMin) { float scaleX = (float) ((mViewPortHandler.contentWidth()) / deltaX);//X軸的縮放數值是用DataSet中的實體數量和整個繪圖區域的寬度進行//比較相除得到的,也就是說假設contentWidth為1080,而我們的實體個數位9時,那麼我們的縮放比例時120倍,但實體個數位18時,我們的縮放比例也就是60倍。所以在這個地方我們的item寬度大小根據實體的數量進行了自適應 float scaleY = (float) ((mViewPortHandler.contentHeight()) / deltaY); if (Float.isInfinite(scaleX)) { scaleX = 0; } if (Float.isInfinite(scaleY)) { scaleY = 0; } // setup all matrices mMatrixValueToPx.reset(); mMatrixValueToPx.postTranslate(-xChartMin, -yChartMin); mMatrixValueToPx.postScale(scaleX, - scaleY);//將Matrix進行行了縮放變換 }使用這個方法將mMatrixValueToPx對相對數據進行了第一次變化。
public void prepareMatrixOffset(boolean inverted) { mMatrixOffset.reset(); // offset.postTranslate(mOffsetLeft, getHeight() - mOffsetBottom); if (!inverted) mMatrixOffset.postTranslate(mViewPortHandler.offsetLeft(), mViewPortHandler.getChartHeight() - mViewPortHandler.offsetBottom()); else { mMatrixOffset .setTranslate(mViewPortHandler.offsetLeft(), -mViewPortHandler.offsetTop()); mMatrixOffset.postScale(1.0f, -1.0f); } }
/** * matrix used for touch events */ protected final Matrix mMatrixTouch = new Matrix();我們得到的是這個Matrix,這個Matrix在注釋中寫道的作用是對手勢操作進行的變化,也就是說當我們使用手勢對圖表進行平移或者擴大縮小的時候進行變化的Matrix。 經過這三個Matrix對得到的相對數據進行了變化之後,我們得到了我們想要繪制的數據Item的真正位置。然後才是在drawDataSet()方法中才可以判斷真實位置,然後根據位置對Item進行了繪制。 好了 我們到這裡完成了對數據的繪制,然後我們來准備一下數據被高亮時的樣式:
protected void prepareBarHighlight(float x, float y1, float y2, float barspaceHalf, Transformer trans) { float barWidth = 0.5f; float left = x - barWidth + barspaceHalf; float right = x + barWidth - barspaceHalf; float top = y1; float bottom = y2; mBarRect.set(left, top, right, bottom); trans.rectValueToPixel(mBarRect, mAnimator.getPhaseY()); }在這裡我們准備高亮顯示的矩形區域,並且根據我們的設置,設置了顏色等信息; 然後
public void drawHighlighted(Canvas c, Highlight[] indices) { BarData barData = mChart.getBarData(); int setCount = barData.getDataSetCount(); for (Highlight high : indices) { final int minDataSetIndex = high.getDataSetIndex() == -1 ? 0 : high.getDataSetIndex(); final int maxDataSetIndex = high.getDataSetIndex() == -1 ? barData.getDataSetCount() : (high.getDataSetIndex() + 1); if (maxDataSetIndex - minDataSetIndex < 1) continue; for (int dataSetIndex = minDataSetIndex; dataSetIndex < maxDataSetIndex; dataSetIndex++) { IBarDataSet set = barData.getDataSetByIndex(dataSetIndex); if (set == null || !set.isHighlightEnabled()) continue; float barspaceHalf = set.getBarSpace() / 2f; Transformer trans = mChart.getTransformer(set.getAxisDependency()); mHighlightPaint.setColor(set.getHighLightColor()); mHighlightPaint.setAlpha(set.getHighLightAlpha()); int index = high.getXIndex(); // check outofbounds if (index >= 0 && index < (mChart.getXChartMax() * mAnimator.getPhaseX()) / setCount) { BarEntry e = set.getEntryForXIndex(index); if (e == null || e.getXIndex() != index) continue; float groupspace = barData.getGroupSpace(); boolean isStack = high.getStackIndex() < 0 ? false : true; // calculate the correct x-position float x = index * setCount + dataSetIndex + groupspace / 2f + groupspace * index;//計算好要高亮的item在X軸上的顯示位置 final float y1; final float y2; if (isStack) { y1 = high.getRange().from; y2 = high.getRange().to; } else { y1 = e.getVal(); y2 = 0.f; }//計算好需要高亮的位置 prepareBarHighlight(x, y1, y2, barspaceHalf, trans); c.drawRect(mBarRect, mHighlightPaint); if (mChart.isDrawHighlightArrowEnabled()) { mHighlightPaint.setAlpha(255); // distance between highlight arrow and bar float offsetY = mAnimator.getPhaseY() * 0.07f; float[] values = new float[9]; trans.getPixelToValueMatrix().getValues(values); final float xToYRel = Math.abs( values[Matrix.MSCALE_Y] / values[Matrix.MSCALE_X]); final float arrowWidth = set.getBarSpace() / 2.f; final float arrowHeight = arrowWidth * xToYRel; final float yArrow = (y1 > -y2 ? y1 : y1) * mAnimator.getPhaseY(); Path arrow = new Path(); arrow.moveTo(x + 0.4f, yArrow + offsetY); arrow.lineTo(x + 0.4f + arrowWidth, yArrow + offsetY - arrowHeight); arrow.lineTo(x + 0.4f + arrowWidth, yArrow + offsetY + arrowHeight); trans.pathValueToPixel(arrow); c.drawPath(arrow, mHighlightPaint); } } } } }
@Override public void drawValues(Canvas c) { // if values are drawn if (passesCheck()) { ListdataSets = mChart.getBarData().getDataSets(); final float valueOffsetPlus = Utils.convertDpToPixel(4.5f); float posOffset = 0f; float negOffset = 0f; boolean drawValueAboveBar = mChart.isDrawValueAboveBarEnabled(); for (int i = 0; i < mChart.getBarData().getDataSetCount(); i++) { IBarDataSet dataSet = dataSets.get(i); if (!dataSet.isDrawValuesEnabled() || dataSet.getEntryCount() == 0) continue; // apply the text-styling defined by the DataSet applyValueTextStyle(dataSet); boolean isInverted = mChart.isInverted(dataSet.getAxisDependency()); // calculate the correct offset depending on the draw position of // the value float valueTextHeight = Utils.calcTextHeight(mValuePaint, "8"); posOffset = (drawValueAboveBar ? -valueOffsetPlus : valueTextHeight + valueOffsetPlus); negOffset = (drawValueAboveBar ? valueTextHeight + valueOffsetPlus : -valueOffsetPlus); if (isInverted) { posOffset = -posOffset - valueTextHeight; negOffset = -negOffset - valueTextHeight; } Transformer trans = mChart.getTransformer(dataSet.getAxisDependency()); float[] valuePoints = getTransformedValues(trans, dataSet, i);//生成item數值繪制位置 // if only single values are drawn (sum) if (!dataSet.isStacked()) { for (int j = 0; j < valuePoints.length * mAnimator.getPhaseX(); j += 2) { if (!mViewPortHandler.isInBoundsRight(valuePoints[j])) break; if (!mViewPortHandler.isInBoundsY(valuePoints[j + 1]) || !mViewPortHandler.isInBoundsLeft(valuePoints[j])) continue; BarEntry entry = dataSet.getEntryForIndex(j / 2); float val = entry.getVal(); drawValue(c, dataSet.getValueFormatter(), val, entry, i, valuePoints[j], valuePoints[j + 1] + (val >= 0 ? posOffset : negOffset), dataSet.getValueTextColor(j / 2)); } // if we have stacks這種情況我們不在分析有Stack的情況 } } }
float[] valuePoints = getTransformedValues(trans, dataSet, i);//生成item數值繪制位置這個地方我們生成了item數值繪制位置的數據:
public float[] getTransformedValues(Transformer trans, IBarDataSet data, int dataSetIndex) { return trans.generateTransformedValuesBarChart(data, dataSetIndex, mChart.getBarData(), mAnimator.getPhaseY()); }
drawValue(c, dataSet.getValueFormatter(), val, entry, i, valuePoints[j], valuePoints[j + 1] + (val >= 0 ? posOffset : negOffset), dataSet.getValueTextColor(j / 2));
public void drawValue(Canvas c, ValueFormatter formatter, float value, Entry entry, int dataSetIndex, float x, float y, int color) { mValuePaint.setColor(color); c.drawText(formatter.getFormattedValue(value, entry, dataSetIndex, mViewPortHandler), x, y, mValuePaint); }分析到現在,我們將Item柱狀圖繪制完畢,高亮的item也進行了繪制,item上的顯示數值也進行了繪制,我們完成了對圖表類中主體的繪制,下面我們將對坐標軸,圖例等輔助部分進行分析。最後我們在對動畫和監聽器進行分析。
Volley 是一個 HTTP 庫,它能夠幫助 Android app 更方便地執行網絡操作,最重要的是,它更快速高效。我們可以通過開源的 AOSP 倉庫獲取到 Voll
前言好幾個月之前關於Android App熱補丁修復火了一把,源於QQ空間團隊的一篇文章安卓App熱補丁動態修復技術介紹,然後各大廠的開源項目都出來了,本文的實踐基於Ho
相信大家都有使用九宮格解鎖,比如在設置手機安全項目中,可以使用九宮格解鎖,提高安全性,以及在使用支付功能的時候,為了提高安全使用九宮鎖,今天就為大家介紹Android實現
本文只是寫了如何配置JDK,以及adt-bundle的配置。對於以前的adt-bundle的版本,會自帶CPU/ABI系統鏡像,經過本文所描述的兩個步驟後可以直接創建AV