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

如何在Modal Bottom Sheet内部获取路由动画位置,避免父组件转StatefulWidget?

解决方案:将Modal Bottom Sheet动画逻辑完全封装到组件内部

要实现无需外部传递AnimationController,同时能监听底部弹窗拖动动画的效果,可以利用Flutter路由系统自带的动画对象,把所有逻辑封装在模态框组件内部。

核心思路

showModalBottomSheet创建的ModalBottomSheetRoute内部会自动维护一个动画控制器,用于控制弹窗的展开/收起和拖动过程。我们可以在模态框组件内部通过ModalRoute.of(context)获取这个路由的动画对象,监听其变化来同步手柄的动画状态,完全不需要外部创建和传递控制器。

代码实现

1. 修改面板手柄组件,内部获取路由动画

class _ModalSheetHandleState extends State<_ModalSheetHandle>
    with SingleTickerProviderStateMixin {
  final _handleHeight = Insets.xs;
  late final _controller = AnimationController(
    duration: Timings.short,
    vsync: this,
  );
  late final _animation = Tween<double>(begin: 35, end: 50).animate(_controller);
  Animation<double>? _routeAnimation;

  @override
  void initState() {
    super.initState();
    // 延迟一帧确保组件挂载到路由树,能获取到ModalBottomSheetRoute
    WidgetsBinding.instance.addPostFrameCallback((_) {
      final modalRoute = ModalRoute.of(context) as ModalBottomSheetRoute?;
      _routeAnimation = modalRoute?.animation;
      _routeAnimation?.addListener(_updateHandle);
      // 初始化手柄状态
      _updateHandle();
    });
  }

  @override
  void dispose() {
    _routeAnimation?.removeListener(_updateHandle);
    _controller.dispose();
    super.dispose();
  }

  void _updateHandle() {
    final animationProgress = _routeAnimation?.value ?? 0.0;
    // 根据路由动画进度判断手柄状态:完全展开时放大,开始收起时缩小
    if (animationProgress >= 1.0 && !_controller.isCompleted) {
      _controller.forward();
    } else if (animationProgress < 1.0 && _controller.isCompleted) {
      _controller.reverse();
    }
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: _animation,
      builder: (_, __) => Container(
        width: _animation.value,
        height: _handleHeight,
        decoration: BoxDecoration(
          color: Colors.white,
          borderRadius: BorderRadius.circular(_handleHeight),
        ),
      ),
    );
  }
}

2. 封装模态框的静态显示方法

把showModalBottomSheet的调用逻辑封装到模态组件内部,让调用方无需处理任何控制器:

class ContextMenu extends StatelessWidget {
  final List<Widget> actions;

  const ContextMenu({super.key, required this.actions});

  // 封装静态方法,统一处理弹窗显示
  static Future<void> show(BuildContext context, {required List<Widget> actions}) {
    return showModalBottomSheet(
      context: context,
      builder: (_) => ContextMenu(actions: actions),
      // 无需手动传递transitionAnimationController,路由会自动创建
    );
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisSize: MainAxisSize.min,
      children: [
        const _ModalSheetHandle(),
        ...actions,
      ],
    );
  }
}

3. 调用方无状态使用模态框

现在所有调用该模态框的组件都可以保持为StatelessWidget:

class SomeUIElement extends StatelessWidget {
  const SomeUIElement({super.key});

  @override
  Widget build(BuildContext context) {
    return SomeWidget(
      child: Button(
        onTap: () {
          // 直接调用封装好的方法即可
          ContextMenu.show(
            context,
            actions: [
              // 这里传入你的操作按钮
              TextButton(onPressed: () {}, child: const Text("操作1")),
              TextButton(onPressed: () {}, child: const Text("操作2")),
            ],
          );
        },
      ),
    );
  }
}

关键说明

  • ModalBottomSheetRoute的animation对象的value范围是0.0到1.0,1.0表示弹窗完全展开,拖动过程中会实时变化。
  • 使用addPostFrameCallback延迟获取路由是因为组件initState阶段还未挂载到路由树,此时无法获取到ModalRoute实例。
  • 所有动画相关逻辑都封装在模态框组件内部,调用方无需关心控制器的创建和管理,完全符合你的需求。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 19:23:21