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

如何在Flutter的StatelessWidget中结合Cubit/Bloc使用AnimationController?

在StatelessWidget中结合Cubit/Bloc使用AnimationController的实现方案

核心思路

StatelessWidget没有initState和dispose这类生命周期方法,所以我们把AnimationController的创建、状态管理和资源释放逻辑完全交给Cubit/Bloc处理,StatelessWidget只负责监听状态变化并渲染动画内容。


1. 基于Cubit的实现

第一步:创建管理动画的Cubit

这个Cubit会封装AnimationController的初始化、播放、反向播放和资源释放逻辑:

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter/scheduler.dart';

// 定义动画状态,包含控制器和动画对象
class AnimationState {
  final AnimationController controller;
  final Animation<double> animation;

  AnimationState({required this.controller, required this.animation});
}

class AnimationCubit extends Cubit<AnimationState> {
  AnimationCubit(TickerProvider vsync) : super(_initAnimation(vsync)) {
    // 监听动画值变化,触发状态更新以刷新UI
    state.controller.addListener(() => emit(state));
  }

  // 初始化动画控制器和动画对象
  static AnimationState _initAnimation(TickerProvider vsync) {
    final controller = AnimationController(
      duration: const Duration(seconds: 2),
      vsync: vsync,
    );
    final animation = Tween<double>(begin: 0, end: 1).animate(controller);
    return AnimationState(controller: controller, animation: animation);
  }

  // 播放动画
  void play() => state.controller.forward();

  // 反向播放动画
  void reverse() => state.controller.reverse();

  // 重置动画
  void reset() => state.controller.reset();

  // 释放资源,避免内存泄漏
  @override
  Future<void> close() {
    state.controller.dispose();
    return super.close();
  }
}

第二步:在StatelessWidget中使用Cubit

通过BlocProvider提供Cubit实例,用BlocBuilder监听状态变化,结合AnimatedBuilder渲染动画:

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter/scheduler.dart';

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

  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      // 使用全局SchedulerBinding作为vsync,解决StatelessWidget无法提供TickerProvider的问题
      create: (_) => AnimationCubit(SchedulerBinding.instance),
      child: Scaffold(
        appBar: AppBar(title: const Text('Stateless + Cubit 动画示例')),
        body: Center(
          child: BlocBuilder<AnimationCubit, AnimationState>(
            builder: (context, state) {
              return AnimatedBuilder(
                animation: state.animation,
                builder: (_, child) {
                  return Transform.scale(
                    scale: state.animation.value,
                    child: child,
                  );
                },
                child: const FlutterLogo(size: 200),
              );
            },
          ),
        ),
        floatingActionButton: Row(
          mainAxisAlignment: MainAxisAlignment.end,
          children: [
            FloatingActionButton(
              onPressed: () => context.read<AnimationCubit>().play(),
              child: const Icon(Icons.play_arrow),
            ),
            const SizedBox(width: 10),
            FloatingActionButton(
              onPressed: () => context.read<AnimationCubit>().reverse(),
              child: const Icon(Icons.replay),
            ),
          ],
        ),
      ),
    );
  }
}

2. 基于Bloc的实现(可选)

如果习惯用Bloc的事件-状态模式,只需调整为Bloc结构:

第一步:定义动画事件和Bloc

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

// 动画事件
abstract class AnimationEvent {}
class PlayAnimation extends AnimationEvent {}
class ReverseAnimation extends AnimationEvent {}
class ResetAnimation extends AnimationEvent {}

// 动画状态(和Cubit的状态一致)
class AnimationState {
  final AnimationController controller;
  final Animation<double> animation;

  AnimationState({required this.controller, required this.animation});
}

class AnimationBloc extends Bloc<AnimationEvent, AnimationState> {
  AnimationBloc(TickerProvider vsync) : super(_initAnimation(vsync)) {
    on<PlayAnimation>((_, emit) => state.controller.forward());
    on<ReverseAnimation>((_, emit) => state.controller.reverse());
    on<ResetAnimation>((_, emit) => state.controller.reset());
    state.controller.addListener(() => emit(state));
  }

  static AnimationState _initAnimation(TickerProvider vsync) {
    final controller = AnimationController(
      duration: const Duration(seconds: 2),
      vsync: vsync,
    );
    final animation = Tween<double>(begin: 0, end: 1).animate(controller);
    return AnimationState(controller: controller, animation: animation);
  }

  @override
  Future<void> close() {
    state.controller.dispose();
    return super.close();
  }
}

第二步:在StatelessWidget中使用Bloc

只需把BlocProvider和事件调用方式修改即可:

// ... 其他代码和Cubit版本一致
BlocProvider(
  create: (_) => AnimationBloc(SchedulerBinding.instance),
  child: Scaffold(
    // ...
    floatingActionButton: Row(
      children: [
        FloatingActionButton(
          onPressed: () => context.read<AnimationBloc>().add(PlayAnimation()),
          child: const Icon(Icons.play_arrow),
        ),
        // ... 其他按钮同理
      ],
    ),
  ),
)

关键注意事项

  • 资源释放:必须在Cubit/Bloc的close方法中调用AnimationController.dispose(),否则会造成内存泄漏。
  • 状态更新触发:动画值变化不会自动触发Bloc/Cubit的状态更新,所以需要在控制器的addListener中调用emit(state)。
  • Vsync获取:StatelessWidget无法直接提供TickerProvider,这里使用全局的SchedulerBinding.instance作为替代;如果是复杂动画场景,建议从父级StatefulWidget传递TickerProvider。

内容的提问来源于stack exchange,提问作者Mohammad Mirshahbazi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.24 17:16:00