android 自定义多边形,Android自定义View实现多边形统计图示例代码

本文介绍了作者利用空闲时间自定义的一个多边形统计图View,可设置边数、顶点位置、文字等。内容包括绘制多边形、连线、覆盖区域和文字的处理,还演示了如何通过属性动画添加动画效果。最后,作者提供了XML属性定义和使用方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

最近利用空闲时间学习了自定义View的一些知识,为了巩固,写了一个小东西,顺便分享出来,下面话不多说了,来一起看看详细的介绍吧。

简介

一个多边形统计图。边数,每个方向的值,每个点的文字等等都是可以设置的。

d66c3d3ce0b5d1153dcb961cc6b8304e.png

下面就来分析一下这个自定义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文件

1056c45fcd9418f99276641412855966.png

然后指定你想要的属性名称和类型

a2ef8bfc981405e16ca62629ae1a2313.png

再然后就是让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"

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();

}

这个东西不能忘了

快速使用

感谢你看到这里,如果你想使用这个组件但是不想自己写的话欢迎访问

项目Github里面有讲如何添加依赖,导入组件

如果能帮到你的话,不胜荣幸!!!

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对的支持。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值