Android六边形战士能力图绘制,Compose实现

1,497 阅读5分钟

数据可视化能力图分析,包含了多边形能力图:四边形,五边形,六边形,七边形,八边形等等能力图

可视化图表系列如下:
(一)Compose曲线图表库WXChart,你只需要提供数据配置就行了
(二)Compose折线图,贝赛尔曲线图,柱状图,圆饼图,圆环图。带动画和点击效果
(三)全网最火视频,Compose代码写出来,动态可视化趋势视频,帅到爆
(四)全网最火可视化趋势视频实现深度解析,同时新增条形图表
(五)庆元旦,出排名,手撸全网火爆的排名视频,排名动态可视化
(六)Android六边形战士能力图绘制,Compose实现
(七)Android之中美PK,赛事PK对比图Compose实现
(八)Android之等级金字塔之Compose智能实现

以下四边形游戏中某战力图: 33333.gif

以下是掘金掘友分五边形社区图: 55555.gif

以下可以是游戏中某个人物能力八边形图: 66666.gif

前言

对于多边形能力图,可以可视化很直观的就能分析某些能力属性上是否均衡,是否有偏科现象,比如:

可以很直观分析学生成绩是否偏科

22222.gif

可以很直观看出NBA球员能力是否均衡:
以下是NBA官方数据 约基奇能力图
11111.gif

作为纯Android开发,假设有下边这五项技能,我们的每一项技能研究得有多深呢?
77777.gif

一、数据模型设计

1. PolygonModel:设计为绘制的数据模型

data class PolygonModel(
    val num: Int,// N变形里面有几层背景
    val list: MutableList<PolygonBean>, //N边形 数据 list.size为多少N值为多少
    val max: Float, //标准总分多少,比如纯Android开发,这五项技能力值最大为100,
    val radiusOffset: Float = 80f, //视图长款各一半最小的那个座位最大半径,减去该值后就是绘制的N变形半径
    var color: Color = Color(0x30ACAA00),//背景填充颜色
    val valueColor: Color = Color(0x50FF0000),//值填充背景颜色
    val valueLineColor: Color = Color(0xFFFF0000)//值外层线条颜色
) : ChartBaseModel()

2. ChartBaseModel的基础类里面只用到以下几个属性

open class ChartBaseModel {
    var offsetx: Float = 10f//原点 x偏移,正数 视图中心右偏移,负数左偏移
    var offsety: Float = 10f //原点 y 偏移 正数 视图中心下偏移,负数上偏移
    var durationMillis: Int = 1000 // 动画时长
    var animateDelay: Long = 1000 //动画延迟执行时间 
}

3. PolygonBean为每一项能力的属性,title为属性名称,value为属性值

data class PolygonBean(val title: String, val value: Float)

二、示例数据提供和调用:

1、repositories中添加如下maven

    repositories {
        maven { url 'https://repo1.maven.org/maven2/' }
        maven { url 'https://s01.oss.sonatype.org/content/repositories/releases/' }
    }
}

2、 dependencies中添加依赖

implementation("io.github.wgllss:Wgllss-WXChart:1.0.16")

3. 在android的ViewModel中提供数据
具体业务数据可以从网络获取,获取完了造成以下配置模型


class PolygonViewModel : ViewModel() {
    private val _datas = MutableLiveData<PolygonModel>()
    val polygonModel: LiveData<PolygonModel> = _datas

    fun setData() {
        _datas.value = PolygonModel(
            5, //背景层N变形从小到大个数
            mutableListOf(
                PolygonBean("应用层UI", 95f),
                PolygonBean("Framework", 81f),
                PolygonBean("性能优化", 94f),
                PolygonBean("NDK音视频", 88f),
                PolygonBean("架构", 80f),
//                PolygonBean("魔法", 58f),
//                PolygonBean("装备", 78f),
//                PolygonBean("血量", 98f),
//                PolygonBean("政治", 75f),
//                PolygonBean("生物", 89f),
//                PolygonBean("地理", 69f),
//                PolygonBean("篮板", 89f),
//                PolygonBean("篮板", 89f),

            ), 100f,//满分多少
            toDp(80f), //最大半径偏移
            color = Color(0x30EEAA00),//背景填充颜色
            valueColor = Color(0x50FF0000),//值填充颜色
            valueLineColor = Color(0xFFFF0000)//值外层线条颜色
        )
    }

    fun toDp(size: Float) = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, size, MyApp.application.resources.displayMetrics)
}

4. Composes使用方调用端如下:
真正绘制N边形战力图方法是 drawPolygonChart
传入的TextStyle 可以配置各项指标熟悉文字颜色,大小 等

@Composable
fun polygonChart(polygonViewModel: PolygonViewModel) {
    val textMeasurer = rememberTextMeasurer()
    val chatModel by polygonViewModel.polygonModel.observeAsState()
    chatModel?.let {
        Column(
            modifier = Modifier
                .fillMaxWidth()
                .fillMaxHeight()
        ) {

            val modifier = Modifier
                .fillMaxWidth()
                .fillMaxHeight()
                .background(Color.White)
            //TextStyle 可以配置各项指标熟悉文字颜色,大小 等
            drawPolygonChart(
                modifier = modifier, textMeasurer, TextStyle(
                    fontSize = 20.sp, fontWeight = FontWeight.Normal, color = Color.Black
                ), it
            )
        }
    }
}

三、N边形战力图真正绘制实现

真正绘制借助于Compose的 Canvas 和drawPath方法,只需要计算出N变形各个点的位置X,Y坐标就可以了。
全是初中数学中几何知识

  1. 先根据数据个数计算出N变形的弧度,然后转化成角度:
    val degrees = 360 / it.list.size
    val radians = degrees * (PI / 180)
  2. 角度大小依次为: radians * k : k从0到list.size-1
  3. 每个点的
    X值为: centerX + distence
    Y值为:centerY - radius * cosValue
    如下面计算得出:
    val cosValue = cos(radians * k).toFloat()
    val sinValue = sin(radians * k).toFloat()
    val distence = radius * sinValue
    path.lineTo(centerX + distence, centerY - radius * cosValue)
  4. 具体实现如下:


@Composable
fun drawPolygonChart(modifier: Modifier, textMeasurer: TextMeasurer, style: TextStyle, it: PolygonModel) {
    val context = LocalContext.current
    val fontSizeDip = DisplayUtil.sp2Dip(context, style.fontSize.value)

    var mSize by remember { mutableStateOf(Size(0f, 0f)) }
    val centerX = mSize.center.x
    val centerY = mSize.center.y
    val radiusMax = min(centerX - it.radiusOffset, centerY - it.radiusOffset) //饼图半径
    val degrees = 360 / it.list.size
    val radians = degrees * (PI / 180)
    val abs = radiusMax / it.max

    var animatedloadPos by remember { mutableStateOf(0) }
    val animatedValue by animateFloatAsState(targetValue = if (animatedloadPos == 0) 0f else 1f, animationSpec = tween(it.durationMillis))
    LaunchedEffect(Unit) {
        delay(it.animateDelay)
        animatedloadPos = 1
    }

    Canvas(modifier) {
        mSize = size
        val path = Path()
        //绘制背景一层一层N边形
        for (i in 0 until it.num) {
            val radius = (i + 1) * (radiusMax / it.num)
            path.moveTo(centerX, centerY - radius)
            for (k in 1 until it.list.size) {
                val cosValue = cos(radians * k).toFloat()
                val sinValue = sin(radians * k).toFloat()
                val distence = radius * sinValue
                path.lineTo(centerX + distence, centerY - radius * cosValue)
            }
            path.lineTo(centerX, centerY - radius)
            drawPath(path = path, color = it.color, style = Fill)
            path.reset()
        }

        //绘制当前能力值N边形
        path.moveTo(centerX, centerY - abs * it.list[0].value * animatedValue)
        for (i in 1 until it.list.size) {
            val cosValue = cos(radians * i).toFloat()
            val sinValue = sin(radians * i).toFloat()
            val radius = abs * it.list[i].value * animatedValue
            val distence = radius * sinValue
            path.lineTo(centerX + distence, centerY - radius * cosValue)
        }
        path.lineTo(centerX, centerY - abs * it.list[0].value * animatedValue)
        drawPath(path = path, color = it.valueColor, style = Fill)
        drawPath(path = path, color = it.valueLineColor, style = Stroke(width = 2f))
        path.close()

        //绘制当前N变形各个点上面的值 文字数字
        for (i in 0 until it.list.size) {
            val cosValue = cos(radians * i).toFloat()
            val sinValue = sin(radians * i).toFloat()
            val distence = radiusMax * sinValue
            val title = "${it.list[i].title}: ${String.format("%.0f", it.list[i].value)}"
            val dl = title.length * fontSizeDip
            if (sinValue == 0.0f || cosValue == 1.0f) {
                drawText(textMeasurer = textMeasurer, style = style, text = title, topLeft = Offset(centerX - dl / 2, max(0f, centerY - radiusMax - 4 * fontSizeDip)))
            } else if (sinValue == 0.0f || cosValue == -1.0f) {
                drawText(textMeasurer = textMeasurer, style = style, text = title, topLeft = Offset(centerX - dl / 2, min(size.height, centerY + radiusMax + 3 * fontSizeDip)))
            } else if (distence > 0f) {
                drawText(textMeasurer = textMeasurer, style = style, text = title, topLeft = Offset(centerX + distence + 3 * fontSizeDip, centerY - radiusMax * cosValue - fontSizeDip))
            } else if (distence < 0f) {
                drawText(textMeasurer = textMeasurer, style = style, text = title, topLeft = Offset(centerX + distence - dl - 3 * fontSizeDip, centerY - radiusMax * cosValue - fontSizeDip))
            }
        }
    }
}

总结

本文重点介绍了怎么用Compose绘制出N变形战力图
已经封装成库,你只需要准备好数据就可以了。

github地址
gitee地址

感谢阅读:

欢迎 关注,点赞、收藏

这里你会学到不一样的东西