Android自定义View实现三角到八角的属性分布图-雷达图(蜘蛛网图)

Android自定义View实现三角到八角的属性分布图-雷达图(蜘蛛网图)

前言

刚开始学习自定义view,简单完成了一个属性分布器,可以实现三条到八条属性的分布图,依次是三角形到正八边形,可以在xml和代码中设置属性个数和显示层数,并没有用到什么复杂的知识点,简单的canvas绘制就可以完成,复杂一点点可能就是绘制坐标的计算,我是使用的kotlin进行的编写,也是一个小新手,没使用太多的简化写法。这篇文章主要做一个记录,如果能帮到大家也自然是极好。废话不多说,开始我的炸弹秀,不好意思跑题了,开始

自定义View的关键点

自定义View分为两种,一是自定义单一View(不包含子View),二是自定义ViewGroup(包含子View),本例是一个单一的View。
首先写一个类继承于View,有四个构造方法

class MyView : View {
	// 自定义View有四个构造函数
    // 如果View是在Java代码里面new的,则调用第一个构造函数
    constructor(context: Context) : super(context)
    
	// 如果View是在.xml里声明的,则调用第二个构造函数
	// 自定义属性是从AttributeSet参数传进来的
    constructor(context: Context, attributeSet: AttributeSet) : this(context, attributeSet, 0)
    
	// 不会自动调用
	// 一般是在第二个构造函数里主动调用
	// 如View有style属性时
	// 这个方法里的内容是自定义属性的内容
    constructor(context: Context, attributeSet: AttributeSet, defStyleAttr: Int) : super(context, attributeSet, defStyleAttr) {
        val typedArray = context.obtainStyledAttributes(attributeSet, R.styleable.MyView)
        if (typedArray != null) {
            //MyView_line_color线条颜色
            lineColor = typedArray.getColor(R.styleable.MyView_line_color, Color.BLACK)
            //MyView_line_size线条宽度
            paintSize = typedArray.getDimension(R.styleable.MyView_line_size, 5f)
            //MyView_floor_count绘制层数(不得小于各元素分配的比例)
            floorCount = typedArray.getInt(R.styleable.MyView_floor_count, 3)
            //MyView_angle_count绘制元素个数
            angleCount = typedArray.getInt(R.styleable.MyView_angle_count, 3)
            //MyView_text_color文本颜色
            textColor = typedArray.getColor(R.styleable.MyView_text_color, Color.BLACK)
            //MyView_text_size文本大小
            textSize = typedArray.getDimension(R.styleable.MyView_text_size, 18f.dp)
            //MyView_area_color覆盖区域颜色
            areaColor = typedArray.getColor(R.styleable.MyView_area_color, ContextCompat.getColor(context, R.color.trared))
            //MyView_default_text_padding默认文本与图形间距
            defaultTextPadding = typedArray.getDimension(R.styleable.MyView_default_text_padding, 20f)
        }
    }
    
    //API21之后才使用
    // 不会自动调用
    // 一般是在第二个构造函数里主动调用
    // 如View有style属性时
    constructor(context: Context, attributeSet: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : super(context, attributeSet, defStyleAttr, defStyleRes)

}

构造方法写好后就可以进行view的绘制了,不过一般的view绘制必须要满足一些view的属性,比如warp_content的实现,在onMeasure方法中判断warp_content并默认控件宽高

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        val widthMode = MeasureSpec.getMode(widthMeasureSpec)
        val widthSize = MeasureSpec.getSize(widthMeasureSpec)
        val heightMode = MeasureSpec.getMode(heightMeasureSpec)
        val heightSize = MeasureSpec.getSize(heightMeasureSpec)


        //设置warp_content的默认宽高
        val width = 400
        val height = 400
        //AT_MOST对应wrap_content;EXACTLY对应match_parent
        if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(width, height)
        } else if (widthMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(width, heightSize)
        } else if (heightMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(widthSize, height)
        }
    }

在这里我们的view还必须支持padding属性,否则在xml中的padding属性将失效,getWidth和getHeight都是包含了padding的,所以我们定义两个新的参数来存储去掉了padding的宽高

var mWidth = width - paddingLeft - paddingRight
var mHeight = height - paddingBottom - paddingTop
//比较并返回较小的值,由于都是正多边形,所以设定宽高相等是最合适的
mWidth = mWidth.coerceAtMost(mHeight)
mHeight = mWidth

自定义属性,在res的values目录下创建自定义资源文件attrs_my_view.xml,名字随意,按照下面的格式写后便可以在布局文件中使用这些属性

<?xml version="1.0" encoding="utf-8"?>
<resources>
	<!--name是view类名--!>
    <declare-styleable name="MyView">
		<!--自定义属性名和值的类型--!>
        <attr name="line_color" format="color" />
        <attr name="line_size" format="dimension" />
        <attr name="floor_count" format="integer" />
        <attr name="angle_count" format="integer" />
        <attr name="text_color" format="color" />
        <attr name="area_color" format="color" />
        <attr name="text_size" format="dimension" />
        <attr name="default_text_padding" format="dimension" />
    </declare-styleable>
</resources>

绘制多边形

由于各多边形的绘制坐标有出入,所以我这里依次从三角形到正八边形都绘制了一遍,在onDraw中根据代码或xml中设置的属性个数判断绘制哪一个,这里我演示一下三角形的绘制

	//首先是画笔的初始化
    private var paint = Paint()
    paint.color = lineColor
    paint.strokeWidth = paintSize
    paint.style = Paint.Style.STROKE
    
    //defaultTextPadding 是文本与边框的距离
    //textSize是文字大小(单位是px,相当于每个字的宽高)
    //textLength是文字最大长度,考虑美观建议标题文字长度相同
    //三角形宽度,边长,去掉了padding属性的参数表示整个图包括文本的宽高
    //这里的ww就是不包含文本的图形宽度
    val ww = mWidth - defaultTextPadding * 2 - textLength * textSize
    //中心点各对角线的夹角  一定是平分360度的
    val angle = (Math.PI * 2 / angleCount).toFloat()
    //本View有大量的三角函数使用,一些角度计算关系就不介绍了,我写的时候脑袋都疼!
    //三角形高度,顶点到底边的垂直线 通过三角函数计算
    var hh = ww * sin(angle / 2)
    val pathLine = Path()
    //中心点到各角的距离
    val r = ww / 2 / sin(angle / 2)

找到图形中心点!!!!这里是重要的,所有图都需要根据中心点向外绘制

	/**
	*                      图形中心点
	*   各正多边形的中心点横坐标X都可以是整个控件的横向中心位置
    *   但是由于三角形,正五边形,正七边形比较特殊
    *   所以它们的中心点纵坐标Y并不在整个控件的中心位置
    *   这个时候就需要根据实际情况做一下小运算
    *   比如说三角形,首先算得三角形的宽度作为底边长
    *   因为中心点到各角的连线夹角都是相等的
    *   所以根据角度和边长就可算得中心点到各夹角的连线长度
    *   然后根据这个长度就可已算出纵坐标Y的位置
    *   之后的图形绘制都需要根据中心点和中心点到各夹角的长度来计算
    */
    val centerX = width / 2
    //图形中心点y
    val centerY = r + paddingTop + textSize + defaultTextPadding

绘制边框
只绘制了边框的三层三角形

	//绘制边框
	//这里有个判断主要是满足有的需求(不想绘制边框的,应该没人会这么干吧)
	if (isShowLine) {
		for (i in 1..floorCount) {
			val ur = r * i / floorCount
			//moveTo是移动到某个点,用来作为绘制的初始点
			pathLine.moveTo(centerX + 0f, centerY - ur)
			//lineTo是从上一个点画线到当前点
			pathLine.lineTo(centerX + ur * sin(angle / 2), centerY + ur * cos(angle / 2))
			pathLine.lineTo(centerX - ur * sin(angle / 2), centerY + ur * cos(angle / 2))
			//close是闭合整个路径,第一个点到最后一个点连线
			pathLine.close()
        }
        canvas.drawPath(pathLine, paint)
   }

绘制连线,为了更加美观
添加了连线的三层三角形

//绘制连接线
if (isShowConnect) {
	pathLine.reset()
	pathLine.moveTo(centerX.toFloat(), centerY)
	pathLine.lineTo(centerX.toFloat(), centerY - r)
	pathLine.moveTo(centerX.toFloat(), centerY)
	pathLine.lineTo(centerX + r * sin(angle / 2), centerY + r * cos(angle / 2))
	pathLine.moveTo(centerX.toFloat(), centerY)
	pathLine.lineTo(centerX - r * sin(angle / 2), centerY + r * cos(angle / 2))
	pathLine.close()
	canvas.drawPath(pathLine, paint)
}

绘制属性文本
添加了属性文本的三层三角形


//绘制文本
paint.color = textColor
paint.textSize = textSize
paint.style = Paint.Style.FILL
canvas.drawText(title[0], centerX - textSize, centerY - r - defaultTextPadding, paint)
canvas.drawText(title[1], centerX + ww / 2 - textSize, centerY + r * cos(angle / 2) + textSize + defaultTextPadding, paint)
canvas.drawText(title[2], centerX - ww / 2 - textSize, centerY + r * cos(angle / 2) + textSize + defaultTextPadding, paint)

绘制比例覆盖
带属性分布覆盖的三层三角形

//绘制覆盖分布区域图
pathLine.reset()
pathLine.moveTo(centerX.toFloat(), centerY - r * scaleList[0] / floorCount)
pathLine.lineTo(centerX + r * scaleList[1] / floorCount * sin(angle / 2), centerY + r * scaleList[1] / floorCount * cos(angle / 2))        pathLine.lineTo(centerX - r * scaleList[2] / floorCount * sin(angle / 2), centerY + r * scaleList[2] / floorCount * cos(angle / 2))
pathLine.close()
paint.color = areaColor
canvas.drawPath(pathLine, paint)

到这呢整个三角分布图就完成了,下面贴一下各个正多边形的效果图

正四边形正四边形
正五边形
正五边形
正六边形
正六边形
正七边形
正七边形
正八边形
正八边形

结尾

代码地址:https://github.com/big-guogai/PolygonDistributionView

文章参考:https://www.jianshu.com/p/e9d8420b1b9c

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值