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.RadarViewandroid: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.RadarViewandroid: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.radarviewimport 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 = 6private var angle = Math.PI / count * 2private 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 = 0fprivate var endY = 0fconstructor(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 = mMainFrameColormainFramePaint!!.isAntiAlias = truemainFramePaint!!.strokeWidth = 4fmainFramePaint!!.style = Paint.Style.STROKE//绘制正多边形区域的画笔mainRegionPaint = Paint()mainRegionPaint!!.style = Paint.Style.FILLmainRegionPaint!!.color = mMainRegionColor//文本的画笔textPaint = Paint()textPaint!!.color = mTextColortextPaint!!.isAntiAlias = truetextPaint!!.textSize = fontSizetextPaint!!.style = Paint.Style.FILL//数据区域1的画笔regionPaint1 = Paint()regionPaint1!!.style = Paint.Style.FILL_AND_STROKEregionPaint1!!.color = mRegion1ColorregionPaint1!!.isAntiAlias = true//数据区域1实际边框的画笔actualFramePaint1 = Paint()actualFramePaint1!!.color = mActualframe1ColoractualFramePaint1!!.isAntiAlias = trueactualFramePaint1!!.strokeWidth = 0factualFramePaint1!!.style = Paint.Style.STROKE//数据区域2的画笔regionPaint2 = Paint()regionPaint2!!.style = Paint.Style.FILL_AND_STROKEregionPaint2!!.color = mRegion2ColorregionPaint2!!.isAntiAlias = true//数据区域2实际边框的画笔actualFramePaint2 = Paint()actualFramePaint2!!.color = mActualFrame2ColoractualFramePaint2!!.isAntiAlias = trueactualFramePaint2!!.strokeWidth = 0factualFramePaint2!!.style = Paint.Style.STROKE//数据区域3的画笔regionPaint3 = Paint()regionPaint3!!.style = Paint.Style.FILL_AND_STROKEregionPaint3!!.color = mRegion3ColorregionPaint3!!.isAntiAlias = true//数据区域3实际边框的画笔actualFramePaint3 = Paint()actualFramePaint3!!.color = mActualFrame3ColoractualFramePaint3!!.isAntiAlias = trueactualFramePaint3!!.strokeWidth = 0factualFramePaint3!!.style = Paint.Style.STROKE}override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {centerX = w / 2centerY = h / 2radius = 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 - radius * cos(angle * i)).toFloat()path.lineTo(endX, endY)}path.close()canvas.drawPath(path, mainRegionPaint!!)}/*** 绘制正多边形边框*/private fun drawPolygon(canvas: Canvas) {val path = Path()val r = radius / 3 //r是蜘蛛丝之间的间距for (i in 1 until 4) { //中心点不用绘制val curR = r * i //当前半径path.reset()path.moveTo(centerX.toFloat(), centerY - curR)for (j in 0 until count) { //根据半径,计算出蜘蛛丝上每个点的坐标val x = (centerX + curR * sin(angle * j)).toFloat()val y = (centerY - curR * cos(angle * j)).toFloat()path.lineTo(x, y)}path.close() //闭合路径canvas.drawPath(path, mainFramePaint!!)}}/*** 绘制三个圆圈*/private fun drawmCircle(canvas: Canvas) {canvas.drawCircle(centerX.toFloat(), centerY.toFloat(), radius / 1.toFloat(), mainFramePaint!!)canvas.drawCircle(centerX.toFloat(), centerY.toFloat(), radius / 1.5.toFloat(), mainFramePaint!!)canvas.drawCircle(centerX.toFloat(), centerY.toFloat(), radius / 3.toFloat(), mainFramePaint!!)}/*** 绘制直线* 绘制从中心到末端的直线* 同样根据半径,计算出每个末端坐标*/private fun drawLines(canvas: Canvas) {val path = Path()for (i in 0 until count) {path.reset()//设置每一次的起点为中心path.moveTo(centerX.toFloat(), centerY.toFloat())val x = (centerX + radius * sin(angle * i)).toFloat()val y = (centerY - radius * cos(angle * i)).toFloat()//画线path.lineTo(x, y)canvas.drawPath(path, mainFramePaint!!)}}/*** 绘制文字** @param canvas 对于文本的绘制,首先要找到末端的坐标,* 由于末端和文本有一定距离,给每个末端加上这个距离以后,再绘制文本。* 另外,当文本在左边时,由于不希望文本和蜘蛛网交叉,* 我们可以先计算出文本的长度,然后使起始绘制坐标向左偏移这个长度。*/private fun drawText(canvas: Canvas) {val fontMetrics = textPaint!!.fontMetricsval fontHeight = fontMetrics.descent - fontMetrics.ascentfor (i in 0 until count) {val x = (centerX + (radius + fontHeight / 2) * sin(angle * i)).toFloat()val y = (centerY - (radius + fontHeight / 2) * cos(angle * i)).toFloat()val dis = textPaint!!.measureText(titles[i]) //文本长度val title = titles[i]when (i) {0 -> {canvas.drawText(title, x - dis / 2, y, textPaint!!)}1 -> {canvas.drawText(title, x, y + 20, textPaint!!)}2 -> {if (count == 3){if (fontSize < 20){canvas.drawText(title, x - 30, y + 20, textPaint!!)}else{canvas.drawText(title, x - 50, y + 30, textPaint!!)}}else{canvas.drawText(title, x, y, textPaint!!)}}3 -> {canvas.drawText(title, x - dis / 2, y + 20, textPaint!!)}4 -> {canvas.drawText(title, x - dis, y, textPaint!!)}else -> {canvas.drawText(title, x - dis, y + 20, textPaint!!)}}}}/*** 绘制数据区域1** @param canvas 使path包围区域被填充*/private fun drawRegion1(canvas: Canvas) {val path = Path()if (mRegion1GradientEnable){gradient = LinearGradient(0f, 0f, 0f, radius * 2, mRegion1StartColor, mRegion1EndColor, Shader.TileMode.CLAMP)regionPaint1!!.shader = gradientregionPaint1!!.alpha = 127}else{regionPaint1!!.alpha = 255}path.reset()for (i in 0 until count) {val percent = data1[i] / maxValueval x = (centerX + radius * sin(angle * i) * percent).toFloat()val y = (centerY - radius * cos(angle * i) * percent).toFloat()if (i == 0){path.moveTo(centerX.toFloat(), y)}path.lineTo(x, y)}regionPaint1!!.alpha = 127//绘制填充区域regionPaint1!!.style = Paint.Style.FILL_AND_STROKEcanvas.drawPath(path, regionPaint1!!)}/*** 绘制区域1实际边框*/private fun drawFrame1(canvas: Canvas) {val path = Path()actualFramePaint1!!.alpha = 255path.reset()for (i in 0 until count) {val percent = data1[i] / maxValueval x = (centerX + radius * sin(angle * i) * percent).toFloat()val y = (centerY - radius * cos(angle * i) * percent).toFloat()if (i == 0){path.moveTo(centerX.toFloat(), y)}path.lineTo(x, y)//绘制小圆点canvas.drawCircle(x, y, 1f, regionPaint1!!)}actualFramePaint1!!.style = Paint.Style.STROKEpath.close() //闭合路径canvas.drawPath(path, actualFramePaint1!!)}/*** 绘制数据区域2** @param canvas 使path包围区域被填充*/private fun drawRegion2(canvas: Canvas) {val path = Path()regionPaint2!!.alpha = 255path.reset()for (i in 0 until count) {val percent = data2[i] / maxValueval x = (centerX + radius * sin(angle * i) * percent).toFloat()val y = (centerY - radius * cos(angle * i) * percent).toFloat()if (i == 0){path.moveTo(centerX.toFloat(), y)}path.lineTo(x, y)}regionPaint2!!.alpha = 127//绘制填充区域regionPaint2!!.style = Paint.Style.FILL_AND_STROKEcanvas.drawPath(path, regionPaint2!!)}/*** 绘制数据区域3** @param canvas 使path包围区域被填充*/private fun drawRegion3(canvas: Canvas) {val path = Path()regionPaint3!!.alpha = 255path.reset()for (i in 0 until count) {val percent = data3[i] / maxValueval x = (centerX + radius * sin(angle * i) * percent).toFloat()val y = (centerY - radius * cos(angle * i) * percent).toFloat()if (i == 0){path.moveTo(centerX.toFloat(), y)}path.lineTo(x, y)}regionPaint3!!.alpha = 127//绘制填充区域regionPaint3!!.style = Paint.Style.FILL_AND_STROKEcanvas.drawPath(path, regionPaint3!!)}/*** 绘制区域2的实际边框*/private fun drawFrame2(canvas: Canvas) {val path = Path()actualFramePaint2!!.alpha = 255path.reset()for (i in 0 until count) {val percent = data2[i] / maxValueval x = (centerX + radius * sin(angle * i) * percent).toFloat()val y = (centerY - radius * cos(angle * i) * percent).toFloat()if (i == 0){path.moveTo(centerX.toFloat(), y)}path.lineTo(x, y)//绘制小圆点canvas.drawCircle(x, y, 1f, regionPaint1!!)}actualFramePaint2!!.style = Paint.Style.STROKEpath.close() //闭合路径canvas.drawPath(path, actualFramePaint2!!)}/*** 绘制区域3的实际边框*/private fun drawFrame3(canvas: Canvas) {val path = Path()actualFramePaint3!!.alpha = 255path.reset()for (i in 0 until count) {val percent = data3[i] / maxValueval x = (centerX + radius * sin(angle * i) * percent).toFloat()val y = (centerY - radius * cos(angle * i) * percent).toFloat()if (i == 0){path.moveTo(centerX.toFloat(), y)}path.lineTo(x, y)//绘制小圆点canvas.drawCircle(x, y, 1f, regionPaint1!!)}actualFramePaint3!!.style = Paint.Style.STROKEpath.close() //闭合路径canvas.drawPath(path, actualFramePaint3!!)}companion object {/*** dp转换为px*/fun dpToPx(dp: Float): Int {return (dp * Resources.getSystem().displayMetrics.density + 0.5f).toInt()}}fun setData(titles: Array<String>, data1: DoubleArray) {this.titles = titlesthis.data1 = data1count = min(titles.size, data1.size)angle = Math.PI / count * 2invalidate()}fun setData(titles: Array<String>, data1: DoubleArray, data2: DoubleArray) {this.titles = titlesthis.data1 = data1this.data2 = data2count = min(titles.size, data1.size)angle = Math.PI / count * 2invalidate()}fun setData(titles: Array<String>, data1: DoubleArray, data2: DoubleArray, data3: DoubleArray) {this.titles = titlesthis.data1 = data1this.data2 = data2this.data3 = data3count = titles.sizeangle = Math.PI / count * 2invalidate()}/*** 是否要绘制数据区域1*/fun setRegion1Enable(mEnable: Boolean){mRegion1Enable = mEnable}/*** 是否要绘制数据区域2*/fun setRegion2Enable(mEnable: Boolean){mRegion2Enable = mEnable}/*** 是否要绘制数据区域3*/fun setRegion3Enable(mEnable: Boolean){mRegion3Enable = mEnable}/*** 是否要绘制数据区域1实际边框*/fun setActualframe1Enable(mEnable: Boolean){mActualframe1Enable = mEnable}/*** 是否要绘制数据区域2实际边框*/fun setActualframe2Enable(mEnable: Boolean){mActualframe2Enable = mEnable}/*** 是否要绘制数据区域3实际边框*/fun setActualframe3Enable(mEnable: Boolean){mActualframe3Enable = mEnable}
}

我们重写了3个构造方法,在上面的构造方法中说过默认的布局文件调用的是两个参数的构造方法,所以记得让所有的构造方法调用三个参数的构造方法,然后在三个参数的构造方法中获得自定义属性。
一开始一个参数的构造方法和两个参数的构造方法是这样的:

constructor(mContext: Context) : super (mContext)
constructor(mContext: Context, attrs: AttributeSet?) : super(mContext, attrs)

有一点要注意的是super应该改成this,然后让一个参数的构造方法引用两个参数的构造方法,两个参数的构造方法引用三个参数的构造方法,代码如下:

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

View的绘制流程是从ViewRoot的performTravarsals方法开始的,经过measure、layout和draw三个过程才能最终将一个View绘制出来,其中:

测量——onMeasure():用来测量View的宽和高来决定View的大小
布局——onLayout():用来确定View在父容器ViewGroup中的放置位置
绘制——onDraw():负责将View绘制在屏幕上

4、在MainActivity中赋值

package co.per.radarviewimport androidx.appcompat.app.AppCompatActivity
import android.os.Bundleclass MainActivity : AppCompatActivity() {private lateinit var viewRadar1: RadarViewprivate lateinit var viewRadar2: RadarViewoverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)viewRadar1 = findViewById(R.id.view_radar1)viewRadar2 = findViewById(R.id.view_radar2)initData()}private fun initData() {var titles = arrayOf("支撑", "回弹", "缓震")var data1: DoubleArray = doubleArrayOf(100.0, 65.0, 85.0)var data2: DoubleArray = doubleArrayOf(60.0, 80.0, 95.0)var userData: DoubleArray = doubleArrayOf(80.0, 100.0, 65.0)viewRadar1.setData(titles, data1, data2, userData)titles = arrayOf("轻量", "美观", "抓地", "耐磨", "缓震", "透气")data1 = doubleArrayOf(100.0, 65.0, 85.0,100.0, 65.0, 85.0)data2 = doubleArrayOf(60.0, 80.0, 95.0,60.0, 80.0, 95.0)userData = doubleArrayOf(80.0, 100.0, 65.0,60.0, 80.0, 95.0)viewRadar2.setData(titles, data1, data2, userData)}
}

源码下载


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部