Android 使用Kotlin来实现自定义View之雷达图

Posted Angelica0520

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 使用Kotlin来实现自定义View之雷达图相关的知识,希望对你有一定的参考价值。

本篇文章讲的是Kotlin 自定义view之实现雷达图。
按照惯例,我们先来看看效果图

一、先总结下自定义View的步骤:
1、自定义View的属性
2、在View的构造方法中获得我们自定义的属性
3、重写onMesure
4、重写onDraw
其中onMesure方法不一定要重写,但大部分情况下还是需要重写的

二、View 的几个构造函数:
1、constructor(mContext: Context)
—>java代码直接new一个RulerView实例的时候,会调用这个只有一个参数的构造函数;
2、constructor(mContext: Context, attrs: AttributeSet)
—>在默认的XML布局文件中创建的时候调用这个有两个参数的构造函数。AttributeSet类型的参数负责把XML布局文件中所自定义的属性通过AttributeSet带入到View内;
3、constructor(mContext: Context, attrs: AttributeSet,defStyleAttr:Int)
—>构造函数中第三个参数是默认的Style,这里的默认的Style是指它在当前Application或者Activity所用的Theme中的默认Style,且只有在明确调用的时候才会调用
4、constructor(mContext: Context, attrs: AttributeSet,defStyleAttr:Int,defStyleRes:Int)
—>该构造函数是在API21的时候才添加上的

三、下面我们就开始来看看代码啦:
1、自定义View的属性,首先在res/values/ 下建立一个attrs.xml , 在里面定义我们的需要用到的属性以及声明相对应属性的取值类型

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <declare-styleable name="RadarView">
        <!-- 绘制文本的颜色 -->
        <attr name="color_text" format="color" />
        <!-- 绘制正多边形区域的颜色 -->
        <attr name="color_main_region" format="color" />
        <!-- 绘制正多边形边框或者圆圈的颜色 -->
        <attr name="color_main_frame" format="color" />
        <!-- 绘制数据区域1的颜色 -->
        <attr name="color_region1" format="color" />
        <!-- 绘制数据区域1实际边框的颜色 -->
        <attr name="color_actual_frame1" format="color" />
        <!-- 绘制数据区域2的颜色 -->
        <attr name="color_region2" format="color" />
        <!-- 绘制数据区域2实际边框的颜色 -->
        <attr name="color_actual_frame2" format="color" />
        <!-- 绘制数据区域3的颜色 -->
        <attr name="color_region3" format="color" />
        <!-- 绘制数据区域3实际边框的颜色 -->
        <attr name="color_actual_frame3" format="color" />
        <!-- 绘制数据区域1要不要使用渐变色 -->
        <attr name="region1_gradient_enable" format="boolean" />
        <!-- 绘制数据区域1渐变的开始颜色 -->
        <attr name="color_region1_start" format="color" />
        <!-- 绘制数据区域1渐变的结束颜色 -->
        <attr name="color_region1_end" format="color" />
        <!-- 要不要绘制正多边形区域 -->
        <attr name="main_region_enable" format="boolean" />
        <!-- 要不要绘制数据区域1 -->
        <attr name="region1_enable" format="boolean" />
        <!-- 要不要绘制数据区域1实际边框 -->
        <attr name="ractual_frame1_enable" format="boolean" />
        <!-- 要不要绘制数据区域2 -->
        <attr name="region2_enable" format="boolean" />
        <!-- 要不要绘制数据区域2实际边框 -->
        <attr name="ractual_frame2_enable" format="boolean" />
        <!-- 要不要绘制数据区域3 -->
        <attr name="region3_enable" format="boolean" />
        <!-- 要不要绘制数据区域3实际边框 -->
        <attr name="ractual_frame3_enable" format="boolean" />
        <!--文字的大小-->
        <attr name="textSize" format="dimension" />
    </declare-styleable>
</resources>

2、在布局文件中引用,一定要引入xmlns:app=”http://schemas.android.com/apk/res-auto”,Android Studio中我们可以使用res-atuo命名空间,就不用在添加自定义View全类名。

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#18191D">

    <co.per.radarview.RadarView
        android:id="@+id/view_radar1"
        android:layout_width="210dp"
        android:layout_height="210dp"
        app:color_actual_frame1="@color/Actualframe1"
        app:color_actual_frame2="@color/ActualFrame2"
        app:color_actual_frame3="@color/ActualFrame3"
        app:color_main_frame="@color/MainFrame"
        app:color_main_region="@color/MainRegion"
        app:color_region1="@color/Region1"
        app:color_region2="@color/Region2"
        app:color_region3="@color/Region3"
        app:color_text="@color/Text"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:ractual_frame3_enable="true"
        app:region3_enable="true" />

    <co.per.radarview.RadarView
        android:id="@+id/view_radar2"
        android:layout_width="210dp"
        android:layout_height="210dp"
        app:color_actual_frame1="@color/Actualframe1"
        app:color_actual_frame2="@color/ActualFrame2"
        app:color_actual_frame3="@color/ActualFrame3"
        app:color_main_frame="@color/MainFrame"
        app:color_main_region="@color/MainRegion"
        app:color_region1="@color/Region1"
        app:color_region2="@color/Region2"
        app:color_region3="@color/Region3"
        app:color_text="@color/Text"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/view_radar1"
        app:ractual_frame3_enable="true"
        app:region3_enable="true" />

</androidx.constraintlayout.widget.ConstraintLayout>

3、在View的构造方法中,获得我们的自定义的样式,并实现雷达图

package co.per.radarview

import android.content.Context
import android.content.res.Resources
import android.graphics.*
import android.util.AttributeSet
import android.view.View
import kotlin.math.cos
import kotlin.math.min
import kotlin.math.sin
/**
 * 雷达图
 * 六维图
 * 三维图
 * Created by juan on 2021/07/20.
 */
class RadarView : View 
    /**
     * 字体颜色
     */
    private var mTextColor = 0
    /**
     * 绘制正多边形区域的颜色
     */
    private var mMainRegionColor = 0
    /**
     * 绘制正多边形边框或者圆圈的颜色
     */
    private var mMainFrameColor = 0
    /**
     * 绘制数据区域1的颜色
     */
    private var mRegion1Color = 0
    /**
     * 绘制数据区域1实际边框的颜色
     */
    private var mActualframe1Color = 0
    /**
     * 绘制数据区域1要不要使用渐变色
     */
    private var mRegion1GradientEnable = false
    /**
     * 绘制数据区域1渐变的开始颜色
     */
    private var mRegion1StartColor = 0
    /**
     * 绘制数据区域1渐变的结束颜色
     */
    private var mRegion1EndColor = 0
    /**
     * 要不要绘制数据区域1
     */
    private var mRegion1Enable = true
    /**
     * 要不要绘制数据区域1实际边框
     */
    private var mActualframe1Enable = true
    /**
     * 绘制数据区域2的颜色
     */
    private var mRegion2Color = 0
    /**
     * 绘制数据区域2实际边框的颜色
     */
    private var mActualFrame2Color = 0
    /**
     * 要不要绘制数据区域2
     */
    private var mRegion2Enable = true
    /**
     * 要不要绘制数据区域2实际边框
     */
    private var mActualframe2Enable = true
    /**
     * 绘制数据区域3的颜色
     */
    private var mRegion3Color = 0
    /**
     * 绘制数据区域3实际边框的颜色
     */
    private var mActualFrame3Color = 0
    /**
     * 要不要绘制数据区域3
     */
    private var mRegion3Enable = false
    /**
     * 要不要绘制数据区域3实际边框
     */
    private var mActualframe3Enable = false
    /**
     * 要不要绘制正多边形区域
     */
    private var mMainRegionEnable = false
    /**
     * 用于创建线性渐变效果
     */
    private var gradient: LinearGradient? = null
    /**
     * 中心X
     */
    private var centerX = 0
    /**
     * 中心Y
     */
    private var centerY = 0
    /**
     * 网格最大半径
     */
    private var radius = 0f
    /**
     * 绘制正多边形边框或者圆圈的画笔
     */
    private var mainFramePaint: Paint? = null
    /**
     * 绘制正多边形区域的画笔
     */
    private var mainRegionPaint: Paint? = null
    /**
     * 文本的画笔
     */
    private var textPaint: Paint? = null
    /**
     * 数据区域1的画笔
     */
    private var regionPaint1: Paint? = null
    /**
     * 数据区域1实际边框的画笔
     */
    private var actualFramePaint1: Paint? = null
    /**
     * 数据区域2的画笔
     */
    private var regionPaint2: Paint? = null
    /**
     * 数据区域2实际边框的画笔
     */
    private var actualFramePaint2: Paint? = null
    /**
     * 数据区域3的画笔
     */
    private var regionPaint3: Paint? = null
    /**
     * 数据区域3实际边框的画笔
     */
    private var actualFramePaint3: Paint? = null
    /**
     * 文字大小
     */
    private var fontSize = 32f
    /**
     * 绘制几边形
     */
    private var count = 6
    private var angle = Math.PI / count * 2
    private var titles = arrayOf("轻量", "美观", "抓地", "耐磨", "缓震", "透气")
    private var data1 = doubleArrayOf(100.0, 65.0, 45.0, 70.0, 80.0, 100.0, 90.0, 80.0) //各维度分值
    private var data2 = doubleArrayOf(80.0, 90.0, 95.0, 80.0, 95.0, 40.0, 90.0, 100.0) //各维度分值
    private var data3 = doubleArrayOf(90.0, 80.0, 65.0, 100.0, 65.0, 70.0, 50.0, 60.0) //各维度分值
    private val maxValue = 100f //数据最大值
    //多边形每一条先的起始坐标和终点坐标
    private var endX = 0f
    private var endY = 0f

    constructor(context: Context?) : this(context, null)
    constructor(context: Context?, attrs: AttributeSet?) : this(context, attrs, 0)
    constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) 
        initView(attrs)
        initPaint()
    

    /**
     * 获取自定义属性
     */
    private fun initView(attrs: AttributeSet?) 
        val t = context.obtainStyledAttributes(attrs, R.styleable.RadarView)
        this.mTextColor = t.getColor(R.styleable.RadarView_color_text,mTextColor)
        this.mMainRegionColor = t.getColor(R.styleable.RadarView_color_main_region,mMainRegionColor)
        this.mMainFrameColor = t.getColor(R.styleable.RadarView_color_main_frame,mMainFrameColor)
        this.mRegion1Color = t.getColor(R.styleable.RadarView_color_region1,mRegion1Color)
        this.mActualframe1Color = t.getColor(R.styleable.RadarView_color_actual_frame1,mActualframe1Color)
        this.mRegion2Color = t.getColor(R.styleable.RadarView_color_region2,mRegion2Color)
        this.mActualFrame2Color = t.getColor(R.styleable.RadarView_color_actual_frame2,mActualFrame2Color)
        this.mRegion3Color = t.getColor(R.styleable.RadarView_color_region3,mRegion3Color)
        this.mActualFrame3Color = t.getColor(R.styleable.RadarView_color_actual_frame3,mActualFrame3Color)
        this.mRegion1GradientEnable = t.getBoolean(R.styleable.RadarView_region1_gradient_enable,false)
        this.mRegion1StartColor = t.getColor(R.styleable.RadarView_color_region1_start,mRegion1StartColor)
        this.mRegion1EndColor = t.getColor(R.styleable.RadarView_color_region1_end,mRegion1EndColor)
        this.mMainRegionEnable = t.getBoolean(R.styleable.RadarView_main_region_enable,false)
        this.mRegion1Enable = t.getBoolean(R.styleable.RadarView_region1_enable,true)
        this.mActualframe1Enable = t.getBoolean(R.styleable.RadarView_ractual_frame1_enable,true)
        this.mRegion2Enable = t.getBoolean(R.styleable.RadarView_region2_enable,true)
        this.mActualframe2Enable = t.getBoolean(R.styleable.RadarView_ractual_frame2_enable,true)
        this.mRegion3Enable = t.getBoolean(R.styleable.RadarView_region3_enable,false)
        this.mActualframe3Enable = t.getBoolean(R.styleable.RadarView_ractual_frame3_enable,false)
        this.fontSize = t.getDimension(R.styleable.RadarView_textSize, dpToPx(14f).toFloat())
    

    /**
     * 初始化画笔
     */
    private fun initPaint() 
        //绘制正多边形边框或者圆圈的画笔
        mainFramePaint = Paint()
        mainFramePaint!!.color = mMainFrameColor
        mainFramePaint!!.isAntiAlias = true
        mainFramePaint!!.strokeWidth = 4f
        mainFramePaint!!.style = Paint.Style.STROKE

        //绘制正多边形区域的画笔
        mainRegionPaint = Paint()
        mainRegionPaint!!.style = Paint.Style.FILL
        mainRegionPaint!!.color = mMainRegionColor

        //文本的画笔
        textPaint = Paint()
        textPaint!!.color = mTextColor
        textPaint!!.isAntiAlias = true
        textPaint!!.textSize = fontSize
        textPaint!!.style = Paint.Style.FILL

        //数据区域1的画笔
        regionPaint1 = Paint()
        regionPaint1!!.style = Paint.Style.FILL_AND_STROKE
        regionPaint1!!.color = mRegion1Color
        regionPaint1!!.isAntiAlias = true

        //数据区域1实际边框的画笔
        actualFramePaint1 = Paint()
        actualFramePaint1!!.color = mActualframe1Color
        actualFramePaint1!!.isAntiAlias = true
        actualFramePaint1!!.strokeWidth = 0f
        actualFramePaint1!!.style = Paint.Style.STROKE

        //数据区域2的画笔
        regionPaint2 = Paint()
        regionPaint2!!.style = Paint.Style.FILL_AND_STROKE
        regionPaint2!!.color = mRegion2Color
        regionPaint2!!.isAntiAlias = true

        //数据区域2实际边框的画笔
        actualFramePaint2 = Paint()
        actualFramePaint2!!.color = mActualFrame2Color
        actualFramePaint2!!.isAntiAlias = true
        actualFramePaint2!!.strokeWidth = 0f
        actualFramePaint2!!.style = Paint.Style.STROKE

        //数据区域3的画笔
        regionPaint3 = Paint()
        regionPaint3!!.style = Paint.Style.FILL_AND_STROKE
        regionPaint3!!.color = mRegion3Color
        regionPaint3!!.isAntiAlias = true

        //数据区域3实际边框的画笔
        actualFramePaint3 = Paint()
        actualFramePaint3!!.color = mActualFrame3Color
        actualFramePaint3!!.isAntiAlias = true
        actualFramePaint3!!.strokeWidth = 0f
        actualFramePaint3!!.style = Paint.Style.STROKE
    

    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) 
        centerX = w / 2
        centerY = h / 2
        radius = h.coerceAtMost(w) / 2 - textPaint!!.measureText(titles[0]) //半径为宽高的一半减去文字的长度
        invalidate() // 工作在ui线程
        super.onSizeChanged(w, h, oldw, oldh)
    

    override fun onDraw(canvas: Canvas) 
        super.onDraw(canvas)
        if (mMainRegionEnable)
            drawPolygon(canvas, radius)
        
        if (count < 4)
            drawmCircle(canvas)
        else
            drawPolygon(canvas)
        
        drawLines(canvas)
        drawText(canvas)
        if (mRegion3Enable)
            drawRegion3(canvas)
        
        if (mActualframe3Enable)
            drawFrame3(canvas)
        
        if (mRegion2Enable)
            drawRegion2(canvas)
        
        if (mActualframe2Enable)
            drawFrame2(canvas)
        
        if (mRegion1Enable)
            drawRegion1(canvas)
        
        if (mActualframe1Enable)
            drawFrame1(canvas)
        
    

    /**
     * 绘制正多边形区域
     */
    private fun drawPolygon(canvas: Canvas, radius: Float) 
        val path = Path()
        path.moveTo(centerX.toFloat(), centerY - radius)
        for (i in 0 until count) 
            endX = (centerX + radius * sin(angle * i)).toFloat()
            endY = (centerY Android 使用Kotlin来实现自定义View之雷达图

Android 使用Kotlin来实现自定义View之雷达图

Android 使用Kotlin来实现水波纹的自定义View

Android 使用Kotlin来实现水波纹的自定义View

Android 使用Kotlin来实现水波纹的自定义View

Android 中自定义ViewGroup实现流式布局的效果