MPAndroidChart系列源码解读(一)

最近有点闲,打算找点事做,做出点什么事!!就选定你了MPAndroidChart,从源码的角度分析这个库,并加以实现,下面不再废话直接进入主题。

  • EntryXIndexComparator

  • FileUtils

  • FSize

  • PointD

  • SelectionDetail

  • Transformer

  • TransformerHorizontalBarChart

  • Utils

  • ViewPortHandler

EntryXIndexComparator是比较器Comparator的实现类,Comparator定义了两个比较方法compare、equals,实现类EntryXIndexComparator重新compare比较传入对象的mXIndex(在x轴上的数值)属性


public class EntryXIndexComparator implements Comparator<Entry> {
    @Override
    public int compare(Entry entry1, Entry entry2) {
        return entry1.getXIndex() - entry2.getXIndex();
    }
}

FileUtils工具类是一个辅助读写基础数据类,在项目工程Assets下面的基础数据读写会用到内部提供了几种读写方法。

    /** * 从指定SDcard文件加载数据 * * @param path the name of the file on the sd-card (+ path if needed) * @return */
    public static List loadEntriesFromFile(String path) {

    }

    /** * 从指定Assets文件下的路径加载数据 * * @param am * @param path the name of the file in the assets folder (+ path if needed) * @return */
    public static List loadEntriesFromAssets(AssetManager am, String path) {

    }

    /** * 保存数据到SDcard本地 * * @param ds * @param path */
    public static void saveToSdCard(List entries, String path) {

    }

    public static List loadBarEntriesFromAssets(AssetManager am, String path) {

    }

Size 、SizeF、FSize这三者的区别和共性:都是不可变类用于描述任意的宽度和高度尺寸。只有Size适用于描述int的值,其他两个都是描述float型,SizeF只能在API > = 21使用,so 该库提供了定制化的FSize,源码贴上(对比参照SizeF)


public final class FSize {

    public final float width;
    public final float height;

    public FSize(final float width, final float height) {
        this.width = width;
        this.height = height;
    }

    @Override
    public boolean equals(final Object obj) {
        if (obj == null) {
            return false;
        }
        if (this == obj) {
            return true;
        }
        if (obj instanceof FSize) {
            final FSize other = (FSize) obj;
            return width == other.width && height == other.height;
        }
        return false;
    }

    @Override
    public String toString() {
        return width + "x" + height;
    }

    /** * {@inheritDoc} */
    @Override
    public int hashCode() {
        return Float.floatToIntBits(width) ^ Float.floatToIntBits(height);
    }
}

PointD用于封装double型数据x、y


public class PointD {

    public double x;
    public double y;

    public PointD(double x, double y) {
        this.x = x;
        this.y = y;
    }

    /** * returns a string representation of the object */
    public String toString() {
        return "PointD, x: " + x + ", y: " + y;
    }
}

SelectionDetail在高亮显示或选中时会用到他,主要是包裹一些数值


public class SelectionDetail {

    public float y;
    public float value;
    public int dataIndex;
    public int dataSetIndex;
    public IDataSet dataSet;

    public SelectionDetail(float y, float value, int dataIndex, int dataSetIndex, IDataSet set) {
        this.y = y;
        this.value = value;
        this.dataIndex = dataIndex;
        this.dataSetIndex = dataSetIndex;
        this.dataSet = set;
    }

    public SelectionDetail(float y, float value, int dataSetIndex, IDataSet set) {
        this(y, value, 0, dataSetIndex, set);
    }

    public SelectionDetail(float value, int dataSetIndex, IDataSet set) {
        this(Float.NaN, value, 0, dataSetIndex, set);
    }
}

Transformer是一个包含矩阵变换和负责把数据转换成屏幕上的像素的类(对于矩阵变换左乘和右乘倒置可逆相关用法表示无能为力了,原谅我读书少,理解不了)


public class Transformer {

    /** * 矩阵的值映射到屏幕上的像素 * matrix to map the values to the screen pixels */
    protected Matrix mMatrixValueToPx = new Matrix();

    /** * 矩阵来处理不同偏移量的图表 * matrix for handling the different offsets of the chart */
    protected Matrix mMatrixOffset = new Matrix();

    protected ViewPortHandler mViewPortHandler;

    public Transformer(ViewPortHandler viewPortHandler) {
        this.mViewPortHandler = viewPortHandler;
    }

    /** * 准备的矩阵变换值像素。计算从图表的大小和偏移量规模因素。 * Prepares the matrix that transforms values to pixels. Calculates the * scale factors from the charts size and offsets. * * @param xChartMin * @param deltaX * @param deltaY * @param yChartMin */
    public void prepareMatrixValuePx(float xChartMin, float deltaX, float deltaY, float yChartMin) {

        float scaleX = (float) ((mViewPortHandler.contentWidth()) / deltaX);
        float scaleY = (float) ((mViewPortHandler.contentHeight()) / deltaY);

        // 如果是正负无穷数,则scaleX \ scaleY归0
        if (Float.isInfinite(scaleX))
        {
            scaleX = 0;
        }
        if (Float.isInfinite(scaleY))
        {
            scaleY = 0;
        }

        //设置所有的矩阵
        mMatrixValueToPx.reset();
        // 平移缩放动画先隐藏掉
        mMatrixValueToPx.postTranslate(-xChartMin, -yChartMin);
        mMatrixValueToPx.postScale(scaleX, -scaleY);
    }

    /** * 准备偏移量的缩放平移 * Prepares the matrix that contains all offsets. * * @param inverted */
    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.set(offset);

        // mMatrixOffset.reset();
        //
        // mMatrixOffset.postTranslate(mOffsetLeft, getHeight() -
        // mOffsetBottom);
    }

    /** * 数据转换成浮点值,封装到数组, * Transforms an List of Entry into a float array containing the x and * y values transformed with all matrices for the SCATTERCHART. * * @param data * @return */
    public float[] generateTransformedValuesScatter(IScatterDataSet data,
                                                    float phaseY) {

        float[] valuePoints = new float[data.getEntryCount() * 2];

        for (int j = 0; j < valuePoints.length; j += 2) {

            Entry e = data.getEntryForIndex(j / 2);

            if (e != null) {
                valuePoints[j] = e.getXIndex();
                valuePoints[j + 1] = e.getVal() * phaseY;
            }
        }
        //获取值在屏幕上的映射的像素值的Matrix,这个矩阵应用于2 d点的数组,并编写转换回数组*点
        getValueToPixelMatrix().mapPoints(valuePoints);

        return valuePoints;
    }
    // ......................略.......................
  }

TransformerHorizontalBarChart继承自Transformer,修改了水平偏移量的计算

/**
 * Prepares the matrix that contains all offsets.
 * 
 * @param chart
 */
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.getChartWidth() - mViewPortHandler.offsetRight()),
                        mViewPortHandler.getChartHeight() - mViewPortHandler.offsetBottom());
        mMatrixOffset.postScale(-1.0f, 1.0f);
    }
}

Utils工具类,提供一些辅助方法供调用,Utils是一个抽象类,初始化在Chart构造函数调用init方法里面调用Utils.init初始化工具类(主要初始化速度值的范围,以及DisplayMetrics的初始化)。

  public static void init(Context context) {

        if (context == null) {
            // noinspection deprecation
            mMinimumFlingVelocity = ViewConfiguration.getMinimumFlingVelocity();
            // noinspection deprecation
            mMaximumFlingVelocity = ViewConfiguration.getMaximumFlingVelocity();

            Log.e("MPChartLib-Utils"
                    , "Utils.init(...) PROVIDED CONTEXT OBJECT IS NULL");

        } else {
            ViewConfiguration viewConfiguration = ViewConfiguration.get(context);
            mMinimumFlingVelocity = viewConfiguration.getScaledMinimumFlingVelocity();
            mMaximumFlingVelocity = viewConfiguration.getScaledMaximumFlingVelocity();

            Resources res = context.getResources();
            mMetrics = res.getDisplayMetrics();
        }
    }

提供方法convertDpToPixel根据出入dp值和初始化的DisplayMetrics实现dp转换px,如果我们没有调用init初始化方法转换px就会原值返回(convertPixelsToDp同理)

    public static float convertDpToPixel(float dp) {

        if (mMetrics == null) {

            Log.e("MPChartLib-Utils",
                    "Utils NOT INITIALIZED. You need to call Utils.init(...) at least once before" +
                            " calling Utils.convertDpToPixel(...). Otherwise conversion does not " +
                            "take place.");
            return dp;
        }

        DisplayMetrics metrics = mMetrics; 
        float px = dp * (metrics.densityDpi / 160f);
        return px;
    }

通过Utils类我们发现的新大陆就在此了,把多个方法共有属性单独抽到一个方法初始化,比如getColor getDrawable相关的都需要Context对象,就可以如此做法,但会不会有内存泄漏问题暂不考虑,下面接着看看Utils其他方法封装,先是Text相关的

/** * 计算测量文本宽度 * calculates the approximate width of a text, depending on a demo text * avoid repeated calls (e.g. inside drawing methods) * * @param paint * @param demoText * @return */
    public static int calcTextWidth(Paint paint, String demoText) {
        return (int) paint.measureText(demoText);
    }

    /** * 计算测量文本高度 * calculates the approximate height of a text, depending on a demo text * avoid repeated calls (e.g. inside drawing methods) * * @param paint * @param demoText * @return */
    public static int calcTextHeight(Paint paint, String demoText) {

        Rect r = new Rect();
        paint.getTextBounds(demoText, 0, demoText.length(), r);
        return r.height();
    }

    /** *传入画笔获取行高 **/
    public static float getLineHeight(Paint paint) {
        Paint.FontMetrics metrics = paint.getFontMetrics();
        return metrics.descent - metrics.ascent;
    }

    /** * 根据画笔获取行间距 */
    public static float getLineSpacing(Paint paint) {
        Paint.FontMetrics metrics = paint.getFontMetrics();
        return metrics.ascent - metrics.top + metrics.bottom;
    }

    /** * 计算测量文字的大小,并把值转换成float型 * calculates the approximate size of a text, depending on a demo text * avoid repeated calls (e.g. inside drawing methods) * * @param paint * @param demoText * @return */
    public static FSize calcTextSize(Paint paint, String demoText) {

        Rect r = new Rect();
        paint.getTextBounds(demoText, 0, demoText.length(), r);
        return new FSize(r.width(), r.height());
    }

关于画笔Paint.ascent top等相关属性的介绍如果你不清楚可以参考我以前的一篇博文TextDrawable文末的介绍,Utils还提供了format相关方法,把给定值限定到指定的范围内,最大35个字符,另外还有对FSize、速度检测的相关封装(看着有点晕,以后如果用到了再来看吧)

ViewPortHandler相信不陌生了,在Utils见过许多次了,内部方法众多,下面分组说明

判断Zoom动画的缩放是否达到临界点,如果没有达到就还能继续缩放

    /** * Returns true if the chart is not yet fully zoomed out on the x-axis * * @return */
    public boolean canZoomOutMoreX() {
        return (mScaleX > mMinScaleX);
    }

    /** * Returns true if the chart is not yet fully zoomed in on the x-axis * * @return */
    public boolean canZoomInMoreX() {
        return (mScaleX < mMaxScaleX);
    }

    /** * Returns true if the chart is not yet fully zoomed out on the y-axis * * @return */
    public boolean canZoomOutMoreY() {
        return (mScaleY > mMinScaleY);
    }

    /** * Returns true if the chart is not yet fully zoomed in on the y-axis * * @return */
    public boolean canZoomInMoreY() {
        return (mScaleY < mMaxScaleY);
    }

hasNoDragOffset方法根据mTransOffsetX、mTransOffsetY判断是否有拖拽产生偏移,setDragOffsetY、setDragOffsetX设置x 、y轴的偏移量(偏移量通过Utils类把值转换成屏幕上的像素值)

  /** * Set an offset in dp that allows the user to drag the chart over it's * bounds on the x-axis. * * @param offset */
    public void setDragOffsetX(float offset) {
        mTransOffsetX = Utils.convertDpToPixel(offset);
    }

    /** * Set an offset in dp that allows the user to drag the chart over it's * bounds on the y-axis. * * @param offset */
    public void setDragOffsetY(float offset) {
        mTransOffsetY = Utils.convertDpToPixel(offset);
    }

    /** * Returns true if both drag offsets (x and y) are zero or smaller. * * @return */
    public boolean hasNoDragOffset() {
        return mTransOffsetX <= 0 && mTransOffsetY <= 0;
    }

判断试图是否完全缩小

 /** * if the chart is fully zoomed out, return true * * @return */
    public boolean isFullyZoomedOut() {

        if (isFullyZoomedOutX() && isFullyZoomedOutY())
            return true;
        else
            return false;
    }

    /** * Returns true if the chart is fully zoomed out on it's y-axis (vertical). * * @return */
    public boolean isFullyZoomedOutY() {
        if (mScaleY > mMinScaleY || mMinScaleY > 1f)
            return false;
        else
            return true;
    }

    /** * Returns true if the chart is fully zoomed out on it's x-axis * (horizontal). * * @return */
    public boolean isFullyZoomedOutX() {
        if (mScaleX > mMinScaleX || mMinScaleX > 1f)
            return false;
        else
            return true;
    }

MPAndroidChart系列源码解读(一)_第1张图片

矩阵变换方面的一些临界点限制值的获取


    /** * returns the current x-scale factor */
    public float getScaleX() {
        return mScaleX;
    }

    /** * returns the current y-scale factor */
    public float getScaleY() {
        return mScaleY;
    }

    public float getMinScaleX() {
        return mMinScaleX;
    }

    public float getMaxScaleX() {
        return mMaxScaleX;
    }

    public float getMinScaleY() {
        return mMinScaleY;
    }

    public float getMaxScaleY() {
        return mMaxScaleY;
    }

    /** * Returns the translation (drag / pan) distance on the x-axis * * @return */
    public float getTransX() {
        return mTransX;
    }

    /** * Returns the translation (drag / pan) distance on the y-axis * * @return */
    public float getTransY() {
        return mTransY;
    }

MPAndroidChart系列源码解读(一)_第2张图片

给定值的范围判断和Matrix配置,refresh、limitT..方法刷新视图View以及Matrix变换(由于代码量太多就不再贴了)

MPAndroidChart系列源码解读(一)_第3张图片

Zoom动画的设置执行

MPAndroidChart系列源码解读(一)_第4张图片

一些宽高偏移量的属性获取,这里内容被Utils调用测量后mContentRect被重新赋值,上面这些方法可以根据mContentRect各取所需。

util包里面大致就是这些内容关联类下次再看,本次源码阅读get到一点: 工具类里多个静态方法共有参数可以选择提取到init方法,在调用该类之前调用init初始化

你可能感兴趣的:(Android)