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

一.概述

    MPAndroidChart是一款基于Android的开源图表库,MPAndroidChart不仅可以在Android设备上绘制各种统计图表,而且可以对图表进行拖动和缩放操作,应用起来非常灵活。MPAndroidChart同样拥有常用的图表类型:线型图、饼图、柱状图和散点图。

GitHub地址:

https://github.com/PhilJay/MPAndroidChart

二.以BarChart为例进行源码讲解

MPAndroidChart中集合了特别多的Chart类型,当然了我们常用的几个图表仅仅只有那么几种对吧,比如说像线性图,就像你高中时候的函数图。比如说饼状图,这个你也应该知道吧,很常见的一种图表。还有一种图表雷达图,知道不?就是这个样子的:

《Android MPAndroidChart使用教程和源码分析(二)》

见识过吧,这种图表。然后还有一种就是我们今天让讲解的BarChart,柱状图。

我们先看一下BarChart是怎么在XML里面怎么得到的:

(1)首先我们在我的XML资源文件里添加上BarChart,这个在上一篇博客中也提到了,很简单。

<com.github.mikephil.charting.charts.BarChart
        android:id="@+id/chart1"
        android:layout_width="match_parent"
        android:layout_height="360dp" /></span>

(2)然后我们在View中得到这个对象。

       mChart = (BarChart) findViewById(R.id.chart1);

(3)然后我们找到BarChart这个对象的类,我们看看他里面到底有什么呢,之前我们有分析过大体是有什么呢,会不会和我们的猜测差不多呢。

public class BarChart extends BarLineChartBase<BarData> implements BarDataProvider {

	/** flag that enables or disables the highlighting arrow */
	private boolean mDrawHighlightArrow = false;

	/**
	 * if set to true, all values are drawn above their bars, instead of below their top
	 */
	private boolean mDrawValueAboveBar = true;

	/**
	 * if set to true, a grey area is drawn behind each bar that indicates the maximum value
	 */
	private boolean mDrawBarShadow = false;

	/**
	 * if set to true, the width of bar will scale by counts of bars
	 */
	private boolean mAutoScale = true;

	/**
	 * bar's width without bar space
	 */
	private float barWidthSize;
	/**
	 * bar first onLayout if first this is true
	 */
	private boolean isFirstOnLayout = true;

	public BarChart(Context context) {
		super(context);
	}

	public BarChart(Context context, AttributeSet attrs) {
		super(context, attrs);
	}

	public BarChart(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
	}

先看代码的第一部分,我们看到了BarChart继承自 BarLineChartBase<BarData>,然后实现了一个BarDataProvider的这个借口,我们在去看一下BarLineChartBase<BarData>这个类里是什么样的吧。

public abstract class BarLineChartBase<T extends BarLineScatterCandleBubbleData<? extends IBarLineScatterCandleBubbleDataSet<? extends Entry>>>
        extends Chart<T> implements BarLineScatterCandleBubbleDataProvider {

    /**
     * 图表中可以包含最多的实体值的数量,如果实体值的数量超过这个数量之后,该实体值就不会被现实出来
     */
    protected int mMaxVisibleCount = 100;

    /**
     * flag that indicates if auto scaling on the y axis is enabled
     */
    private boolean mAutoScaleMinMaxEnabled = false;
    private Integer mAutoScaleLastLowestVisibleXIndex = null;
    private Integer mAutoScaleLastHighestVisibleXIndex = null;

    /**
     *是否可以支持双指手势
     */
    protected boolean mPinchZoomEnabled = false;

    /**
     * 两次点击可以放大柱状图的显示
     */
    protected boolean mDoubleTapToZoomEnabled = true;

    /**
     * flag that indicates if highlighting per dragging over a fully zoomed out
     * chart is enabled
     */
    protected boolean mHighlightPerDragEnabled = true;

    /**
     * flag that indicates whether the highlight should be full-bar oriented, or single-value?
     */
    protected boolean mHighlightFullBarEnabled = false;

    /**
     * 是否可以缩放
     */
    private boolean mDragEnabled = true;

    private boolean mScaleXEnabled = true;
    private boolean mScaleYEnabled = true;</span>
//前面这些都是相对一些柱状图的属性的值,在后面的BarChart绘制中,会使用到这些值将BarChart绘制成不同的属性
   /**
     * paint object for the BarChart
     */
    protected Paint mGridBackgroundPaint;

    protected Paint mBorderPaint;

    protected boolean mDrawGridBackground = false;

    protected boolean mDrawBorders = false;

    /**
     * Sets the minimum offset (padding) around the chart, defaults to 15
     */
    protected float mMinOffset = 15.f;

    /**
     * flag indicating if the chart should stay at the same position after a rotation. Default is false.
     */
    protected boolean mKeepPositionOnRotation = false;
//好了下面就是验证我们猜测的重点
    /**
     * the listener for user drawing on the chart
     */
    protected OnDrawListener mDrawListener;//这是监听器

    /**
     * the object representing the labels on the left y-axis
     */
    protected YAxis mAxisLeft;//这是左侧的Y轴

    /**
     * the object representing the labels on the right y-axis
     */
    protected YAxis mAxisRight;//这是右侧的Y轴,可能会疑惑为什么有两个轴,等到实际应用的时候你就知道为什么饿

    protected YAxisRenderer mAxisRendererLeft;
    protected YAxisRenderer mAxisRendererRight;

    protected Transformer mLeftAxisTransformer;
    protected Transformer mRightAxisTransformer;//我们的Transformer,这个之后会拿出一篇来讲

    protected XAxisRenderer mXAxisRenderer;//X轴的渲染器

    // /** the approximator object used for data filtering */
    // private Approximator mApproximator;

    public BarLineChartBase(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    public BarLineChartBase(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public BarLineChartBase(Context context) {
        super(context);
    }
//在init中,我们看到讲X轴Y轴进行了初始化,并且初始化了transformer
//初始化chartTouchListener
//初始化了背景和描边的画笔
    @Override
    protected void init() {
        super.init();

        mAxisLeft = new YAxis(AxisDependency.LEFT);
        mAxisRight = new YAxis(AxisDependency.RIGHT);

        mLeftAxisTransformer = new Transformer(mViewPortHandler);
        mRightAxisTransformer = new Transformer(mViewPortHandler);

        mAxisRendererLeft = new YAxisRenderer(mViewPortHandler, mAxisLeft, mLeftAxisTransformer);
        mAxisRendererRight = new YAxisRenderer(mViewPortHandler, mAxisRight, mRightAxisTransformer);

        mXAxisRenderer = new XAxisRenderer(mViewPortHandler, mXAxis, mLeftAxisTransformer);

        setHighlighter(new ChartHighlighter(this));

        mChartTouchListener = new BarLineChartTouchListener(this, mViewPortHandler.getMatrixTouch());

        mGridBackgroundPaint = new Paint();
        mGridBackgroundPaint.setStyle(Style.FILL);
        // mGridBackgroundPaint.setColor(Color.WHITE);
        mGridBackgroundPaint.setColor(Color.rgb(240, 240, 240)); // light
        // grey

        mBorderPaint = new Paint();
        mBorderPaint.setStyle(Style.STROKE);
        mBorderPaint.setColor(Color.BLACK);
        mBorderPaint.setStrokeWidth(Utils.convertDpToPixel(1f));
    }

    // for performance tracking
    private long totalTime = 0;
    private long drawCycles = 0;
//然后这里是LineBase的OnDraw方法

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        if (mData == null)
            return;

        long starttime = System.currentTimeMillis();
        calcModulus();

        mXAxisRenderer.calcXBounds(this, mXAxis.mAxisLabelModulus);
        mRenderer.calcXBounds(this, mXAxis.mAxisLabelModulus);//在X轴的区域内进行绘制

        // execute all drawing commands
        drawGridBackground(canvas);//绘制图表的背景
        if (mAxisLeft.isEnabled())
            mAxisRendererLeft.computeAxis(mAxisLeft.mAxisMinimum, mAxisLeft.mAxisMaximum);
        if (mAxisRight.isEnabled())
            mAxisRendererRight.computeAxis(mAxisRight.mAxisMinimum, mAxisRight.mAxisMaximum);//计算XY轴的范围

        mXAxisRenderer.renderAxisLine(canvas);
        mAxisRendererLeft.renderAxisLine(canvas);
        mAxisRendererRight.renderAxisLine(canvas);//绘制XY轴的线

        if (mAutoScaleMinMaxEnabled) {
            final int lowestVisibleXIndex = getLowestVisibleXIndex();
            final int highestVisibleXIndex = getHighestVisibleXIndex();

            if (mAutoScaleLastLowestVisibleXIndex == null ||
                    mAutoScaleLastLowestVisibleXIndex != lowestVisibleXIndex ||
                    mAutoScaleLastHighestVisibleXIndex == null ||
                    mAutoScaleLastHighestVisibleXIndex != highestVisibleXIndex) {

                calcMinMax();
                calculateOffsets();

                mAutoScaleLastLowestVisibleXIndex = lowestVisibleXIndex;
                mAutoScaleLastHighestVisibleXIndex = highestVisibleXIndex;
            }
        }

        // make sure the graph values and grid cannot be drawn outside the
        // content-rect

        mRenderer.drawData(canvas);

        // if highlighting is enabled
        if (valuesToHighlight())
            mRenderer.drawHighlighted(canvas, mIndicesToHighlight);//绘制上初始状态的高亮

        // Removes clipping rectangle
        canvas.restoreToCount(clipRestoreCount);

        mRenderer.drawExtras(canvas);

        clipRestoreCount = canvas.save();
        canvas.clipRect(mViewPortHandler.getContentRect());

        if (!mXAxis.isDrawLimitLinesBehindDataEnabled())
            mXAxisRenderer.renderLimitLines(canvas);

        if (!mAxisLeft.isDrawLimitLinesBehindDataEnabled())
            mAxisRendererLeft.renderLimitLines(canvas);

        if (!mAxisRight.isDrawLimitLinesBehindDataEnabled())
            mAxisRendererRight.renderLimitLines(canvas);//什么叫LimitLine啊?你想啊X轴和Y轴载屏幕上的显示有没有尽头

        canvas.restoreToCount(clipRestoreCount);

        mXAxisRenderer.renderAxisLabels(canvas);
        mAxisRendererLeft.renderAxisLabels(canvas);
        mAxisRendererRight.renderAxisLabels(canvas);//将X轴和Y轴的标签值绘制上

        mRenderer.drawValues(canvas);//通过传入的数据使用渲染器讲数据进行了会址

        mLegendRenderer.renderLegend(canvas);//绘制了图表的图例

        drawMarkers(canvas);//绘制了MarkerView

        drawDescription(canvas);//绘制了图表的描述

        if (mLogEnabled) {
            long drawtime = (System.currentTimeMillis() - starttime);
            totalTime += drawtime;
            drawCycles += 1;
            long average = totalTime / drawCycles;
            Log.i(LOG_TAG, "Drawtime: " + drawtime + " ms, average: " + average + " ms, cycles: "
                    + drawCycles);
        }

    }

首先BarLineDataBase<BarData>是一个抽象类,在这个类中有很多基本的属性,这些属性用来控制图表的一些基本属性,比如说是否显示XY轴,是否支持缩放以及图表的数据限制等,在图表进行绘制的时候会根据这些属性进行绘制,然后绘制出不同属性的图表。

然后在Init方法中,BarLineDataBase<BarData>这个类将绘制图表所需要的几个重要的组成部分进行了初始化,包括XY轴,包括监听器,还有Transformer和Paint。

初始化之后,BarLineDataBase<BarData>复写了onDraw方法,在onDraw方法中绘制了XY轴,并且使用在Chart类中继承来的mRenderer执行了drawData(),drawValues()和drawExtras()这三个方法,根据装入chart中的数据进行了绘制。然后有绘制了Legend图例,MarkerView标记和Description描述。这样你会发现我们将图表里的东西都进行了绘制。并且还装入了监听器,转化器(transformer)等,在BarLineDataBase<BarData>类中的方法执行完之后,我们将BarChart的基本组成进行了组装。

BarLineDataBase<BarData>类是一个继承自Chart抽象类的抽象类,在BarLineDataBase<BarData>中持有了mRender,mLegend等对象,所以他可以对Chart抽象类中的基本组成对象进行操作。

分析到这里,我们答题知道BarChart的继承关系,BarChart这个类是BarLineDataBase<BarData>这个抽象类的一个实现类,而BarLineDataBase<BarData>是Chart抽象类的一个抽象类,那么我们去看一下Chart抽象类里面有什么对象组成,有执行了什么方法吧,根据名字我们可以知道Chart在这个项目是一个顶层的抽象类,分析到他进算是分析到头了,当然了她也继承自ViewGroup但是ViewGroup我们并没有分析的必要。

public abstract class Chart<T extends ChartData<? extends IDataSet<? extends Entry>>> extends
        ViewGroup
        implements ChartInterface {

    public static final String LOG_TAG = "MPAndroidChart";

    /**
     * object that holds all data that was originally set for the chart, before
     * it was modified or any filtering algorithms had been applied
     */
    <span style="color:#ff0000;">protected T mData = null;</span>
<span style="color:#ff0000;">    /**
     * 默认的格式转换器
     */
    protected ValueFormatter mDefaultFormatter;

    /**
     * 绘制描述的Paint
     */
    protected Paint mDescPaint;

    /**
     * 绘制详情的Paint
     */
    protected Paint mInfoPaint;

    /**
     * 绘制在图表右下角的描述
     */
    protected String mDescription = "Description";

    /**
     * the object representing the labels on the x-axis
     */
    protected XAxis maxis;//X轴

    protected boolean mTouchEnabled = true;

    protected Legend mLegend;//图例</span>
<span style="color:#ff0000;">   protected OnChartValueSelectedListener mSelectionListener;//item选中时触发的监听器

    protected ChartTouchListener mChartTouchListener;//图表被点击时触发的监听器

    private OnChartGestureListener mGestureListener;//图表中的手势监听器

    protected LegendRenderer mLegendRenderer;//图例的渲染器

    protected DataRenderer mRenderer;//数据的渲染器

    protected ChartHighlighter mHighlighter;//高亮

    protected ViewPortHandler mViewPortHandler;//item绘制区域

    protected ChartAnimator mAnimator;//动画</span>

//根据上面的这些声明的对象,我们基本印证了自己的猜测,图表的组成部分就是这些。
    private float mExtraTopOffset = 0.f,
            mExtraRightOffset = 0.f,
            mExtraBottomOffset = 0.f,
            mExtraLeftOffset = 0.f;

    /**
     * default constructor for initialization in code
     */
    public Chart(Context context) {
        super(context);
        init();
    }

    /**
     * constructor for initialization in xml
     */
    public Chart(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    /**
     * even more awesome constructor
     */
    public Chart(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }

    /**
     * initialize all paints and stuff
     *<span style="font-family: Arial, Helvetica, sans-serif;">//在init中,对图表的基本组成进行了初始化</span>
<pre name="code" class="java">
        setWillNotDraw(false);
        // setLayerType(View.LAYER_TYPE_HARDWARE, null);

        if (android.os.Build.VERSION.SDK_INT < 11)
            mAnimator = new ChartAnimator();
        else
            mAnimator = new ChartAnimator(new AnimatorUpdateListener() {

                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    // ViewCompat.postInvalidateOnAnimation(Chart.this);
                    postInvalidate();
                }
            });

        // initialize the utils
        Utils.init(getContext());

        mDefaultFormatter = new DefaultValueFormatter(1);

        mViewPortHandler = new ViewPortHandler();

        mLegend = new Legend();

        mLegendRenderer = new LegendRenderer(mViewPortHandler, mLegend);

        mXAxis = new XAxis();

        mDescPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mDescPaint.setColor(Color.BLACK);
        mDescPaint.setTextAlign(Align.RIGHT);
        mDescPaint.setTextSize(Utils.convertDpToPixel(9f));

        mInfoPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mInfoPaint.setColor(Color.rgb(247, 189, 51)); // orange
        mInfoPaint.setTextAlign(Align.CENTER);
        mInfoPaint.setTextSize(Utils.convertDpToPixel(12f));

        mDrawPaint = new Paint(Paint.DITHER_FLAG);

        if (mLogEnabled)
            Log.i("", "Chart.init()");
    }

      然后在初始化之后,我们来看一下,Chart抽象类的几个重要的方法,这些方法写出来大部分需要被实现Chart抽象类的子类复写。

<@Override
    protected void onDraw(Canvas canvas) {
        // super.onDraw(canvas);

        if (mData == null) {

            boolean hasText = !TextUtils.isEmpty(mNoDataText);
            boolean hasDescription = !TextUtils.isEmpty(mNoDataTextDescription);
            float line1height = hasText ? Utils.calcTextHeight(mInfoPaint, mNoDataText) : 0.f;
            float line2height = hasDescription ? Utils.calcTextHeight(mInfoPaint, mNoDataTextDescription) : 0.f;
            float lineSpacing = (hasText && hasDescription) ?
                    (mInfoPaint.getFontSpacing() - line1height) : 0.f;

            // if no data, inform the user

            float y = (getHeight() -
                    (line1height + lineSpacing + line2height)) / 2.f
                    + line1height;

            if (hasText) {
                canvas.drawText(mNoDataText, getWidth() / 2, y, mInfoPaint);

                if (hasDescription) {
                    y = y + line1height + lineSpacing;
                }
            }

            if (hasDescription) {
                canvas.drawText(mNoDataTextDescription, getWidth() / 2, y, mInfoPaint);
            }
            return;
        }

        if (!mOffsetsCalculated) {

            calculateOffsets();
            mOffsetsCalculated = true;
        }
    }</span>

onDraw()作者写的实在太聪明了,因为他知道,在对数据进行绘制的时候不同的图表有着完全不同的绘制方式,所以她在最基本的Chart类的onDraw方法中写了当数据为空的时候的绘制原则,因为有数据的时候各有各的展示方式,而没有数据的时候处理的方式都一样,假设你真的是很奇怪,那么你复写也就行了。

protected void drawMarkers(Canvas canvas) {

        // if there is no marker view or drawing marker is disabled
        if (mMarkerView == null || !mDrawMarkerViews || !valuesToHighlight())
            return;

        for (int i = 0; i < mIndicesToHighlight.length; i++) {

            Highlight highlight = mIndicesToHighlight[i];
            int xIndex = highlight.getXIndex();
            int dataSetIndex = highlight.getDataSetIndex();

            float deltaX = mXAxis != null 
                ? mXAxis.mAxisRange
                : ((mData == null ? 0.f : mData.getXValCount()) - 1.f);

            if (xIndex <= deltaX && xIndex <= deltaX * mAnimator.getPhaseX()) {

                Entry e = mData.getEntryForHighlight(mIndicesToHighlight[i]);
	</span>//得到该实体,进行非空判断后,拿到该实体的数据
                // make sure entry not null
                if (e == null || e.getXIndex() != mIndicesToHighlight[i].getXIndex())
                    continue;

                float[] pos = getMarkerPosition(e, highlight);
//计算出MarkerView的显示位置
                // check bounds
                if (!mViewPortHandler.isInBounds(pos[0], pos[1]))
                    continue;

                // callbacks to update the content
                mMarkerView.refreshContent(e, highlight);

                // mMarkerView.measure(MeasureSpec.makeMeasureSpec(0,
                // MeasureSpec.UNSPECIFIED),
                // MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
                // mMarkerView.layout(0, 0, mMarkerView.getMeasuredWidth(),
                // mMarkerView.getMeasuredHeight());
                // mMarkerView.draw(mDrawCanvas, pos[0], pos[1]);
//执行mMarkerView的Measure,layout 和Draw方法绘制出MarkerView
                mMarkerView.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
                        MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
                mMarkerView.layout(0, 0, mMarkerView.getMeasuredWidth(),
                        mMarkerView.getMeasuredHeight());

                if (pos[1] - mMarkerView.getHeight() <= 0) {
                    float y = mMarkerView.getHeight() - pos[1];
                    mMarkerView.draw(canvas, pos[0], pos[1] + y);
                } else {
                    mMarkerView.draw(canvas, pos[0], pos[1]);
                }</span>
//至于是怎么绘制的,你应该去看是谁实现了MarkerView的这三个方法,然后根据实现的方法了解绘制的方式
            }
        }
    }

绘制MarkerView的方法。

其他还有很多基本方法,诸如得到Chart的基本组成部分的实体对象,对Item进行高亮的HighLightValue方法等(当然HighLighte r这个类也是需要详细讲解的)。其他的杂暗示不赘述。

分析到这里,我们完成了有下面的类往他的父类进行溯源的这个过程,我们找到了最基本的Chart类,然后又有BarLineDataBase<BarData>这个抽象类对BarChart进行了进一步的抽象,然后我们Bar Chart类继承自BarLineDataBase<BarData>类,实现了一个具体的可以实例话的类。我们弄明白了Chart类的基本继承关系,并且我们在网上溯源的过程中了解了一个Chart的基本组成部分,这些基本组成是Chart的共性。

所以我们接下来要做的事情就行对这些基本组成部分进行分析,在我们对源码进行修改的时候,假设我们需要修改柱状图的item显示的宽度,那么我们就应该去找DataRenderer 这个类对吧,假设我们不要要图例前面的标签就仅仅是简单的正方形的色块,需求要求我们讲起实现为一个圆形的色块,那么我们就应该对Legend这个对象和mLegendRenderer  进行修改对吧。

当然了对应我们的BarChart我们理所当然的能想到,我们在做修改的时候(假设就是上面的那些需求),我们修改的类应该是DataRenderer 的对应BarChart的子类BarChartDataRenderer,和Legend对应的BarChart的Legend类(在后面的分析中,我发现我的这个猜测是错的,图例类在图表中的变化并不大,所以开发者并没有编写对应不同图表的Legend类。)

接下来的博客我们将分析图表的这些基本组成部分。

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