MPAndroidChart 随心所欲定义Marker显示位置

1,236 阅读3分钟

背景和问题

图表点击显示的 Marker 默认情况下是可以正常展示的,也就是可以完整的展示在Chart图表上。(仅以折线图为例,柱状图同理)

image.png

但是呢,我们的测试和产品要求比较高,对于 Marker 显示位置有以下要求:

  1. 不可以遮挡X轴文字
  2. 上下左右都不能超出图表被切割
  3. 尽量不要挡住高亮线
  4. 不要离交点太远

很明显,默认的 Marker 位置无法满足需求1、3、4。

解决思路

研究MarkerView源码,负责绘制 Marker 的draw 方法传递的坐标是选中点的坐标。之后会加上getOffsetForDrawingAtPoint额外计算的偏移量最终决定了 Marker 绘制位置。也就是说默认的情况下,marker 的左上角就是我们的交点。Marker 整体是在选中点右下方,因此我们在计算额外偏移量时需要避免 Marker 超出Chart底部和右边界。

  • 为了满足要求1,我们需要给底部显示的X轴文字留20dp的空间,
  • 为了满足要求2,我们需要确保Marker超出底部或者右边空间时向上或者向左偏移
  • 为了满足要求3,正常情况下,Marker需要向右或向左偏移一定的长度从而避免边界与高亮线重叠,由于我的Marker自身四周是有8dp的阴影存在的,所以不需要考虑额外的偏移。
  • 为了满足要求4,我们可以尽量让 Marker 的边紧贴交点。

image.png

以竖直方向为例,Marker 底部紧贴X轴文字上方时相关距离满足以下等式。

posY + height + xAxisLabelHeight = chart.height

此时

posY = chart.height - height - xAxisLabelHeight

因此当 posY 大于右边数值时说明 Marker 会覆盖底部X轴文字或者超出 ChartView 被切割,这种情况下需要向上偏移。如果 ChartView 高度比较大,可以直接向上偏移整个 Marker 高度,这样 Marker 的底部正好在交点上,视觉效果比较好。

但是呢,如果 Chart 本身并没有两个 Marker 的高度的话,这样的偏移量会导致 Marker 顶部超出 Chart 范围,导致被切割。因此当 posY 小于 Marker 自身高度时不可以直接向上偏移整个 Marker 高度,我选择了让它向上偏移到整个 Marker 位于居中的位置。大家也可以考虑让它向上偏移到正好不被底部切割的距离。

横向的偏移也是同理。

最终方案代码(Kotlin)

在自定义 Marker 中重写以下方法。

override fun getOffsetForDrawingAtPoint(posX: Float, posY: Float): MPPointF {
    val offset = offset
    val chart = chartView
    //Marker 自身宽度
    val width = width
    //Marker 自身高度
    val height = height

    //避免盖住X轴文字
    val xAxisLabelHeight = Utils.dp2px(20f, context).toFloat()

    // posY \posX 指的是markerView左上角点在图表上面的位置
    //处理Y方向
    if (posY > chart.height - height - xAxisLabelHeight) {
        //不处理会超出下边界
        if (posY < height) {
            //直接-height会超出上边界
            //让Marker整体正好在图表View的竖直方向中心,这样可以尽可能避免Marker超出View范围被切割。
            offset.y = 0.5f * chart.height - 0.5f * height - posY
        } else {
            offset.y = -height.toFloat()
        }
    } else {
        offset.y = 0f
    }

    //处理X方向,分为2种情况,1、超出右边 2、正常情况
    if (posX > chart.width - width) {
        //如果超过右边界,则向左偏移markerView的宽度
        offset.x = -width.toFloat()
    } else {
        //默认情况,不用偏移
        offset.x = 0f
    }
    return offset;
}

成品展示

1、Marker 自身相当高的折线图 image.png 2、柱状图 image.png