Android RecyclerLineChart实现图表绘制教程
作者:cxy107750 发布时间:2023-10-02 11:29:44
本篇介绍线性图标RecyclerLineChart 的绘制,对于图表的公共部分X、Y轴,背景Board等的绘制先前章节已经有过介绍,这里不再重复;以及高亮选中顶部的poupWindow基本的绘制逻辑跟BarChart类似,可参照之前章节。所以针对LineChart,这里只介绍主体图表的绘制逻辑,以及线性表底部的drawFillColor填充。
首先介绍主体图表的逻辑,与BarChart不同之处在于,BartChart的每个Item的绘制比较独立,而LineChart对于当前的Item,需要找到PreItem或者NextItem中的Y的点进行drawLine, 相比而言稍显复杂一些。图表的中间位置的Line还比较容易绘制,图表左右边界是LineChart绘制最难的地方。
整个的绘制逻辑第一章节有介绍在Render类中,这里的话是LineChartRender的drawLineChartWithoutPoint 方法里,这个方法代码比较长,分段介绍:
private <T extends BarEntry> void drawLineChartWithoutPoint(Canvas canvas, RecyclerView parent, YAxis mYAxis) {
final float parentRightBoard = parent.getWidth() - parent.getPaddingRight();
final float parentLeft = parent.getPaddingLeft();
BaseBarChartAdapter adapter = (BaseBarChartAdapter) parent.getAdapter();
List<T> entryList = adapter.getEntries();
final int childCount = parent.getChildCount();
int adapterPosition;
for (int i = 0; i < childCount; i++) {
View child = parent.getChildAt(i);
T barEntry = (T) child.getTag();
if (barEntry.getY() == 0) {
continue;
}
adapterPosition = parent.getChildAdapterPosition(child);
RectF rectF = ChartComputeUtil.getBarChartRectF(child, parent,
mYAxis, mLineChartAttrs, barEntry);
PointF pointF2 = new PointF((rectF.left + rectF.right) / 2, rectF.top);
// 这里还有好多绘制逻辑代码
}//end for
}// end drawLineChartWithoutPoint
整个绘制绘制依次遍历 Adapter中当前展现的点,总共childcount 个,遍历的当前点位pointF2, 以pointF2 为基准接下来会涉及找 pointF0,pointF1, 这两在pointF2左边(假设存在的情况下);pointF3, pointF4, 这两个点在PointFd2的右边,之所以要找左右各两个点是处理边界情况。
正常情况下绘制逻辑
连接pointF1、pointF2。
if (i < childCount - 1) {//这里的LayoutManager设置的reverse倒叙,所以i+1在i的左边i对应的是pointF2
View pointF1Child = parent.getChildAt(i + 1);
T barEntryLeft = (T) pointF1Child.getTag();
//这里的RectF跟之前的Barchart类似,为ItemView中除去space后所占的RectF区域,其中pointF1的X为RectF的X轴方向的中心。
RectF rectFLeft = ChartComputeUtil.getBarChartRectF(pointF1Child, parent, mYAxis, mLineChartAttrs, barEntryLeft);
//找到PointF1
PointF pointF1 = new PointF((rectFLeft.left + rectFLeft.right) / 2, rectFLeft.top);
//parentLeft为左边界, parentRightBoard 为Chart的右边界
if (pointF1.x >= parentLeft && pointF2.x <= parentRightBoard) {
float[] pointsOut = new float[]{pointF1.x, pointF1.y, pointF2.x, pointF2.y};
drawChartLine(canvas, pointsOut);//绘制正常情况下的Line。
drawFill(parent, mLineChartAttrs, canvas, pointF1, pointF2, rectF.bottom);
//其它边界绘制逻辑。
}//end if
左边界绘制
以上的情况是pointF1.x 在Chart内,见图, 黄色为当前的PointF1, 紫色为PointF2, 上面代码李drawLine绘制的是PointF1跟PointF2之前的连线。
继续看上面的那个图,要绘制PointF1到Chart左边边界的线段,需要继续找到PointF0,然后用PointF0、PointF1与Chart相交得到上图黑色圈里的点,记为pointFIntercept, drawLine(pointFIntercept, PointF1)
if (pointF1Child.getLeft() < parentLeft) {//左边界,处理pointF1值显示出来了的情况。
if (adapterPosition + 2 < entryList.size()) {
float x = pointF1.x - pointF1Child.getWidth();
T barEntry0 = entryList.get(adapterPosition + 2);
float y = ChartComputeUtil.getYPosition(barEntry0, parent, mYAxis, mLineChartAttrs);
PointF pointF0 = new PointF(x, y);
//PointF0、PointF1 跟Chart的交点pointFIntercept
PointF pointFIntercept = ChartComputeUtil.getInterceptPointF(pointF0, pointF1, parentLeft);
float[] points = new float[]{pointFIntercept.x, pointFIntercept.y, pointF1.x, pointF1.y};
drawChartLine(canvas, points);
drawFill(parent, mLineChartAttrs, canvas, pointFIntercept, pointF1, rectF.bottom);
}
}
上面是 pointF1.x >= parentLeft,在左边界内的情况,当pointF1.x < parentLeft时,rectLeft 出来一小部分的情况,如下图所示:紫色为当前的PointF2点
这时需要求PointF1、PointF2跟Chart相交的点pointF, 然后drawLine(pointF, PointF2)即可, 见代码:
if (pointF1.x < parentLeft && pointF1Child.getRight() >= parentLeft) {//左边界,处理pointF1值没有显示出来
PointF pointF = ChartComputeUtil.getInterceptPointF(pointF1, pointF2, parentLeft);
float[] points = new float[]{pointF.x, pointF.y, pointF2.x, pointF2.y};
drawChartLine(canvas, points);
drawFill(parent, mLineChartAttrs, canvas, pointF, pointF2, rectF.bottom);
}
右边界绘制
处理完左边界的绘制,右边界的绘制跟左边界大致一样,PointF2 往右两个点PointF3, PointF4; 注意这里RecyclerView的LayoutManager为reverse, 所以当 PointF2对应的下标为i时, PointF3对应的为i-1, PointF4为i-2.
然后就是分情况讨论PointF3.x 是否在Chart范围内,跟parentRightBorad比较即可。
看PointF3.x 在 Chart范围内的情况,如图:紫色为PointF2点,黄色为PonitF3点,黑色为PointF3,PointF4跟Chart的交点,这里只需要绘制PointF3跟交点之间的Line;PointF2、PointF3之间的Line 在当黄色点遍历到i时,紫色点位PointF1,所以这里不需要重复绘制了。
代码逻辑
if (pointF3.x < parentRightBoard) {//pointF3 在界内。
if (adapterPosition - 2 > 0) {
float xInner = pointF3.x + child.getWidth();
T barEntry4 = entryList.get(adapterPosition - 2);
float yInner = ChartComputeUtil.getYPosition(barEntry4, parent, mYAxis, mLineChartAttrs);
PointF pointF4 = new PointF(xInner, yInner);//找到PointF4.
PointF pointFInterceptInner = ChartComputeUtil.getInterceptPointF(pointF3, pointF4, parentRightBoard);
float[] pointsInner = new float[]{pointF3.x, pointF3.y, pointFInterceptInner.x, pointFInterceptInner.y};
drawChartLine(canvas, pointsInner);
drawFill(parent, mLineChartAttrs, canvas, pointF3, pointFInterceptInner, rectF.bottom);
}
}
最后就是 pointF3.x >parentRightBoard的情况,见图:紫色为PointF2, 黄色为 PointF2、PointF3跟Chart的交点:
代码逻辑如下:
if (pointF3.x > parentRightBoard) {//在Chart之外。
PointF pointFIntercept = ChartComputeUtil.getInterceptPointF(pointF2, pointF3, parentRightBoard);
float[] points = new float[]{pointF2.x, pointF2.y, pointFIntercept.x, pointFIntercept.y};
drawChartLine(canvas, points);
drawFill(parent, mLineChartAttrs, canvas, pointFIntercept, pointF2, rectF.bottom);
}
以上的边界处理中涉及到的工具类方法求相交点,简单的数学公司带入:
public static PointF getInterceptPointF(PointF pointF1, PointF pointF2, float x) {
float width = Math.abs(pointF1.x - pointF2.x);
float height = Math.abs(pointF1.y - pointF2.y);
float interceptWidth = Math.abs(pointF1.x - x);
float interceptHeight = interceptWidth * 1.0f / width * height;
float y;
if (pointF2.y < pointF1.y) {
y = pointF1.y - interceptHeight;
} else {
y = pointF1.y + interceptHeight;
}
return new PointF(x, y);
}
见以上图表中的红色半透明的FillColor的绘制,每次drawLine()紧跟着就是drawFill(), 以下是drawFill的逻辑,跟X轴构建一个path,然后drawPath 即可:
private void drawFill(RecyclerView parent, LineChartAttrs mBarChartAttrs, Canvas canvas, PointF pointF, PointF pointF1, float bottom) {
if (mBarChartAttrs.enableLineFill) {
float yBottom = parent.getBottom() - parent.getPaddingBottom();
float yTop = parent.getTop() + parent.getPaddingTop();
LinearGradient mLinearGradient = new LinearGradient(
0,
yBottom,
0,
yTop,
new int[]{
mBarChartAttrs.lineShaderBeginColor, mBarChartAttrs.lineShaderEndColor},
null,
Shader.TileMode.CLAMP
);
mLineFillPaint.setShader(mLinearGradient);
Path path = ChartComputeUtil.createColorRectPath(pointF, pointF1, bottom);
LineChartDrawable drawable = new LineChartDrawable(mLineFillPaint, path);
drawable.draw(canvas);
}
}
设置了一个Color的Linear渐变从bottom到top.
至此,RecyclerLineChart的主体图表绘制逻辑介绍完毕。还有部分的细节,当前Point带圆圈,以及边界圆圈的绘制等,选中圆圈的处理等多处细节,读者想了解的,可以GitHub上下载看源码demo, 连接在本专栏的第一篇里有链接。
来源:https://juejin.cn/post/7174775634168643639


猜你喜欢
- 下面是利用Java实现读写文件功能的示例代码读文件TextRead.javaimport java.io.BufferedReader;im
- 文件的读取 FileStream fs = new FileStream(@"D:\12.txt", File
- 这是进行Java Web开发必备的一个过程,仅供新手参考,高手可以忽略!JDK 和 JRE 的区别JRE(Java Runtime Envi
- 一、代码实现创建窗口首先创建一个游戏窗体类GameFrame,继承至JFrame,用来显示在屏幕上(window的对象),每个游戏都有一个窗
- 什么是Run Dashboard当springcloud的服务有多个时,管理多个服务的启动使用run会不好管理,这样我们就可以使用Run D
- 添加MyBatis的代码并修改以下部分:1.添加MyBatisConfigpackage myshop.config;import java
- 1.在action中定义变量 private List<String> downLoadPaths = new ArrayLis
- 一、首先将网页内容整个抓取下来,数据放在byte[]中(网络上传输时形式是byte),进一步转化为String,以便于对其操作,实例如下:p
- 由于大多数便携式设备支持浏览图片而不支持浏览PowerPoint 文件,所以相比较而言,图像对于用户而言更加友好。除此之外,将PowerPo
- 无平台限制,依赖于快递api网接口 ----------------实体类 [DataContract]  
- 本教程基于 JetBrains IntelliJ IDEA 2018.3.6 编写,高版本未经测试,或有不兼容,请见谅!JetBrains
- Quote在学习 Kotlin 的过程中,对 Kotlin 的类型系统产生了好奇,Kotlin 是否存在类似于 Java 中 Object
- 本文实例讲述了Java中对象的比较操作。分享给大家供大家参考,具体如下:一 点睛在Java中,有两种方式可用于对象间的比较:利用"
- 1.JVM Heap(堆)溢出:java.lang.OutOfMemoryError: Java heap spaceJVM在启动的时候会自
- 本文实例讲述了java数据结构与算法之双向循环队列的数组实现方法。分享给大家供大家参考,具体如下:需要说明的是此算法我并没有测试过,这里给出
- Java 理解 ThreadLocal摘要: ThreadLocal 又名线程局部变量,是 Java 中一种较为特殊的线程绑定机制,用于保证
- 上一篇,我们已经详细讲解了Android微信支付,今天接着为大家带来支付宝支付,支付宝支付相对微信支付要简单一些,吐槽一下,而且支付宝文档确
- 什么是 Nacos Config在分布式系统中,由于服务数量巨多,为了方便服务 配置文件统一管理,实时更新,所以需要分布式配置中心组件。Sp
- C# 的类型系统可分为两种类型,一是值类型,一是引用类型,这个每个C#程序员都了解。还有托管堆,栈,ref,out等等概念也是每个C#程序员
- 一般我们用它来自动帮我们注册APT文件(全称是Annotation Process Tool,或者叫注解处理器,AbstractProces