Android自定义View实现多边形统计图示例代码
作者:BigTotoro 发布时间:2021-09-28 13:17:44
前言
最近利用空闲时间学习了自定义View的一些知识,为了巩固,写了一个小东西,顺便分享出来,下面话不多说了,来一起看看详细的介绍吧。
简介
一个多边形统计图。边数,每个方向的值,每个点的文字等等都是可以设置的。
下面就来分析一下这个自定义View
这个view由以下几个部分组成
M层N边形
中心到各顶点的连线
填充区域
文字
@Override
protected void onDraw(Canvas canvas) {
if (!canDraw()) {
return;
}
canvas.translate(width / 2, height / 2);
computeMaxPoint();
drawPolygon(canvas);
drawLine(canvas);
drawArea(canvas);
drawText(canvas);
}
我们一步一步来说明
绘制多边形
绘制多边形主要用到的是Path这个东西。具体的思路就是先计算好每个点的位置,同Path的lineTo方法连接起来,然后绘制。
我的做法是先算出最大的半径(再之后还会用到,建议单独存起来),然后根据所在层数来计算每一层的半径,利用cos函数各sin函数计算出每一层各顶点的位置。
计算最大半径并且保存顶点
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
width = w;
height = h;
maxRadius = (float) ((width / 2) * 0.8);
postInvalidate();
}
/*
计算最大半径,之后的位置都是基于最大半径的比例
*/
public void computeMaxPoint() {
maxPointXList = new ArrayList<>();
maxPointYList = new ArrayList<>();
for (int i = 0; i < eageCount; i++) {
float currentAngle = i * angle - 90;
float currentX = (float) (maxRadius * Math.cos((currentAngle / 180) * Math.PI));
float currentY = (float) (maxRadius * Math.sin((currentAngle / 180) * Math.PI));
maxPointXList.add(currentX);
maxPointYList.add(currentY);
}
}
注意:cos和sin都是按照弧度制计算的,要换算。
这里解释一下为currentAngle什么要减去90度
按照android的坐标系,如果不减去90度直接乘上cos的话,第一个顶点会默认在中心的右侧,而一般的认知是第一个点在正上方,所以减去90度
按照比例和层数边数绘制多边形
/*
绘制多边形和每一层
*/
private void drawPolygon(Canvas canvas) {
Path path = new Path();
for (int i = 0; i < loopCount; i++) {
path.reset();
//依据最大半径和角度来判断每一层点的位置
float rate = computeRate(i + 1, loopCount);
for (int j = 0; j < eageCount; j++) {
float currentX = maxPointXList.get(j) * rate;
float currentY = maxPointYList.get(j) * rate;
if (j == 0) {
path.moveTo(currentX, currentY);
} else {
path.lineTo(currentX, currentY);
}
}
path.close();
canvas.drawPath(path, eagePaint);
}
}
代码还是很容易的吧,要是看不懂的话自己动手算算就知道了,很容易计算各个点的位置。
绘制连线
由于之前保存了顶点的坐标,这个就很容易了
/*
画出从中心向各顶点的连线
*/
private void drawLine(Canvas canvas) {
Path path = new Path();
for (int i = 0; i < eageCount; i++) {
path.reset();
path.lineTo(maxPointXList.get(i), maxPointYList.get(i));
canvas.drawPath(path, eagePaint);
}
}
绘制覆盖区域
这个原理其实和绘制多边形是一样的,就是对顶点坐标乘的比例发生了变化。每个方向的数值是由用户传递进来的。
/*
绘制个方向值覆盖的区域
*/
private void drawArea(Canvas canvas) {
Path path = new Path();
//原理就是用path根据各方向值创建一个封闭的区域,然后填充
for (int i = 0; i < eageCount; i++) {
float rate = pointValue.get(i);
float currentX = maxPointXList.get(i) * rate;
float currentY = maxPointYList.get(i) * rate;
if (i == 0) {
path.moveTo(currentX, currentY);
} else {
path.lineTo(currentX, currentY);
}
}
path.close();
canvas.drawPath(path, areaPaint);
}
绘制文字
说实话,前面的没有什么难点,但是唯独绘制文字有许多麻烦事。主要是文字是默认自左向右的,最上面和最先面的文字倒是没啥,左侧和右侧的文字就会出现问题了,文字会绘制到多边形上,看起来特别难受。这里我的解决办法就是前面图中看到的,让字跟着多边形的顶点位置一起旋转。
/*
绘制文字
*/
private void drawText(Canvas canvas) {
if (pointName == null) {
return;
}
//绘制文字的难点在于无法最好的适配屏幕的位置,会发生难以控制的偏倚
for (int i = 0; i < pointName.size(); i++) {
//解决办法就是让文字在不同的角度也发生旋转,并且在x轴上减去一定的数值来保证正确的位置
float currentAngle = i * angle;
//180度需要也别的处理,让它正着显示,不然就是倒着的
if (currentAngle == 180) {
float currentX = maxPointXList.get(i) * 1.1f;
float currentY = maxPointYList.get(i) * 1.1f;
canvas.drawText(pointName.get(i), currentX - (textPaint.getTextSize() / 4)
* (pointName.get(i).length()), currentY, textPaint);
} else {
canvas.save();
float currentX = maxPointXList.get(0) * 1.1f;
float currentY = maxPointYList.get(0) * 1.1f;
//旋转画布,达到旋转文字的效果
canvas.rotate(currentAngle);
canvas.drawText(pointName.get(i), currentX - (textPaint.getTextSize() / 4)
* (pointName.get(i).length()), currentY, textPaint);
canvas.restore();
}
}
}
到这里,整个组件就绘制完成了
额外的属性
如果单纯只是想画出这个组件来,其实没啥难度。我们可以在加一些别的东西让他更加实用。
动画效果
利用属性动画的知识,我们可以做到让中间的填充区域慢慢的扩散出来。原理也简单,就是把0到1用属性计算展开,当做一个演化的比例,让各个方向的值乘上这个数值,绘制一个比原先覆盖区域小的区域就可以了。
/*
用属性动画绘制组件
*/
public void draw() {
if (canDraw()) {
final Float[] trueValues = pointValue.toArray(new Float[pointValue.size()]);
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 1);
valueAnimator.setDuration(1000);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float rate = animation.getAnimatedFraction();
for (int i = 0; i < pointValue.size(); i++) {
pointValue.set(i, trueValues[i] * rate);
}
invalidate();
}
});
valueAnimator.start();
}
}
定义xml属性
我们正常使用系统组件的时候都会写一大堆的xml来控制我们组件的属性,自定义View也可以尝试这些
首先在value下创建atts文件
然后指定你想要的属性名称和类型
再然后就是让atts和我们的view联系上。这个也简单,仔细观察View的构造方法中的参数,有这么一个玩意 AttributeSet attrs
public PolygonView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public PolygonView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
它就是联系xml和view的纽带
xmlns:app="http://schemas.android.com/apk/res-auto"
<com.totoro.xkf.polygonview.PolygonView
android:id="@+id/pv_polygon_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:areaColor="@android:color/holo_blue_light"
app:eageColor="@android:color/black"
app:eageCount="6"
app:loopCount="4"
app:textColor="@android:color/black" />
public void init(Context context, AttributeSet attrs) {
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.Polygon);
initPaint();
setTextColor(typedArray.getColor(R.styleable.Polygon_textColor, Color.BLACK));
setLoopCount(typedArray.getInteger(R.styleable.Polygon_loopCount, 0));
setEageCount(typedArray.getInteger(R.styleable.Polygon_eageCount, 0));
setAreaColor(typedArray.getColor(R.styleable.Polygon_areaColor, Color.BLUE));
setEageColor(typedArray.getColor(R.styleable.Polygon_eageColor, Color.GRAY));
typedArray.recycle();
}
xmlns:app=http://schemas.android.com/apk/res-auto
这个东西不能忘了
快速使用
感谢你看到这里,如果你想使用这个组件但是不想自己写的话欢迎访问
项目Github里面有讲如何添加依赖,导入组件
如果能帮到你的话,不胜荣幸!!!
来源:https://juejin.im/post/5a4b8a786fb9a0450b66c417


猜你喜欢
- 1. 前言在目前众多编程语言中,Java 语言的表现还是抢眼,不论是企业级服务端开发,还是 Andorid 客户端开发,都是作为开发语言的首
- 背景介绍在实际项目中,特别是一些管理后台类的项目,会遇到底层数据是按照一对多关系的数据表存储的管理界面。列表页是一对多关系中一对应的数据列表
- mybatis-plus依赖导入<dependency> <groupId>
- spring cloud zuul增加header传输在使用OAuth2.0传输权限认证,为了再调用其他的项目的时候获取token,必须在t
- Maven的安装安装Maven之前要确保已经安装好了jdk,并且配置好了环境变量JAVA_HOME。具体安装步骤如下:1. 从ap
- 数据库结构如下strategy中有外键member_id(关联member表)外键strategy_category(关联category表
- 一、泛型1.1 泛型类的定义// 1. 尖括号 <> 是泛型的标志// 2. E 是类型变量(Type Variable),变量
- 本文实例分析了java中成员变量与局部变量区别。分享给大家供大家参考。具体分析如下:成员变量:在这个类里定义的私有变量,属于这个类。创建以及
- 问题注意:本人使用的Spring Boot 2.0.2, 对1.5.x系列未必有用。官方文档在这里直接解决办法0, 移除spring-boo
- 在开发时,手机先要ROOT,然后在通过代码改变权限。<span style="color:#330033;">
- C语言数据结构之二叉树的非递归后序遍历算法前言:前序、中序、后序的非递归遍历中,要数后序最为麻烦,如果只在栈中保留指向结点的指针,那是不够的
- 要求:如下图,使用线程操作 1、实时显示当前时间 2、输入加数和被加数,自动出现结果 分析:两个问题解决的方式一致,使用子线程进
- 写在前面元旦三天在家闲着无事,就看了看Linq的相关内容,也准备系统的学习一下,作为学习Linq的前奏,还是先得说说Lambda与匿名方法的
- 具体安装步骤,不再赘述,仅附上个人工作、学习中的对 EasyCode 的详细配置。插件链接地址:https://gitee.com/make
- 虽然Android给我们提供了众多组件,但是使用起来都不是很方便,我们开发的APK都有自己的风格,如果使用了系统自带的组件,总是觉得和应用的
- 对于ApplicationListener使用Spring的应该也熟悉,因为这就是我们平时学习的观察者模式的实际代表。Spring基于Jav
- 前两天给同事做 code review,感觉自己对 Java 的 Generics 掌握得不够好,便拿出 《Effective Java》1
- 实现步骤:工具:IDEA数据库版本:mysql5.7一、环境搭建1.创建springboot项目pom.xml2.pom.xml : spr
- javaWeb 四大域对象1)和属性相关的方法Object getAttribute(String name) 获取指定的属性En
- 什么是抽象类什么是抽象类呢?抽象类顾名思义就是很抽象,就是当我们没有足够的信息去描述这个类的时候我们就可以先不用描述,这样的类就是抽象类。用