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

如何使用BLoC实现仅重建屏幕动态上部区域(每秒更新)

用BLoC优化Flutter动态区域重绘,避免静态区域不必要重建

核心思路

把动态区域的状态(计时器数值、动画值、事件名称等)交给BLoC统一管理,让上部动态区域仅监听BLoC的状态变化进行局部重建,下部静态区域完全不参与重绘流程。


步骤1:定义BLoC的事件与状态

先封装动态区域需要的所有数据为状态类,再定义触发更新的事件类:

// 触发计时器更新的事件
class TimerTickEvent {}

// 存储动态区域所有数据的状态类
class CounterState {
  final double value;
  final String smallEventName;
  final String hour;
  final String minute;
  final String second;

  CounterState({
    required this.value,
    required this.smallEventName,
    required this.hour,
    required this.minute,
    required this.second,
  });
}

步骤2:实现BLoC逻辑

创建Bloc类处理计时器逻辑,每秒生成包含最新数据的状态:

class CounterBloc extends Bloc<TimerTickEvent, CounterState> {
  late final Timer _timer;
  final Animation<double> animation;
  final double baseValue;
  final String eventName;
  late Duration counter;

  CounterBloc({
    required this.animation,
    required this.baseValue,
    required this.eventName,
    required Duration initialCounter,
  }) : super(CounterState(
          value: animation.value * baseValue,
          smallEventName: eventName,
          hour: _formatHour(initialCounter),
          minute: _formatMinute(initialCounter),
          second: _formatSecond(initialCounter),
        )) {
    counter = initialCounter;
    // 启动每秒计时器
    _timer = Timer.periodic(const Duration(seconds: 1), (_) {
      counter = counter - const Duration(seconds: 1);
      add(TimerTickEvent());
    });

    on<TimerTickEvent>((event, emit) {
      emit(CounterState(
        value: animation.value * baseValue,
        smallEventName: eventName,
        hour: _formatHour(counter),
        minute: _formatMinute(counter),
        second: _formatSecond(counter),
      ));
    });
  }

  // 抽离时间格式化逻辑,避免重复代码
  static String _formatHour(Duration counter) {
    return counter.inHours > 9
        ? counter.toString().substring(0, 2)
        : '0${counter.toString().substring(0, 1)}';
  }

  static String _formatMinute(Duration counter) {
    return counter.inHours > 9
        ? counter.toString().substring(3, 5)
        : counter.toString().substring(2, 4);
  }

  static String _formatSecond(Duration counter) {
    return counter.inHours > 9
        ? counter.toString().substring(6, 8)
        : counter.toString().substring(5, 7);
  }

  @override
  Future<void> close() {
    _timer.cancel();
    return super.close();
  }
}

步骤3:封装动态区域为独立Widget

把原上部CustomPaint区域封装成单独Widget,用BlocBuilder监听BLoC状态变化,仅该Widget会在状态更新时重建:

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

  @override
  Widget build(BuildContext context) {
    return Expanded(
      flex: 5,
      child: BlocBuilder<CounterBloc, CounterState>(
        builder: (context, state) {
          return CustomPaint(
            painter: CustomHomeScreenCounter(
              value: state.value,
              smallEventName: state.smallEventName,
              hour: state.hour,
              minute: state.minute,
              second: state.second,
            ),
          );
        },
      ),
    );
  }
}

步骤4:在父Widget中集成BLoC

在包含上下区域的父Widget里,用BlocProvider提供CounterBloc,静态区域直接放在外层,不会随状态变化重建:

class HomeScreen extends StatefulWidget {
  const HomeScreen({super.key});

  @override
  State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateMixin {
  late AnimationController _animationController;
  late Animation<double> _animation;
  final double _baseValue = 1.0; // 根据实际业务替换
  final String _eventName = 'Fajr in'; // 根据实际业务替换
  late Duration _initialCounter; // 初始计时器值,根据实际业务初始化

  @override
  void initState() {
    super.initState();
    // 初始化动画控制器
    _animationController = AnimationController(
      vsync: this,
      duration: const Duration(seconds: 1),
    )..repeat();
    _animation = CurvedAnimation(parent: _animationController, curve: Curves.linear);
    // 示例初始计时器值:1小时30分20秒
    _initialCounter = const Duration(hours: 1, minutes: 30, seconds: 20);
  }

  @override
  void dispose() {
    _animationController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (context) => CounterBloc(
        animation: _animation,
        baseValue: _baseValue,
        eventName: _eventName,
        initialCounter: _initialCounter,
      ),
      child: Column(
        children: [
          // 动态区域:仅在BLoC状态变化时重建
          const DynamicCounterArea(),
          // 静态区域:永远不会因计时器更新重建
          Expanded(
            flex: 5,
            child: Container(
              color: Colors.grey[200],
              child: const Center(
                child: Text('静态内容区域'),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

关键说明

  • BLoC统一管理动态数据的更新逻辑,避免在父Widget中维护状态导致全局重绘。
  • BlocBuilder仅监听CounterBloc的状态变化,触发DynamicCounterArea局部重建,下部静态区域完全不受影响。
  • 时间格式化逻辑抽离到BLoC内部,减少Widget层代码冗余,保证状态数据的统一性。

内容的提问来源于stack exchange,提问作者Seif Kandil

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 06:48:19