You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在MPAndroidChart柱状图中为柱子添加四边边框及相关效果

MPAndroidChart 自定义柱状图实现方案

需求说明

  • 为每个柱子添加四边边框
  • 柱子底部不贴合X轴,保留间隙
  • 点击柱子时显示阴影效果

目标效果:
目标柱状图效果

问题分析

你之前自定义BarChartRenderer时,仅实现了柱子的左上、右上圆角,且未调整柱子底部位置导致贴合X轴,同时缺少边框和点击阴影逻辑。以下是完整的修改方案:

完整实现代码

自定义BarRenderer类

class CustomBarRenderer(chart: BarChart) : BarChartRenderer(chart, chart.animator, chart.viewPortHandler) {
    private var selectedBarIndex = -1 // 记录选中的柱子索引
    private val borderPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        style = Paint.Style.STROKE
        strokeWidth = 2f // 边框宽度,可按需调整
        color = Color.GRAY // 边框颜色,可按需调整
    }
    private val fillPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        style = Paint.Style.FILL
    }

    init {
        // 绑定柱子点击事件
        chart.setOnChartValueSelectedListener(object : OnChartValueSelectedListener {
            override fun onValueSelected(e: Entry?, h: Highlight?) {
                selectedBarIndex = e?.x?.toInt() ?: -1
                chart.invalidate()
            }

            override fun onNothingSelected() {
                selectedBarIndex = -1
                chart.invalidate()
            }
        })
    }

    override fun drawDataSet(c: Canvas, dataSet: IBarDataSet, index: Int) {
        val trans: Transformer = mChart.getTransformer(dataSet.axisDependency)
        val phaseX = mAnimator.phaseX
        val phaseY = mAnimator.phaseY
        mBarBuffers?.let { buffers ->
            val buffer = buffers[index]
            buffer.setPhases(phaseX, phaseY)
            buffer.setDataSet(index)
            buffer.setBarWidth(mChart.barData.barWidth)
            buffer.setInverted(mChart.isInverted(dataSet.axisDependency))
            buffer.feed(dataSet)
            trans.pointValuesToPixel(buffer.buffer)

            val barBottomSpacing = 20f // 柱子底部与X轴的间距,可调整
            var j = 0
            val bufferSize = buffer.size()
            while (j < bufferSize) {
                if (!mViewPortHandler.isInBoundsLeft(buffer.buffer[j + 2])) {
                    j += 4
                    continue
                }
                if (!mViewPortHandler.isInBoundsRight(buffer.buffer[j])) break

                // 提取柱子坐标,调整底部位置
                val left = buffer.buffer[j]
                val top = buffer.buffer[j + 1]
                val right = buffer.buffer[j + 2]
                val bottom = buffer.buffer[j + 3] - barBottomSpacing

                // 设置填充颜色
                fillPaint.color = dataSet.getColor(j / 4)
                // 为选中柱子添加阴影
                if (j / 4 == selectedBarIndex) {
                    fillPaint.setShadowLayer(10f, 0f, 5f, Color.parseColor("#88000000"))
                } else {
                    fillPaint.clearShadowLayer()
                }

                // 绘制填充圆角柱子
                val fillPath = RoundedRect(left, top, right, bottom, 25f, 25f, true, true, false, false)
                c.drawPath(fillPath, fillPaint)

                // 绘制柱子边框
                val borderPath = RoundedRect(left, top, right, bottom, 25f, 25f, true, true, false, false)
                c.drawPath(borderPath, borderPaint)

                j += 4
            }
        }
    }

    companion object {
        // 生成圆角矩形Path的工具方法
        fun RoundedRect(
            left: Float, top: Float, right: Float, bottom: Float, rx: Float, ry: Float,
            tl: Boolean, tr: Boolean, br: Boolean, bl: Boolean
        ): Path {
            var rx = rx
            var ry = ry
            val path = Path()
            if (rx < 0) rx = 0f
            if (ry < 0) ry = 0f
            val width = right - left
            val height = bottom - top
            if (rx > width / 2) rx = width / 2
            if (ry > height / 2) ry = height / 2
            val widthMinusCorners = width - 2 * rx
            val heightMinusCorners = height - 2 * ry

            path.moveTo(right, top + ry)
            if (tr) path.rQuadTo(0f, -ry, -rx, -ry) // 右上圆角
            else {
                path.rLineTo(0f, -ry)
                path.rLineTo(-rx, 0f)
            }
            path.rLineTo(-widthMinusCorners, 0f)
            if (tl) path.rQuadTo(-rx, 0f, -rx, ry) // 左上圆角
            else {
                path.rLineTo(-rx, 0f)
                path.rLineTo(0f, ry)
            }
            path.rLineTo(0f, heightMinusCorners)
            if (bl) path.rQuadTo(0f, ry, rx, ry) // 左下圆角
            else {
                path.rLineTo(0f, ry)
                path.rLineTo(rx, 0f)
            }
            path.rLineTo(widthMinusCorners, 0f)
            if (br) path.rQuadTo(rx, 0f, rx, -ry) // 右下圆角
            else {
                path.rLineTo(rx, 0f)
                path.rLineTo(0f, -ry)
            }
            path.rLineTo(0f, -heightMinusCorners)
            path.close()
            return path
        }
    }
}

使用自定义Renderer

在你的Activity/Fragment中设置BarChart的Renderer:

val barChart = findViewById<BarChart>(R.id.barChart)
// 其他图表配置(设置数据、坐标轴等)...
barChart.renderer = CustomBarRenderer(barChart)

关键修改点说明

  1. 四边边框实现:新增borderPaint(描边模式)和fillPaint(填充模式),分别绘制柱子的边框和内部填充,确保四边都有边框。
  2. 底部间隙调整:将柱子的底部Y坐标减去barBottomSpacing,让柱子与X轴保持固定间隙。
  3. 点击阴影效果:通过OnChartValueSelectedListener监听柱子点击事件,给选中的柱子添加阴影层,未选中时清除阴影。
  4. 简化逻辑:统一处理多颜色和单颜色数据集的绘制,减少冗余代码。

内容的提问来源于stack exchange,提问作者Arman Pandey

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 04:27:04