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

fl_chart散点图长按Tooltip闪显消失及选中点不显示问题

问题:fl_chart散点图Tooltip异常问题

我基于GitHub上的fl_chart散点图示例代码修改,仅将handleBuiltInTouches参数设为true,期望长按点位弹出Tooltip,但Tooltip仅闪显瞬间就消失;同时设置该参数后,即便showingTooltipIndicators已选中所有点位,也没有任何点位弹出Tooltip。相关代码如下:

class ScatterChartSample2 extends StatefulWidget {
  const ScatterChartSample2({Key? key}) : super(key: key);

  @override
  State<StatefulWidget> createState() => _ScatterChartSample2State();
}

class _ScatterChartSample2State extends State {
  int touchedIndex = -1;

  Color greyColor = Colors.grey;

  List<int> selectedSpots = [];

  @override
  Widget build(BuildContext context) {
    return AspectRatio(
      aspectRatio: 1,
      child: Card(
        color: const Color(0xff222222),
        child: fl.ScatterChart(
          fl.ScatterChartData(
            scatterSpots: [
              fl.ScatterSpot(
                4,
                4,
                color: selectedSpots.contains(0) ? Colors.green : greyColor,
              ),
              fl.ScatterSpot(
                2,
                5,
                color: selectedSpots.contains(1) ? Colors.yellow : greyColor,
                radius: 12,
              ),
              fl.ScatterSpot(
                4,
                5,
                color:
                    selectedSpots.contains(2) ? Colors.purpleAccent : greyColor,
                radius: 8,
              ),
              fl.ScatterSpot(
                8,
                6,
                color: selectedSpots.contains(3) ? Colors.orange : greyColor,
                radius: 20,
              ),
              fl.ScatterSpot(
                5,
                7,
                color: selectedSpots.contains(4) ? Colors.brown : greyColor,
                radius: 14,
              ),
              fl.ScatterSpot(
                7,
                2,
                color: selectedSpots.contains(5)
                    ? Colors.lightGreenAccent
                    : greyColor,
                radius: 18,
              ),
              fl.ScatterSpot(
                3,
                2,
                color: selectedSpots.contains(6) ? Colors.red : greyColor,
                radius: 36,
              ),
              fl.ScatterSpot(
                2,
                8,
                color:
                    selectedSpots.contains(7) ? Colors.tealAccent : greyColor,
                radius: 22,
              ),
            ],
            minX: 0,
            maxX: 10,
            minY: 0,
            maxY: 10,
            borderData: fl.FlBorderData(
              show: false,
            ),
            gridData: fl.FlGridData(
              show: true,
              drawHorizontalLine: true,
              checkToShowHorizontalLine: (value) => true,
              getDrawingHorizontalLine: (value) =>
                  fl.FlLine(color: Colors.white.withOpacity(0.1)),
              drawVerticalLine: true,
              checkToShowVerticalLine: (value) => true,
              getDrawingVerticalLine: (value) =>
                  fl.FlLine(color: Colors.white.withOpacity(0.1)),
            ),
            titlesData: fl.FlTitlesData(
              show: false,
            ),
            showingTooltipIndicators: selectedSpots,
            scatterTouchData: fl.ScatterTouchData(
              enabled: true,
              handleBuiltInTouches: true,
              touchTooltipData: fl.ScatterTouchTooltipData(
                tooltipBgColor: Colors.black,
                getTooltipItems: (fl.ScatterSpot touchedBarSpot) {
                  return fl.ScatterTooltipItem(
                    'X: ',
                    textStyle: TextStyle(
                      height: 1.2,
                      color: Colors.grey[100],
                      fontStyle: FontStyle.italic,
                    ),
                    bottomMargin: 10,
                    children: [
                      TextSpan(
                        text: '${touchedBarSpot.x.toInt()} \n',
                        style: const TextStyle(
                          color: Colors.white,
                          fontStyle: FontStyle.normal,
                          fontWeight: FontWeight.bold,
                        ),
                      ),
                      TextSpan(
                        text: 'Y: ',
                        style: TextStyle(
                          height: 1.2,
                          color: Colors.grey[100],
                          fontStyle: FontStyle.italic,
                        ),
                      ),
                      TextSpan(
                        text: touchedBarSpot.y.toInt().toString(),
                        style: const TextStyle(
                          color: Colors.white,
                          fontStyle: FontStyle.normal,
                          fontWeight: FontWeight.bold,
                        ),
                      ),
                    ],
                  );
                },
              ),
              touchCallback: (fl.FlTouchEvent event,
                  fl.ScatterTouchResponse? touchResponse) {
                if (touchResponse == null ||
                    touchResponse.touchedSpot == null) {
                  return;
                }
                if (event is fl.FlTapUpEvent) {
                  final sectionIndex = touchResponse.touchedSpot!.spotIndex;
                  setState(() {
                    if (selectedSpots.contains(sectionIndex)) {
                      selectedSpots.remove(sectionIndex);
                    } else {
                      selectedSpots.add(sectionIndex);
                    }
                  });
                }
              },
            ),
          ),
        ),
      ),
    );
  }
}

原因分析

  • 当handleBuiltInTouches设为true时,fl_chart内部会接管触摸事件的完整逻辑,包括Tooltip的显示与隐藏,此时自定义touchCallback中的点击事件会和内置逻辑冲突,触发Tooltip后立刻被关闭。
  • showingTooltipIndicators参数在handleBuiltInTouches=true时会被内置触摸逻辑覆盖,无法手动控制Tooltip的显示状态。

解决方案

方案1:保留内置触摸逻辑,调整事件处理

保留handleBuiltInTouches=true,让fl_chart自行处理Tooltip的长按显示逻辑,同时修改touchCallback避免事件冲突:

scatterTouchData: fl.ScatterTouchData(
  enabled: true,
  handleBuiltInTouches: true,
  touchTooltipData: fl.ScatterTouchTooltipData(
    tooltipBgColor: Colors.black,
    getTooltipItems: (fl.ScatterSpot touchedBarSpot) {
      return fl.ScatterTooltipItem(
        'X: ',
        textStyle: TextStyle(
          height: 1.2,
          color: Colors.grey[100],
          fontStyle: FontStyle.italic,
        ),
        bottomMargin: 10,
        children: [
          TextSpan(
            text: '${touchedBarSpot.x.toInt()} \n',
            style: const TextStyle(
              color: Colors.white,
              fontStyle: FontStyle.normal,
              fontWeight: FontWeight.bold,
            ),
          ),
          TextSpan(
            text: 'Y: ',
            style: TextStyle(
              height: 1.2,
              color: Colors.grey[100],
              fontStyle: FontStyle.italic,
            ),
          ),
          TextSpan(
            text: touchedBarSpot.y.toInt().toString(),
            style: const TextStyle(
              color: Colors.white,
              fontStyle: FontStyle.normal,
              fontWeight: FontWeight.bold,
            ),
          ),
        ],
      );
    },
  ),
  touchCallback: (fl.FlTouchEvent event, fl.ScatterTouchResponse? touchResponse) {
    if (touchResponse == null || touchResponse.touchedSpot == null) {
      return;
    }
    final sectionIndex = touchResponse.touchedSpot!.spotIndex;
    // 仅处理点击事件,不干扰内置长按Tooltip逻辑
    if (event is fl.FlTapUpEvent) {
      setState(() {
        if (selectedSpots.contains(sectionIndex)) {
          selectedSpots.remove(sectionIndex);
        } else {
          selectedSpots.add(sectionIndex);
        }
      });
    }
  },
),

同时移除showingTooltipIndicators参数,因为该参数在内置触摸逻辑下无效。

方案2:关闭内置触摸,手动控制Tooltip

如果需要通过showingTooltipIndicators手动控制Tooltip显示,将handleBuiltInTouches设为false,在touchCallback中手动管理Tooltip状态:

scatterTouchData: fl.ScatterTouchData(
  enabled: true,
  handleBuiltInTouches: false,
  touchTooltipData: fl.ScatterTouchTooltipData(
    tooltipBgColor: Colors.black,
    getTooltipItems: (fl.ScatterSpot touchedBarSpot) {
      // 保持原Tooltip样式逻辑不变
      return fl.ScatterTooltipItem(
        'X: ',
        textStyle: TextStyle(
          height: 1.2,
          color: Colors.grey[100],
          fontStyle: FontStyle.italic,
        ),
        bottomMargin: 10,
        children: [
          TextSpan(
            text: '${touchedBarSpot.x.toInt()} \n',
            style: const TextStyle(
              color: Colors.white,
              fontStyle: FontStyle.normal,
              fontWeight: FontWeight.bold,
            ),
          ),
          TextSpan(
            text: 'Y: ',
            style: TextStyle(
              height: 1.2,
              color: Colors.grey[100],
              fontStyle: FontStyle.italic,
            ),
          ),
          TextSpan(
            text: touchedBarSpot.y.toInt().toString(),
            style: const TextStyle(
              color: Colors.white,
              fontStyle: FontStyle.normal,
              fontWeight: FontWeight.bold,
            ),
          ),
        ],
      );
    },
  ),
  touchCallback: (fl.FlTouchEvent event, fl.ScatterTouchResponse? touchResponse) {
    if (touchResponse == null || touchResponse.touchedSpot == null) {
      setState(() {
        selectedSpots.clear();
      });
      return;
    }
    final sectionIndex = touchResponse.touchedSpot!.spotIndex;
    if (event is fl.FlLongPressStartEvent) {
      setState(() {
        if (!selectedSpots.contains(sectionIndex)) {
          selectedSpots.add(sectionIndex);
        }
      });
    } else if (event is fl.FlLongPressEndEvent) {
      setState(() {
        selectedSpots.remove(sectionIndex);
      });
    } else if (event is fl.FlTapUpEvent) {
      setState(() {
        if (selectedSpots.contains(sectionIndex)) {
          selectedSpots.remove(sectionIndex);
        } else {
          selectedSpots.add(sectionIndex);
        }
      });
    }
  },
),

此时showingTooltipIndicators: selectedSpots会正常生效,可手动控制哪些点位显示Tooltip。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.25 00:54:33