stay Custom View and Radar chart Based on the two blogs, I have made some modifications. Here I summarize my own learning experience.
There are several ways to customize the View
type | definition |
---|---|
Custom composite control | Multiple controls are combined into a new control to facilitate multiple reuse |
Inherit system View control | It inherits from TextView and other system controls and extends the basic functions of system controls |
Inherit View | Do not reuse system control logic, inherit View for function definition |
Inherit system ViewGroup | It inherits from system controls such as LinearLayout and extends the basic functions of system controls |
Inherit ViewGroup | Do not reuse system control logic, inherit ViewGroup for function definition |
This paper implements radar map by inheriting from View.
Layout file
Add the radar chart control in the layout file to display it. The control program is in the LeiDaMap class, and the layout is directly added to the XML file in the layout folder:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <com.example.demo.radarMap.LeiDaMap android:id="@+id/leiDaMap" android:layout_width="300dp" android:layout_height="280dp" android:layout_gravity="center_horizontal" /> </LinearLayout>
Control file
The complete code is as follows. The program flow is described in detail below.
package com.example.demo.radarMap import android.content.Context import android.graphics.Canvas import android.graphics.Color import android.graphics.Paint import android.graphics.Path import android.util.AttributeSet import android.view.View import kotlin.math.cos import kotlin.math.min import kotlin.math.sin /** * @description: Radar chart */ class LeiDaMap(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : View(context, attrs, defStyleAttr) { /** * Number of polygon points */ private val count = 6 /** * Number of radar layers */ private val num = 4 /** * Polygons are equally divided into angles, expressed in radians */ private val angle = (Math.PI * 2 / count).toFloat() /** * Maximum radius of mesh */ private var radius = 0f /** * Center x */ private var centerX = 0 /** * Center y */ private var centerY = 0 /** * Data Max */ private var maxValue = 100f /** * Score of each dimension */ private var data = doubleArrayOf(50.0, 60.0, 70.0, 80.0, 90.0, 100.0) private var titles = arrayOf("one by one", "two two", "three three", "four four", "five five", "six six") /** * Radar area brush */ private var mMainPaint: Paint? = null /** * Text brush */ private var mTextPaint: Paint? = null /** * Data area brush */ private var mValuePaint: Paint? = null constructor(context: Context?) : this(context, null) constructor(context: Context?, attrs: AttributeSet?) : this(context, attrs, 0) private fun initPaint() { mMainPaint = Paint() mMainPaint?.isAntiAlias = true mMainPaint?.strokeWidth = 3F mMainPaint?.style = Paint.Style.STROKE mMainPaint?.color = Color.BLACK mTextPaint = Paint() mTextPaint?.isAntiAlias = true mTextPaint?.color = Color.BLUE mTextPaint?.textSize = 60F mValuePaint = Paint() mValuePaint?.isAntiAlias = true mValuePaint?.color = Color.RED mValuePaint?.style = Paint.Style.FILL_AND_STROKE } override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { super.onSizeChanged(w, h, oldw, oldh) //Maximum radius of mesh radius = min(h, w).toFloat() / 2 * 0.7f centerX = w / 2 centerY = h / 2 postInvalidate() } override fun onDraw(canvas: Canvas) { super.onDraw(canvas) //Draw regular polygon drawPolygon(canvas) //Draw a line from center to end drawLines(canvas) //Draw text drawText(canvas) //Draw area drawRegion(canvas) } /** * Draw regular polygon */ private fun drawPolygon(canvas: Canvas) { val path = Path() //Spacing between spider silk val r = radius / num for (i in 1..num) { //Current radius val curR = r * i path.reset() //Number of polygon points for (j in 0 until count) { if (j == 0) { path.moveTo(centerX.toFloat(), centerY + curR) } else { //According to the radius, calculate the coordinates of each point on the spider silk val x = (centerX + curR * sin((angle * j).toDouble())).toFloat() val y = (centerY + curR * cos((angle * j).toDouble())).toFloat() path.lineTo(x, y) } } //Closed path path.close() mMainPaint?.let { canvas.drawPath(path, it) } } } /** * Draw a line from center to end */ private fun drawLines(canvas: Canvas) { val path = Path() for (i in 0 until count) { path.reset() path.moveTo(centerX.toFloat(), centerY.toFloat()) //Calculate the coordinates of each point on the outermost spider silk val x = (centerX + radius * sin((angle * i).toDouble())).toFloat() val y = (centerY + radius * cos((angle * i).toDouble())).toFloat() path.lineTo(x, y) mMainPaint?.let { canvas.drawPath(path, it) } } } /** * Draw text * First calculate the length of the text, and then offset the starting drawing coordinate to the left by this length. */ private fun drawText(canvas: Canvas) { val fontMetrics: Paint.FontMetrics = mTextPaint!!.fontMetrics val fontHeight: Float = fontMetrics.descent - fontMetrics.ascent for (i in 0 until count) { //Calculate the coordinates of each point on the outermost spider silk val x = (centerX + (radius + fontHeight / 2) * sin((angle * i).toDouble())).toFloat() val y = (centerY + (radius + fontHeight / 2) * cos((angle * i).toDouble())).toFloat() // Text length to move text based on text length val dis: Float = mTextPaint!!.measureText(titles[i]) //First quadrant, second quadrant if (i == 1 || i == 2) { canvas.drawText(titles[i], x, y, mTextPaint!!) } // Three quadrant, four quadrant else if (i == 4 || i == 5) { canvas.drawText(titles[i], x - dis, y, mTextPaint!!) } // Point on axis else if (i == 0) { canvas.drawText(titles[i], x - dis / 2, y + dis / 3, mTextPaint!!) } else if (i == 3) { canvas.drawText(titles[i], x - dis / 2, y, mTextPaint!!) } } } /** * Draw area */ private fun drawRegion(canvas: Canvas) { val path = Path() mValuePaint?.alpha = 255 for (i in 0 until count) { val percent = data[i] / maxValue //Calculate the coordinates of each point on the outermost spider silk val x = (centerX + radius * sin((angle * i).toDouble()) * percent).toFloat() val y = (centerY + radius * cos((angle * i).toDouble()) * percent).toFloat() if (i == 0) { path.moveTo(centerX.toFloat(), y) } else { path.lineTo(x, y) } //Draw small dots mValuePaint?.let { canvas.drawCircle(x, y, 20F, it) } } mValuePaint?.alpha = 127 //Draw filled area mValuePaint?.let { canvas.drawPath(path, it) } } /** * @param titles */ fun setTitles(titles: Array<String>) { this.titles = titles } /** * Score of each dimension * @param data data */ fun setData(data: DoubleArray) { this.data = data } /** * Data Max * @param maxValue maxValue */ fun setMaxValue(maxValue: Float) { this.maxValue = maxValue } /** * Set spider web color * @param color */ fun setMainPaintColor(color: Int) { mMainPaint?.color = color } /** * Set title color * @param color */ fun setTextPaintColor(color: Int) { mTextPaint?.color = color } /** * @param color */ fun setValuePaintColor(color: Int) { mValuePaint?.color = color } init { initPaint() } }
After operation:
The previous steps are to initialize variables. The drawing process is as follows:
1. Find the center of the layout
2. Draw polygon
3. Draw a line from the center to the corner
4. Draw text (may involve text offset)
5. Draw area
1. Find the center of the layout
Rewrite the onSizeChanged function to find the center point and the maximum radius of the radar map. To make room for the text, you need to multiply it by a factor.
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { super.onSizeChanged(w, h, oldw, oldh) //Maximum radius of mesh radius = min(h, w).toFloat() / 2 * 0.7f centerX = w / 2 centerY = h / 2 postInvalidate() }
2. Draw polygon
private fun drawPolygon(canvas: Canvas) { val path = Path() //Spacing between spider silk val r = radius / num for (i in 1..num) { //Current radius val curR = r * i path.reset() //Number of polygon points for (j in 0 until count) { if (j == 0) { path.moveTo(centerX.toFloat(), centerY + curR) } else { //According to the radius, calculate the coordinates of each point on the spider silk val x = (centerX + curR * sin((angle * j).toDouble())).toFloat() val y = (centerY + curR * cos((angle * j).toDouble())).toFloat() path.lineTo(x, y) } } //Closed path path.close() mMainPaint?.let { canvas.drawPath(path, it) } } }
3. Draw a line from the center point to the corner
private fun drawLines(canvas: Canvas) { val path = Path() for (i in 0 until count) { path.reset() path.moveTo(centerX.toFloat(), centerY.toFloat()) //Calculate the coordinates of each point on the outermost spider silk val x = (centerX + radius * sin((angle * i).toDouble())).toFloat() val y = (centerY + radius * cos((angle * i).toDouble())).toFloat() path.lineTo(x, y) mMainPaint?.let { canvas.drawPath(path, it) } } }
4. Draw text
You can judge according to the value of i or quadrant in the if selection statement as needed.
private fun drawText(canvas: Canvas) { val fontMetrics: Paint.FontMetrics = mTextPaint!!.fontMetrics val fontHeight: Float = fontMetrics.descent - fontMetrics.ascent for (i in 0 until count) { //Calculate the coordinates of each point on the outermost spider silk val x = (centerX + (radius + fontHeight / 2) * sin((angle * i).toDouble())).toFloat() val y = (centerY + (radius + fontHeight / 2) * cos((angle * i).toDouble())).toFloat() // Text length to move text based on text length val dis: Float = mTextPaint!!.measureText(titles[i]) //First quadrant, second quadrant if (i == 1 || i == 2) { canvas.drawText(titles[i], x, y, mTextPaint!!) } // Three quadrant, four quadrant else if (i == 4 || i == 5) { canvas.drawText(titles[i], x - dis, y, mTextPaint!!) } // Coordinate axis else if (i == 0) { canvas.drawText(titles[i], x - dis / 2, y + dis / 3, mTextPaint!!) } else if (i == 3) { canvas.drawText(titles[i], x - dis / 2, y, mTextPaint!!) } } }
5. Draw area
private fun drawRegion(canvas: Canvas) { val path = Path() mValuePaint?.alpha = 255 for (i in 0 until count) { val percent = data[i] / maxValue //Calculate the coordinates of each point on the outermost spider silk val x = (centerX + radius * sin((angle * i).toDouble()) * percent).toFloat() val y = (centerY + radius * cos((angle * i).toDouble()) * percent).toFloat() if (i == 0) { path.moveTo(centerX.toFloat(), y) } else { path.lineTo(x, y) } //Draw small dots mValuePaint?.let { canvas.drawCircle(x, y, 20F, it) } } mValuePaint?.alpha = 127 //Draw filled area mValuePaint?.let { canvas.drawPath(path, it) } }