最近有点闲,打算找点事做,做出点什么事!!就选定你了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;
}
矩阵变换方面的一些临界点限制值的获取
/** * 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;
}
给定值的范围判断和Matrix配置,refresh、limitT..方法刷新视图View以及Matrix变换(由于代码量太多就不再贴了)
Zoom动画的设置执行
一些宽高偏移量的属性获取,这里内容被Utils调用测量后mContentRect被重新赋值,上面这些方法可以根据mContentRect各取所需。
util包里面大致就是这些内容关联类下次再看,本次源码阅读get到一点: 工具类里多个静态方法共有参数可以选择提取到init方法,在调用该类之前调用init初始化