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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 02:41:28