Android MPAndroidChart使用教程和源码分析(三)

一.概述

    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<IBarDataSet> {

    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;
    }

没有什么特殊的方法,但是有一个比较重要的方法AddBar(),他传入的是上下左右的位置信息,并且将它加入到内置的buffer数组中,buffer数组是BarBuffer的父抽象类AbstractBuffer的内置对象,BarBuffer继承并实现了AbstractBuffer。

然后我们接着来看BarDataRenderer:

@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);
                }
            }
        }
    }

上面的DrawDataSet()方法根据已经计算好的位置数据将具体一个DataSet中的数据绘制成了对应的柱状图item,那么位置数据是怎么得到的呢,下面将给你答案:

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();
    }

好了通过feed方法,我们将每一个item的相对位置数据(上下左右)加入到了buffer数组中,但是这个位置数据是不能够直接使用的,要经过transformer进行转换才能变成实际的数据,那么我们来看一下transformer中都做了什么:首先在drawDataSet方法中使用了transformer的pointValuesToPixel()这个方法。

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);
        }
    }

使用这个方法对mMatrixOffset这个Matrix进行了第二次变化 然后我们看一下mViewPortHandler.getMatrixTouch()这个方法返回的Matrix是什么:

/**
     * 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);
                    }
                }
            }
        }
    }

复写了drawHighlighted()方法,使用mHighlightPaint对要显示成高亮的item进行了高亮显示,indices中存储的是需要高亮显示的位置。

接下来我们分析是如何进行某个item数值的绘制的:

 @Override
    public void drawValues(Canvas c) {
        // if values are drawn
        if (passesCheck()) {

            List<IBarDataSet> dataSets = 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));
                   

然后我们根据得到的位置数据和自定义的属性对数值进行了绘制。 drawValue方法是父抽象类DataRenderer中定义的方法:

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上的显示数值也进行了绘制,我们完成了对图表类中主体的绘制,下面我们将对坐标轴,图例等辅助部分进行分析。最后我们在对动画和监听器进行分析。

    原文作者:Android源码分析
    原文地址: https://blog.csdn.net/baidu_34934230/article/details/52396193
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞