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

Flutter Web中InkWell的onHover事件触发AnimationController已销毁异常如何解决

错误原因

报错的核心诱因是事件冒泡+组件提前销毁:

  • IconButton 本身自带悬停交互动画,当鼠标从外层InkWell区域移动到内部的IconButton上时,会先触发父级InkWell的onHover(isHoverIn: false)事件,此时你执行setState直接把IconButton从组件树中移除。
  • 但IconButton被移除时,它内部管理悬停动效的AnimationController已经触发了reverse()回调,回调执行时控制器已经随着IconButton的销毁被dispose了,所以抛出断言异常。

你打印的三次日志也刚好对应这个完整流程:

  1. 鼠标进入InkWell → 触发isHoverIn: true,显示按钮
  2. 鼠标移到IconButton上 → 触发父InkWell的isHoverIn: false,准备销毁按钮
  3. 按钮销毁瞬间鼠标还在原InkWell区域 → 再次触发isHoverIn: true,重新渲染按钮

解决方案

方案1:隐藏而非销毁IconButton(最简便,推荐)

不要用条件判断直接移除IconButton,改用Opacity+IgnorePointer控制显隐,组件不会被销毁,也就不会出现动画控制器被提前释放的问题。IgnorePointer 用来保证按钮隐藏时不会响应点击事件,避免误触。
代码修改如下:

child: Stack(
  children: [
    // 原有装饰背景保留
    Positioned.fill(
      child: Container(
        height: boxSize,
        width: boxSize,
        decoration: BoxDecoration(
          image: DecorationImage(
            image: widget.imageData.image!.image,
            fit: BoxFit.cover,
          ),
          border: Border.all(
            color: Colors.grey,
            width: 5,
          ),
          borderRadius: BorderRadius.circular(12),
        ),
      ),
    ),
    // IconButton常驻,仅控制显隐
    Align(
      alignment: Alignment.topRight,
      child: IgnorePointer(
        ignoring: !isCancelButtonVisible,
        child: Opacity(
          opacity: isCancelButtonVisible ? 1 : 0,
          child: IconButton(
            iconSize: 20,
            icon: const Icon(Icons.cancel),
            tooltip: 'rimuovi',
            onPressed: () => widget.onRemove(widget.position),
          ),
        ),
      ),
    )
  ],
),

方案2:阻止IconButton的悬停事件冒泡

给IconButton外层套MouseRegion,手动拦截hover事件,避免触发父级InkWell的离开回调:

Align(
  alignment: Alignment.topRight,
  child: MouseRegion(
    onHover: (_) {},
    child: IconButton(
      iconSize: 20,
      icon: const Icon(Icons.cancel),
      tooltip: 'rimuovi',
      onPressed: () => widget.onRemove(widget.position),
    ),
  ),
)

方案3:延迟状态更新(兜底方案)

如果不想改布局结构,可以给状态更新加个极短的延迟,等IconButton的动画回调执行完再销毁组件,注意要加mounted判断避免页面销毁后触发异常:

onHover: (bool isHoverIn) {
  print("isHoverIN: $isHoverIn iscancel: $isCancelButtonVisible");
  if (isHoverIn != isCancelButtonVisible) {
    Future.delayed(const Duration(milliseconds: 50), () {
      if (mounted) {
        setState(() {
          isCancelButtonVisible = isHoverIn;
        });
      }
    });
  }
},

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.25 03:54:06