萌萌の初音
萌萌の初音
发布于 2021-03-05 / 802 阅读
0

android实现简单的饼状统计图组件

项目开发中涉及统计功能,花时间自己做了饼状图组件,先贴实现样式以及代码,再讲如何使用。
Screenshot_202103052.jpg

import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.view.View
import com.rp.statisticalchart.R
import kotlin.math.cos
import kotlin.math.sin

/**
 * @ClassName: PieChartView
 * @Description: <p>饼状图View (请用一句话描述该类做什么)</p>
 * @author:RainPan
 * @date: 2021/3/4 10:54
 * ${tags}$
 */
class PieChartWidget : View {

    private var tangle = 0f
    private val delayMillis = 10
    private val mColor = Color.GREEN
    private var mCircleWidth = 0f
    private lateinit var mPaint:Paint

    /** 绑定的数据*/
    private val values = mutableListOf<DataBean>()

    /** 扇形图中央显示数据类型名称*/
    private var centerName = "资源数量"

    /** 扇形图中央显示单位*/
    private var centerUnit = "个"

    /** 显示类型 0显示具体数量 1显示百分比*/
    private var showType = 0
    private var total = 0f;

    constructor(context: Context?) : super(context)
    constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
    constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)

    constructor(context: Context?, values: MutableList<DataBean>) : super(context) {
        this.values.addAll(values)
    }
    constructor(context: Context?, values: MutableList<DataBean>, centerName: String, centerUnit: String, showType: Int) : super(context) {
        this.values.addAll(values)
        this.centerName = centerName
        this.centerUnit = centerUnit
        this.showType = showType
    }

    /**
     * Implement this to do your drawing.
     *
     * @param canvas the canvas on which the background will be drawn
     */
    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)
        mCircleWidth = (if (width / 2 >= height / 2) height / 2 / 5 else width / 2 / 5).toFloat()
        val center = (width / 2).toFloat()
        val radius = (if (width / 2 >= height / 2) height / 2  - height / 4 else width / 2 - mCircleWidth - height / 4).toFloat()
        var ttangle = -90f
        mPaint = Paint()
        mPaint.strokeWidth = mCircleWidth
        mPaint.isAntiAlias = true
        mPaint.style = Paint.Style.STROKE
        mPaint.color = mColor

        val oval = RectF(center - radius, height / 2 - radius, center + radius, height / 2 + radius)
        val oval_white = RectF(center - radius + mCircleWidth / 2, height / 2 - radius + mCircleWidth / 2,
                center + radius - mCircleWidth / 2, height / 2 + radius - mCircleWidth / 2)

        if (values.size > 0) {
            for (i in values) {
                total += i.num
            }
            if (total > 0) {
                val center_y = height / 2
                val heightInt = resources.displayMetrics.heightPixels

                //连接线的画笔
                val label_line = Paint()
                label_line.strokeWidth = height / 427f
                label_line.isAntiAlias = true
                label_line.style = Paint.Style.STROKE

                //连接线后面小圆的画笔
                val little_circle = Paint()
                little_circle.strokeWidth = 2f
                little_circle.isAntiAlias = true
                little_circle.style = Paint.Style.STROKE
                val radius_little_circle = height / 160f

                //连接线上文字
                val textSize = height / 40f

                val line_text = Paint()
                line_text.color = Color.rgb(225, 225, 225)
                line_text.isAntiAlias = true
                line_text.textSize = textSize
                line_text.typeface = Typeface.DEFAULT

                val radian = FloatArray(values.size)
                val d = FloatArray(values.size)
                for (i in 0 until values.size) {
                    radian[i] = (values[i].num / total * 360)
                    d[i] = if (i > 0) (radian[i-1] + radian[i]) / 2 else radian[i] / 2
                    label_line.color = resources.getColor(values[i].color.toInt())
                    little_circle.color = resources.getColor(values[i].color.toInt())

                    var dSize = 0f
                    for (num in 0..i) {
                        dSize += d[num]
                    }
                    addLine(i, dSize, center, center_y, radius, canvas, label_line, radius_little_circle, little_circle, line_text, textSize)

                    mPaint.strokeWidth = mCircleWidth
                    mPaint.color = resources.getColor(values[i].color.toInt())
                    val d = (values[i].num / total * 360)

                    //画圆弧
                    val paint_white = Paint()
                    paint_white.strokeWidth = 15f
                    paint_white.color = resources.getColor(R.color.colorWhite)
                    paint_white.isAntiAlias = true
                    paint_white.style = Paint.Style.STROKE

                    canvas?.let {
                        it.drawArc(oval, ttangle.toFloat(), d, false, mPaint)
                        it.drawArc(oval_white, 0f, 360f, false, paint_white)
                    }

                    ttangle += d
                }

                canvas?.let {
                    //画中间的圆
                    val paint_circle = Paint()
                    paint_circle.isAntiAlias = true
                    paint_circle.color = resources.getColor(R.color.color_midcircle)
                    it.drawCircle(center, center_y.toFloat(), radius - mCircleWidth / 2, paint_circle)

                    val paintText = Paint()
                    paintText.color = Color.rgb(225, 225, 225)
                    val mid_text = height / 20f
                    val mid_num = height / 22f
                    paintText.textSize = mid_text
                    paintText.typeface = Typeface.DEFAULT
                    paintText.isAntiAlias = true
                    val text_width = paintText.measureText(centerName)
                    it.drawText(centerName, center - text_width / 2, height / 2 - mid_text, paintText)

                    val textNum = Paint()
                    textNum.color = Color.rgb(225, 225, 225)
                    textNum.textSize = mid_num
                    textNum.typeface = Typeface.DEFAULT
                    textNum.isAntiAlias = true
                    var num = 0
                    for (i in values.indices) {
                        num += values[i].num.toInt()
                    }
                    val num_width = textNum.measureText("$num$centerUnit")
                    val num_width2 = textNum.measureText("$num")
                    it.drawText("$num", center - num_width / 2, height / 2 + mid_num, textNum)
                    it.drawText("$centerUnit", center - num_width / 2 + num_width2, height / 2 + mid_num, paintText)

                    if (ttangle < 270) {
                        tangle += 2f
                        postInvalidateDelayed(delayMillis.toLong())
                    }
                }
            }
        }
    }

    private fun addLine(i: Int, d: Float, center: Float, center_y: Int, radius: Float, canvas: Canvas?, label_line: Paint, radius_little_circle: Float, little_circle: Paint, line_text: Paint, textSize: Float) {
        canvas?.let{
            val num = when (showType) {
                1 -> "${(values[i].num / total * 100).toInt()}%"
                else -> values[i].num.toInt().toString()
            }
            when (d) {
                in 0f..90f -> {
                    val startX = (center + radius * sin(d * Math.PI / 180)).toFloat()
                    val startY = (center_y - radius * cos(d * Math.PI / 180)).toFloat()
                    val stopX = startX + height / 32
                    val stopY = startY - height / 32
                    it.drawLine(startX, startY, stopX, stopY, label_line)
                    it.drawLine(stopX, stopY, stopX + radius, stopY, label_line)
                    it.drawCircle(stopX + radius + radius_little_circle, stopY, radius_little_circle, little_circle)
                    it.drawText(values[i].type, stopX + radius / 4, stopY - height / 128f, line_text)
                    it.drawText("$num", stopX + radius / 3, stopY + textSize / 0.9f, line_text)
                }
                in 90f..135f -> {
                    val startX = (center + radius * sin(d * Math.PI / 180)).toFloat()
                    val startY = (center_y - radius * cos(d * Math.PI / 180)).toFloat()
                    val stopX = startX + height / 32
                    val stopY = startY + height / 32
                    it.drawLine(startX, startY, stopX, stopY, label_line)
                    it.drawLine(stopX, stopY, stopX + radius, stopY, label_line)
                    it.drawCircle(stopX + radius + radius_little_circle, stopY, radius_little_circle, little_circle)
                    it.drawText(values[i].type, stopX + radius / 4, stopY - height / 128f, line_text)
                    it.drawText("$num", stopX + radius / 3, stopY + textSize / 0.9f, line_text)
                }
                in 135f..180f -> {
                    val startX = (center + radius * sin(d * Math.PI / 180)).toFloat()
                    val startY = (center_y - radius * cos(d * Math.PI / 180)).toFloat()
                    val stopX = startX + height / 32
                    val stopY = startY + height / 32
                    it.drawLine(startX, startY, stopX, stopY, label_line)
                    it.drawLine(stopX, stopY, stopX + radius + radius / 4, stopY, label_line)
                    it.drawCircle(stopX + radius + radius_little_circle + radius / 4, stopY, radius_little_circle, little_circle)
                    it.drawText(values[i].type, stopX + radius / 2, stopY - height / 128f, line_text)
                    it.drawText("$num", stopX + radius / 2, stopY + textSize / 0.9f, line_text)
                }
                in 180f..225f -> {
                    val startX = (center + radius * sin(d * Math.PI / 180)).toFloat()
                    val startY = (center_y - radius * cos(d * Math.PI / 180)).toFloat()
                    val stopX = startX - height / 32
                    val stopY = startY + height / 32
                    it.drawLine(startX, startY, stopX, stopY, label_line)
                    it.drawLine(stopX, stopY, stopX - radius - radius / 4, stopY, label_line)
                    it.drawCircle(stopX - radius - radius_little_circle - radius / 4, stopY, radius_little_circle, little_circle)
                    it.drawText(values[i].type, stopX - radius - radius / 6, stopY - height / 128f, line_text)
                    it.drawText("$num", stopX - radius - radius / 6, stopY + textSize / 0.9f, line_text)
                }
                in 225f..270f -> {
                    val startX = (center + radius * sin(d * Math.PI / 180)).toFloat()
                    val startY = (center_y - radius * cos(d * Math.PI / 180)).toFloat()
                    val stopX = startX - height / 32
                    val stopY = startY + height / 32
                    it.drawLine(startX, startY, stopX, stopY, label_line)
                    it.drawLine(stopX, stopY, stopX - radius, stopY, label_line)
                    it.drawCircle(stopX - radius - radius_little_circle, stopY, radius_little_circle, little_circle)
                    it.drawText(values[i].type, stopX - radius + radius / 6, stopY - height / 128f, line_text)
                    it.drawText("$num", stopX - radius + radius / 3, stopY + textSize / 0.9f, line_text)
                }
                in 270f..360f -> {
                    val startX = (center + radius * sin(d * Math.PI / 180)).toFloat()
                    val startY = (center_y - radius * cos(d * Math.PI / 180)).toFloat()
                    val stopX = startX - height / 32
                    val stopY = startY - height / 32
                    it.drawLine(startX, startY, stopX, stopY, label_line)
                    it.drawLine(stopX, stopY, stopX - radius, stopY, label_line)
                    it.drawCircle(stopX - radius - radius_little_circle, stopY, radius_little_circle, little_circle)
                    it.drawText(values[i].type, stopX - radius + radius / 6, stopY - height / 128f, line_text)
                    it.drawText("$num", stopX - radius + radius / 3, stopY + textSize / 0.9f, line_text)
                }
            }
        }
    }

    data class DataBean(
        var num : Float,
        var color : String,
        var type : String
    )
}

点击跳转代码片段
以上就是具体实现饼状图方法,如何使用呢?在activity或fragment的布局文件中建立一个FrameLayout布局。

            <FrameLayout
                android:layout_gravity="center"
                android:id="@+id/pie_layout"
                android:layout_width="match_parent"
                android:layout_height="300dp"/>

在activity和fragment中添加view(组件绑定使用的ViewBinding)。

    lateinit var layout : ActivityMainBinding
    private val dataBeans = mutableListOf<PieChartWidget.DataBean>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        layout = ActivityMainBinding.inflate(layoutInflater)
        setContentView(layout.root)
        layout.pieLayout.removeAllViews()
        layout.pieLayout.addView(PieChartWidget(this,dataBeans))
    }

其中,dataBeans是要显示的数据。如下声明:

        dataBeans.clear()
        dataBeans.add(PieChartWidget.DataBean((100..1000).random().toFloat(),"${R.color.color_statistics4}","名师"))
        dataBeans.add(PieChartWidget.DataBean((100..1000).random().toFloat(),"${R.color.color_statistics1}","研究员"))
        dataBeans.add(PieChartWidget.DataBean((100..1000).random().toFloat(),"${R.color.color_statistics2}","特级教师"))
        dataBeans.add(PieChartWidget.DataBean((100..1000).random().toFloat(),"${R.color.color_statistics3}","优秀教师"))
        dataBeans.add(PieChartWidget.DataBean((100..1000).random().toFloat(),"${R.color.color_statistics5}","高级教师"))
        dataBeans.add(PieChartWidget.DataBean((100..1000).random().toFloat(),"${R.color.color_statistics6}","骨干教师"))

第一个参数为你要统计的数据大小;第二个为你的颜色配置文件,在res/values/color.xml文件里进行配置即可;最后一个参数为每个统计参数的名称。以上就可以实现饼状图的显示。

有朋友就会问中间的标题、单位和百分比想要修改怎么修改呢?

        PieChartView(this, teacherModel,"教师师资","个",1)

通过以上代码初始化就可以显示标题、单位、百分比了,如图。
Screenshot_202103053.jpg

以上就是android饼状统计图的使用方法了,具体可以进入我的仓库查看,代码地址