Flutter如何从AnimatedSwitcher子组件内部修改状态实现切换动画
问题根因
AnimatedSwitcher 只有在自身 build 阶段检测到子组件的key或runtimeType发生变化时,才会触发新旧子组件的过渡动画。你的代码存在两个核心问题导致动画不生效:
- 控制切换的
index是全局变量,且点击答案时调用的是子组件QuestionWidget的setState,仅刷新子组件自身,不会触发持有AnimatedSwitcher的父组件Test重新构建,AnimatedSwitcher完全感知不到子组件需要替换 - 问答切换的状态和触发逻辑放在子组件内部,违背了状态持有原则:谁渲染动画组件,谁就应该持有控制动画切换的状态。
可行实现方式
不需要更换组件,AnimatedSwitcher完全可以满足问答整体参与动画的需求,只需要做3处调整:
- 将
index状态从全局变量移动到持有AnimatedSwitcher的_TestState中统一管理 - 给
QuestionWidget新增答案点击回调参数,点击答案按钮时不直接修改index,而是调用父组件传入的回调 - 在父组件的回调中执行
setState更新index,让AnimatedSwitcher感知到子组件变化,自动触发新旧问答模块的过渡动画
如果后续需要支持滑动切换、自定义切换方向等更复杂的交互,再考虑替换为PageView结合自定义转场动画即可,当前场景无需额外引入组件。
修正后完整代码
import 'package:flutter/material.dart'; class Test extends StatefulWidget { const Test({Key? key}) : super(key: key); @override State<Test> createState() => _TestState(); } class _TestState extends State<Test> { // 将index状态移动到AnimatedSwitcher所在的父组件管理 int index = 0; // 问答数据统一放在父组件管理,避免全局变量 final questionList = [ Question("What is your favorite color?", ["red", "blue", "green"]), Question("How do you do today?", ["great", "not so well"]), Question("Do you like Flutter", ["yes", "no"]), ]; // 统一处理答案点击切换逻辑 void _onAnswerTap() { final isLastIndex = index == questionList.length - 1; setState(() => index = isLastIndex ? 0 : index + 1); } @override Widget build(BuildContext context) { var size = MediaQuery.of(context).size; return Scaffold( body: Center( child: SizedBox( width: size.width * 0.7, child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ AnimatedSwitcher( duration: const Duration(milliseconds: 400), transitionBuilder: (child, animation) => SlideTransition( position: Tween<Offset>( begin: const Offset(0.0, -1.0), end: const Offset(0.0, 0.0)) .animate(animation), child: FadeTransition( opacity: animation, child: child)), // 每次index更新生成带新key的问答组件,传入点击回调 child: QuestionWidget( key: ValueKey(index), question: questionList[index], onAnswerSelected: _onAnswerTap, ) ), ], )))); } } class QuestionWidget extends StatefulWidget { final Question question; // 新增答案点击回调参数 final VoidCallback? onAnswerSelected; const QuestionWidget({ required this.question, this.onAnswerSelected, Key? key, }) : super(key: key); @override State<QuestionWidget> createState() => _QuestionWidgetState(); } class _QuestionWidgetState extends State<QuestionWidget> { @override Widget build(BuildContext context) { return Column(children: [ Text( widget.question.questionText, style: const TextStyle( fontSize: 25, ), ), Wrap( direction: Axis.horizontal, alignment: WrapAlignment.center, spacing: 5.0, children: [ for (var i in widget.question.answers) ElevatedButton( child: Text(i.toString()), onPressed: widget.onAnswerSelected ) ], ) ]); } } class Question { String questionText; List<String> answers; Question(this.questionText, this.answers); }
扩展提示:如果你需要自定义旧组件退出的动画方向(比如旧题向上滑出、新题从下滑入),可以在
transitionBuilder中通过判断animation.status区分进入/退出组件,分别配置不同的位移动画参数即可。
内容的提问来源于stack exchange,提问作者codecodecode
相关产品推荐
相关产品推荐

