Flutter 如何在AnimatedSwitcher转场动画运行时禁用所有点击事件
问题解决:AnimatedSwitcher转场期间禁用点击异常修复
错误原因
transitionBuilder本身属于组件构建流程的执行环节,你直接在该方法内给动画添加状态监听器,监听器内同步调用setState会触发新一轮组件构建,和当前正在执行的构建流程冲突,就会抛出setState() or markNeedsBuild() called during build.异常。
同时原写法存在重复绑定监听器的隐藏问题:每次转场触发transitionBuilder执行时,都会给动画对象重复添加监听器,造成多余的性能开销。
方案1:动画状态监听修复版
将监听器绑定逻辑推迟到当前帧构建完成后执行,即可避免构建冲突,同时新增状态标记避免重复绑定监听器:
class _MainPageState extends State<MainPage> { int pageIndex = 1; bool disableClicks = false; // 记录上一次动画状态,避免重复绑定监听器 AnimationStatus? _lastAnimStatus; @override Widget build(BuildContext context) { return AbsorbPointer( absorbing: disableClicks, child: Scaffold( body: AnimatedSwitcher( duration: 500.milliseconds, child: pageIndex == 0 ? Container(color: Colors.red, key: UniqueKey()) : pageIndex == 1 ? Container(color: Colors.blue, key: UniqueKey()) : Container(color: Colors.green, key: UniqueKey()), transitionBuilder: (child, animation) { // 把监听器绑定逻辑放到当前帧构建完成后执行 WidgetsBinding.instance.addPostFrameCallback((_) { if (animation.status == _lastAnimStatus) return; _lastAnimStatus = animation.status; animation.addStatusListener((status) { _lastAnimStatus = status; switch (status) { case AnimationStatus.forward: case AnimationStatus.reverse: if (mounted && !disableClicks) { setState(() => disableClicks = true); } break; case AnimationStatus.dismissed: case AnimationStatus.completed: if (mounted && disableClicks) { setState(() => disableClicks = false); } break; } }); }); return FadeTransition(opacity: animation, child: child); }, ), ), ); } }
方案2:更简洁的预标记方案
如果你的转场时长是固定值,不需要动态适配动画时长,可以直接在切换页面的逻辑里提前标记禁用点击,等动画时长结束后自动恢复,不需要监听动画状态,实现更简单:
// 页面切换逻辑示例 void switchPage(int targetIndex) { // 已禁用点击或目标页和当前页相同直接返回 if (disableClicks || pageIndex == targetIndex) return; setState(() { disableClicks = true; pageIndex = targetIndex; }); // 动画时长到后恢复点击 Future.delayed(500.milliseconds, () { if (mounted) setState(() => disableClicks = false); }); }
内容的提问来源于stack exchange,提问作者Saman Salehi
相关产品推荐
相关产品推荐

