如何在Bloc架构中实现LinearProgressIndicator的平滑动画?
在Bloc架构中实现状态间的平滑动画过渡
问题背景
我有一个Bloc,会发出包含进度值(0-1对应0%-100%)的状态,通过LinearProgressIndicator展示。进度更新是阶段性跳变的(比如从0直接到0.3),但当前实现中进度指示器是瞬间切换状态,想要实现从旧状态平滑过渡到新状态的动画效果。
试过用TweenAnimationBuilder,但不知道怎么在Bloc架构下获取当前的动画值。本质问题是:在Bloc架构中,如何为无状态/有状态组件实现两个连续状态间的动画过渡?
复现代码
import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; class StartProgressEvent {} class ProgressState { final double progress; ProgressState(this.progress); } class ProgressBloc extends Bloc<StartProgressEvent, ProgressState> { ProgressBloc() : super(ProgressState(0)) { on<StartProgressEvent>(_startProgress); } void _startProgress( StartProgressEvent event, Emitter<ProgressState> emit, ) async { emit(ProgressState(0.1)); await Future.delayed(const Duration(seconds: 3)); emit(ProgressState(0.4)); await Future.delayed(const Duration(seconds: 3)); emit(ProgressState(0.7)); await Future.delayed(const Duration(seconds: 3)); emit(ProgressState(1.0)); } } void main() { runApp(const DemoApp()); } class DemoApp extends StatelessWidget { const DemoApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( body: Center( child: BlocProvider<ProgressBloc>( create: (context) => ProgressBloc()..add(StartProgressEvent()), child: BlocBuilder<ProgressBloc, ProgressState>( builder: (context, state) => LinearProgressIndicator(value: state.progress), ), ), ), ), ); } }
解决方案
方法一:UI层维护动画状态(推荐,职责分离)
不需要修改Bloc逻辑,在UI层用StatefulWidget保存当前动画值,当Bloc发出新状态时,触发从旧值到新值的过渡动画。
自定义动画进度指示器组件
class AnimatedProgressIndicator extends StatefulWidget { final double targetProgress; const AnimatedProgressIndicator({super.key, required this.targetProgress}); @override State<AnimatedProgressIndicator> createState() => _AnimatedProgressIndicatorState(); } class _AnimatedProgressIndicatorState extends State<AnimatedProgressIndicator> with SingleTickerProviderStateMixin { late AnimationController _controller; double _currentProgress = 0; @override void initState() { super.initState(); _controller = AnimationController( vsync: this, duration: const Duration(milliseconds: 500), // 可自定义动画时长 ); _animateToTarget(widget.targetProgress); } @override void didUpdateWidget(covariant AnimatedProgressIndicator oldWidget) { super.didUpdateWidget(oldWidget); if (oldWidget.targetProgress != widget.targetProgress) { _animateToTarget(widget.targetProgress); } } void _animateToTarget(double target) { final tween = Tween<double>(begin: _currentProgress, end: target); tween.animate(_controller) ..addListener(() { setState(() { _currentProgress = _animation.value; }); }); _controller.reset(); _controller.forward(); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return LinearProgressIndicator(value: _currentProgress); } }
使用组件替换原指示器
在BlocBuilder中使用自定义组件:
BlocBuilder<ProgressBloc, ProgressState>( builder: (context, state) => AnimatedProgressIndicator(targetProgress: state.progress), )
方法二:Bloc层控制动画逻辑(复杂场景适用)
如果需要根据业务逻辑动态调整动画(比如不同阶段用不同时长),可以在Bloc中集成AnimationController,持续发出动画过程中的中间状态。
修改后的Bloc
class ProgressBloc extends Bloc<StartProgressEvent, ProgressState> with SingleTickerProviderStateMixin { late AnimationController _animationController; double _currentProgress = 0; ProgressBloc() : super(ProgressState(0)) { _animationController = AnimationController( vsync: this, duration: const Duration(milliseconds: 500), ); on<StartProgressEvent>(_startProgress); } void _startProgress( StartProgressEvent event, Emitter<ProgressState> emit, ) async { await _animateTo(0.1, emit); await Future.delayed(const Duration(seconds: 3)); await _animateTo(0.4, emit); await Future.delayed(const Duration(seconds: 3)); await _animateTo(0.7, emit); await Future.delayed(const Duration(seconds: 3)); await _animateTo(1.0, emit); } Future<void> _animateTo(double target, Emitter<ProgressState> emit) async { final tween = Tween<double>(begin: _currentProgress, end: target); tween.animate(_animationController) ..addListener(() { emit(ProgressState(_animationController.value)); }); _animationController.reset(); await _animationController.forward(); _currentProgress = target; } @override Future<void> close() { _animationController.dispose(); return super.close(); } }
UI层无需修改
直接使用原来的BlocBuilder即可,Bloc会自动发出动画过程中的进度值。
通用逻辑总结
无论哪种组件,实现状态过渡动画的核心是:
- 保留上一次的状态值
- 新状态到来时,创建从旧值到新值的动画
- 动画过程中持续更新UI显示
无状态组件可以通过父级StatefulWidget维护动画状态;有状态组件直接在自身维护。Bloc专注于业务逻辑和发出目标状态,UI层负责动画过渡,这样职责分离更清晰。
内容的提问来源于stack exchange,提问作者SqueezyMo
相关产品推荐
相关产品推荐

