Android-简单的条形图

319 阅读2分钟

image.png

效果图1

image.png

效果图2

image.png

效果图3

image.png

效果图4

前言:只是为了研究,细节待优化。 挺好奇,别人的柱状图是怎么实现的,今天终于有空研究的机会了。

1. 实现思路:

刚开始的时候,不知道怎么去设置数据的总高度值。后来突发奇想,获取最大值乘以一个系数不就好了吗。 先找最高点,然后乘以系数(我这里是1.2),这个高度等于View的总高度。然后其他点也好算了。
比如最高值是50,那么高度是50*1.2=60 把60作为View的高度。
最高点的view高度是50/60= 0.8倍的view高度。
其他点的值,比如20,那么它的高度是20/60 = 0.3倍的view高度。
以此类推。

2. 相关的知识:

canvas的drawRect方法来画区域
public RectF (float left, float top, float right, float bottom)
四个参数分别是区域左边到View X轴的距离,区域上面到Y轴的距离,区域右边到X轴的距离,区域底部到Y轴的距离。 比较直观的图。

3.实现:

首先重写View的三个重载(官方推荐4个)并且定义一个初始化方法。

class HomeDiagramView : View {
    constructor(context: Context?) : super(context) {
        initView(context, null)
    }

    constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) {
        initView(context, attrs)
    }

    constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(
        context,
        attrs,
        defStyleAttr
    ) {
        initView(context, attrs)
    }
    
    //后期可以做View自定义属性相关的。我这里随机产生10条数据。
private fun initView(context: Context?, attrs: AttributeSet?) {
    for (i: Int in 0..10) {
        val rand = Random.nextInt(10, 130)
        data.add(rand)
    }
}

然后对数据集做位置关系的处理。说白了生成RectF集合,然后再通过canvas绘制出来。 我的思路如果有1条数据,那么这个数据应该在View中间。
如果数据是2条,那么第一个在View的1/3位置,第二个在View的2/3位置。 以此类推。

    private fun generateLine() {
        //获取最大数据,作为View的高度。
        val max = data.maxOf { it } * 1.2
        //总数
        val needTotal = data.size + 1
        data.forEachIndexed { index, i ->
            rectFS.add(
          //  剪掉5dp,加5dp = 10dp是长方形的宽度
                RectF(
                    ((width * (index + 1)) / needTotal).toFloat() - context.dp2px(5f),
                    (height - ((height * i) / max)).toFloat(),
                    ((width * (index + 1)) / needTotal).toFloat() + context.dp2px(5f),
                    height.toFloat()
                )
            )
        }
    }

最后通过onDraw画出来

override fun onDraw(canvas: Canvas?) {
    super.onDraw(canvas)
    mPaint.color = Color.BLACK
    mPaint.style = Paint.Style.STROKE

    mPaint.typeface = Typeface.DEFAULT
    canvas!!.drawColor(Color.WHITE)
    mPaint.strokeWidth = context!!.dp2px(2f)
    //画X轴
    canvas.drawLine(0f, 0f, 0f, height.toFloat(), mPaint)
    //画Y轴
    canvas.drawLine(0f, height.toFloat(), width.toFloat(), height.toFloat(), mPaint)
    //那四边形数据集合。
    generateLine()
    //画笔细度调整为1dp
    mPaint.strokeWidth = context!!.dp2px(1f)
    rectFS.forEach {
    //画四边形
        mPaint.color = Color.BLACK
        mPaint.style = Paint.Style.STROKE
        canvas.drawRect(it, mPaint)
    //画内部颜色
        val rnd = Random
        mPaint.setARGB(255, rnd.nextInt(256), rnd.nextInt(256), rnd.nextInt(256))
        mPaint.style = Paint.Style.FILL
       cRectF = Rect(
            (it.left + context.dp2px(0.5)).toInt(),
            (it.top + context.dp2px(0.5)).toInt(),
            it.right.toInt(),
            (it.bottom - context.dp2px(0.5)).toInt()
        )
        canvas.drawRect(
            cRectF, mPaint
        )
        //画点中心
        mPaint.color = Color.RED
        mPaint.style = Paint.Style.FILL
        canvas.drawCircle(it.right - context.dp2px(5), it.top, context.dp2px(1f), mPaint)
    }

//下面是画值和线条。
    mPaint.textSize = 10f
    mPaint.color = Color.BLACK
    mPaint.textAlign = Paint.Align.CENTER
    rectFS.forEachIndexed { index, rectF ->
        if (index < data.size) {
            canvas.drawText(
                "${data[index]}M", rectF.right - context.dp2px(5),
                rectF.top - context.dp2px(5f),
                mPaint
            )
            if (index == 0) {
                path.moveTo(rectF.right - context.dp2px(5), rectF.top)
            } else {
                path.lineTo(rectF.right - context.dp2px(5), rectF.top)
            }
        }
    }
    mPaint.color = Color.MAGENTA
    mPaint.strokeWidth = context.dp2px(0.5f)
    mPaint.style = Paint.Style.STROKE
    canvas.drawPath(path, mPaint)
}

就这么多。

谢谢欣赏。