Android RecyclerBarChart绘制使用教程
作者:cxy107750 发布时间:2023-06-19 12:18:36
上篇介绍了几种图表的公共组件X、Y轴、背景Board的绘制。这章节介绍柱状图表的绘制,相对其它图表而言简单一些,这里主要介绍图表主体的绘制,以及高亮选中的其中一个的选中框的绘制的相关逻辑。对每个ItemView中的ItemDecoration上进行onDraw的操作,需要将View跟Model进行绑定在一起,单个柱子的一些属性可以通过Model来获取,整体的一些绘制的辅助信息color、size等可以通过Attribute类设置。View 跟Model的绑定不止是BarChart图表,所以的都是一样的。
以下是在BarAdapter中的onBindViewHolder方法中进行关联二者:
根据之前的介绍绘制的逻辑都在ItemDecoration里,我们看下BarChartItemDecoration的onDrawOver, 对于X、Y轴、Board的绘制其实可以沉淀到BaseItemDecoration中的,这里直接写了。
这里我们着重看下drawBarChart、drawHighLight、drawBarChartValues的绘制。
1.drawBarChart
绘制柱状图的主体,通过ItemView拿到对应的Entry对象,根据Entry中的Y值进行Y坐标值的转化,然后绘制单个Item RectF的绘制。
//绘制柱状图, mYAxis这个坐标会实时变动,所以通过 BarChartItemDecoration 传过来的精确值。
final public void drawBarChart(final Canvas canvas, @NonNull final RecyclerView parent, final YAxis mYAxis) {
final float parentRight = parent.getWidth() - parent.getPaddingRight();
final float parentLeft = parent.getPaddingLeft();
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
View child = parent.getChildAt(i);
BarEntry barChart = (BarEntry) child.getTag();
RectF rectF = ChartComputeUtil.getBarChartRectF(child, parent, mYAxis, mBarChartAttrs, barChart);
drawChart(canvas, rectF, parentLeft, parentRight);
}
}
绘制的逻辑具体在 drawChart(canvas, rectF, parentLeft, parentRight) 的方法里,这里我们先看看 RectF 的计算,工具类ChartComputeUtil.getBarChartRectF() 的方法。
public static <T extends BarEntry, V extends BaseYAxis, E extends BaseChartAttrs> RectF getBarChartRectF(View child, final RecyclerView parent, V mYAxis, E chartAttrs, T barEntry) {
final RectF rectF = new RectF();
float contentBottom = parent.getHeight() - parent.getPaddingBottom() - chartAttrs.contentPaddingBottom;
float realYAxisLabelHeight = contentBottom - parent.getPaddingTop() - chartAttrs.contentPaddingTop;
float width = child.getWidth();
float barSpaceWidth = width * chartAttrs.barSpace;
float barChartWidth = width - barSpaceWidth;//柱子的宽度
final float left = child.getLeft() + barSpaceWidth / 2;
final float right = left + barChartWidth;
float height = barEntry.getY() / mYAxis.getAxisMaximum() * realYAxisLabelHeight;
if (chartAttrs.yAxisReverse && barEntry.getY() > 0) {
float valueTemp = mYAxis.getAxisMaximum() - barEntry.getY();
height = valueTemp / mYAxis.getAxisMaximum() * realYAxisLabelHeight;
}
final float top = Math.max(contentBottom - height, parent.getPaddingTop());
rectF.set(left, top, right, contentBottom);
return rectF;
}
柱子RectF 的计算,Width根据 itemView的width 以及每个ItemView的空余所占比例的一个ChartAttrs中的参数
barSpace计算得来,算出RectF的 left、right; height 的计算,涉及到Entry 的Y值以及YAxis 当前显示情况下的getAxisMaximum(),这里默认了Minmum为0,业务逻辑的Y值比例转化成 屏幕Pixel对应的高度,然后根据ItemView的top、bottom计算得到 RectF的 top, bottom. (这里的计算,到时候其它图表高度也可以用)。
获取到 单个ItemView 中BarChart 所占的RectF确定后,画RectF就比较简单了,稍微有点难点的是处理一下边界的问题,边界问题,柱状图相比线形图等简单一些,处于边界的柱子绘制的颜色不一样,交给用户mBarChartAttrs.chartEdgeColor传color值,这里默认设置的是Gray。
private void drawChart(Canvas canvas, RectF rectF, float parentLeft, float parentRight) {
float radius = (rectF.right - rectF.left) *mBarChartAttrs.barChartRoundRectRadiusRatio;
// 浮点数的 == 比较需要注意
if (DecimalUtil.smallOrEquals(rectF.right, parentLeft)) {
//continue 会闪,原因是end == parentLeft 没有过滤掉,显示出来柱状图了。
return;
} else if (rectF.left < parentLeft && rectF.right > parentLeft) {
//左边部分滑入的时候,处理柱状图的显示
rectF.left = parentLeft;
Path path = CanvasUtil.createRectRoundPath(rectF, radius, RoundRectType.TYPE_RIGHT);
mBarChartPaint.setColor(mBarChartAttrs.chartEdgeColor);
canvas.drawPath(path, mBarChartPaint);
} else if (DecimalUtil.bigOrEquals(rectF.left, parentLeft) && DecimalUtil.smallOrEquals(rectF.right, parentRight)) {
//中间的; 浮点数的 == 比较需要注意
mBarChartPaint.setColor(mBarChartAttrs.chartColor);
Path path = CanvasUtil.createRectRoundPath(rectF, radius);
canvas.drawPath(path, mBarChartPaint);
} else if (DecimalUtil.smallOrEquals(rectF.left, parentRight) &&
rectF.right > parentRight) {
//右边部分滑出的时候,处理柱状图,文字的显示
float distance = (parentRight - rectF.left);
rectF.right = rectF.left + distance;
Path path = CanvasUtil.createRectRoundPath(rectF, radius, RoundRectType.TYPE_LEFT);
mBarChartPaint.setColor(mBarChartAttrs.chartEdgeColor);
canvas.drawPath(path, mBarChartPaint);
}
}
下面是个步数的周视图:
2.drawHighLight
首先看下这里高亮具体是如何显示的,直观的看些图:
当前的RecyclerView的getChildCount内每个ItemView对应的Entry,设定了一个 selected 的字段来确定显示高亮,至于该字段的值的设定及变化,后续章节会介绍,这里假定已经确定了当前的某一个ItemView的Entry的selected是选中状态的,它有可能在中间,或者在边界需要处理边界绘制的问题,这里分画顶部的矩形框及drawTextValue值,底部绘制drawLine(这个不存在绘制的边界问题)
//绘制选中时 highLight 标线及浮框。
public <E extends BaseYAxis> void drawHighLight(Canvas canvas, @NonNull RecyclerView parent, E yAxis) {
if (mBarChartAttrs.enableValueMark) {
int childCount = parent.getChildCount();
View child;
for (int i = 0; i < childCount; i++) {
child = parent.getChildAt(i);
T entry = (T) child.getTag();
RectF rectF = ChartComputeUtil.getBarChartRectF(child, parent, yAxis, mBarChartAttrs, entry);
float width = child.getWidth();
float childCenter = child.getLeft() + width / 2;
String valueStr = mHighLightValueFormatter.getBarLabel(entry);
if (entry.isSelected() && !TextUtils.isEmpty(valueStr)) {
int chartColor = getChartColor(entry);
float rectHeight = drawHighLightValue(canvas, valueStr, childCenter, parent, chartColor);//绘制顶部的poupWindow,高亮矩形框及drawText
float[] points = new float[]{childCenter, rectF.top, childCenter, rectHeight};
drawHighLightLine(canvas, points, chartColor);//绘制底部的Line
}
}
}
}
以上中 drawHighLightValue 中, 包含了绘制矩形、drawText两项具体的内容:
//绘制柱状图选中浮框
protected float drawHighLightValue(Canvas canvas, String valueStr, float childCenter,
RecyclerView parent, int barChartColor) {
float parentTop = parent.getPaddingTop();
float contentRight = parent.getWidth() - parent.getPaddingRight();
float contentLeft = parent.getPaddingLeft();
String[] strings = valueStr.split(DefaultHighLightMarkValueFormatter.CONNECT_STR);
float leftEdgeDistance = Math.abs(childCenter - contentLeft);
float rightEdgeDistance = Math.abs(contentRight - childCenter);
float leftPadding = DisplayUtil.dip2px(8);
float rightPadding = DisplayUtil.dip2px(8);
float centerPadding = DisplayUtil.dip2px(16);
float rectBottom = parentTop;
float txtTopPadding = DisplayUtil.dip2px(8);
String leftStr = strings[0];
String rightStr = strings[1];
float txtLeftWidth = mHighLightValuePaint.measureText(leftStr);
float txtRightWidth = mHighLightValuePaint.measureText(rightStr);
float rectFHeight = TextUtil.getTxtHeight1(mHighLightValuePaint) + txtTopPadding * 2;
float txtWidth = txtLeftWidth + txtRightWidth + leftPadding +
rightPadding + centerPadding;
float edgeDistance = txtWidth / 2.0f;
float rectTop = parentTop - rectFHeight;
//绘制RectF
RectF rectF = new RectF();
mBarChartPaint.setColor(barChartColor);
if (leftEdgeDistance <= edgeDistance) {//矩形框靠左对齐
rectF.set(contentLeft, rectTop, contentLeft + txtWidth, rectBottom);
float radius = DisplayUtil.dip2px(8);
canvas.drawRoundRect(rectF, radius, radius, mBarChartPaint);
} else if (rightEdgeDistance <= edgeDistance) {//矩形框靠右对齐
rectF.set(contentRight - txtWidth, rectTop, contentRight, rectBottom);
float radius = DisplayUtil.dip2px(8);
canvas.drawRoundRect(rectF, radius, radius, mBarChartPaint);
} else {//居中对齐。
rectF.set(childCenter - edgeDistance, rectTop, childCenter + edgeDistance, rectBottom);
float radius = DisplayUtil.dip2px(8);
canvas.drawRoundRect(rectF, radius, radius, mBarChartPaint);
}
//绘文字
RectF leftRectF = new RectF(rectF.left + leftPadding, rectTop + txtTopPadding,
rectF.left + leftPadding +
txtLeftWidth, rectTop + txtTopPadding + rectFHeight);
mHighLightValuePaint.setTextAlign(Paint.Align.LEFT);
Paint.FontMetrics fontMetrics = mHighLightValuePaint.getFontMetrics();
float top = fontMetrics.top;//为基线到字体上边框的距离,即上图中的top
float bottom = fontMetrics.bottom;//为基线到字体下边框的距离,即上图中的bottom
int baseLineY = (int) (leftRectF.centerY() + (top + bottom) / 2);//基线中间点的y轴计算公式
canvas.drawText(leftStr, rectF.left + leftPadding, baseLineY, mHighLightValuePaint);
float dividerLineStartX = rectF.left + leftPadding + txtLeftWidth + centerPadding / 2.0f;
float dividerLineStartY = rectTop + DisplayUtil.dip2px(10);
float dividerLineEndX = dividerLineStartX;
float dividerLineEndY = rectBottom - DisplayUtil.dip2px(10);
float[] lines = new float[]{dividerLineStartX, dividerLineStartY,
dividerLineEndX, dividerLineEndY};
canvas.drawLines(lines, mHighLightValuePaint);
float rightRectFStart = rectF.left + leftPadding + txtLeftWidth + centerPadding;
RectF rightRectF = new RectF(rightRectFStart, rectTop + txtTopPadding,
rectF.right - rightPadding, rectBottom - txtTopPadding);
canvas.drawText(rightStr, rightRectF.left, baseLineY, mHighLightValuePaint);
return rectFHeight;
}
具体的文案绘制内容 valueStr 从 ValueFormatter里获取,我这里需要拆分一下ValueStr,然后绘制leftStr, rightStr这里相当于各个项目自己的需求。
来源:https://juejin.cn/post/7165304701355819039
![](https://www.aspxhome.com/images/zang.png)
![](https://www.aspxhome.com/images/jiucuo.png)
猜你喜欢
- java7增强的try语句关闭资源传统的关闭资源方式import java.io.FileInputStream;import java.i
- 前言需要对一个List中的对象进行唯一值属性去重,属性求和,对象假设为BillsNums,有id、nums、sums三个属性,其中id表示唯
- 在Unity开发中捕捉Android的常用事件其实很简单Input.GetKey(KeyCode.Escape) Input.G
- C/C++的数据类型:一,整型Turbo C: [signed] int 2Byte//有符号数,-32768~32
- \r与\n到底有何区别,编码的时候又应该如何使用,我们下面来了解一下。区别:\r:全称:carriage return (carriage是
- 不一致,那么用来接收查询出来的result对应的数据将会是Null,如果不使用resultMap,那么一般为了避免pojo对象对应的属性为N
- JVM应用度量框架Micrometer实战前提spring-actuator做度量统计收集,使用Prometheus(普罗米修斯)进行数据收
- 本人从开始用Android Studio到现在已经快一年了吧,在我刚开始用的时候Android Studio还是1.2的版本。当时安装会因为
- cron表达式每天整点执行一次的问题最近写了个发短信的定时任务,需求是每天上午10点发信息,然后我百度了一篇文章,复制了一个cron表达式:
- 其实很简单,就是把我们的数据库文件放到我们的手机里,所以不必局限在哪个地方写这个代码,在第一次创建数据库的时候可以,我觉得在软件起动页里效果
- 这篇文章主要介绍了springboot如何使用AOP做访问请求日志,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价
- 本文实例为大家分享了android实现圆环倒计时控件的具体代码,供大家参考,具体内容如下1.自定义属性<?xml version=&q
- 多表联合查询resultType的返回值一般数据按参数类型返回<select id="queryCarIdList"
- 前言泛型,一个孤独的守门者。大家可能会有疑问,我为什么叫做泛型是一个守门者。这其实是我个人的看法而已,我的意思是说泛型没有其看起来那么深不可
- webservice restful接口跟soap协议的接口实现大同小异,只是在提供服务的类/接口的注解上存在差异,具体看下面的代码,然后自
- 一、引言在许多编程语言中,都有函数回调这一概念。C 和 C++ 中有函数指针,因此可以将函数作为参数传给其它函数,以便过后调用。而在 Jav
- Java中线程的创建有两种方式: 1. 通过继承Thread类,重写Thread的run()方法,将线程运行的逻辑放在
- 概念:LruCache什么是LruCache?LruCache实现原理是什么?这两个问题其实可以作为一个问题来回答,知道了什么是 LruCa
- 背景知识Fluent Interface是一种通过连续的方法调用以完成特定逻辑处理的API实现方式,在代码中引入Fluent Interfa
- 先看看电影票在线选座功能实现的效果图:界面比较粗糙,主要看原理。这个界面主要包括以下几部分1、座位 2、左边的排数 3、左上方的缩略图 4、