效果图1
效果图2
效果图3
效果图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)
}
就这么多。
谢谢欣赏。