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

Flutter点击按钮时Bloc实现的价格计数器数值不更新问题

问题描述

基于Bloc状态管理实现带价格展示、增减按钮的计数器功能,运行时点击增减按钮后价格数值无变化,判定为状态更新未触发UI联动,需定位原因并修复。
涉及两个核心组件:price_counter、input_fields_price,原始实现代码如下:

price_counter 原始代码

class PriceCounter extends StatelessWidget {
  PriceCounter({Key? key, required this.price}) : super(key: key);
  double price = 0.13;

  @override
  Widget build(BuildContext context) {
    final FilterPriceCubit cubit = BlocProvider.of<FilterPriceCubit>(context);

    return BlocBuilder<FilterPriceCubit, FilterPriceState>(
      builder: (context, state) {
        if (state is FilterPriceInitial) {
          state.fitlerPrice = price;
          return Padding(
            padding: const EdgeInsets.symmetric(horizontal: 21),
            child: Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                IconButton(
                  onPressed: () =>
                      cubit.priceFilterDecrement(state.fitlerPrice),
                  icon: SvgPicture.asset(constants.Assets.minus),
                  constraints: const BoxConstraints(),
                  padding: EdgeInsets.zero,
                ),
                const SizedBox(width: 20),
                InputFieldPrice(
                    price: state.fitlerPrice,
                    textStyle: constants.Styles.normalBookTextStyleWhite),
                const SizedBox(width: 20),
                IconButton(
                  onPressed: () =>
                      cubit.priceFilterIncrement(state.fitlerPrice),
                  icon: SvgPicture.asset(constants.Assets.plus),
                  constraints: const BoxConstraints(),
                  padding: EdgeInsets.zero,
                ),
              ],
            ),
          );
        }
        return const SizedBox();
      },
    );
  }
}

input_fields_price 原始代码

class InputFieldPrice extends StatefulWidget {
  final double price;
  final TextStyle textStyle;

  const InputFieldPrice(
      {Key? key, required this.price, required this.textStyle})
      : super(key: key);

  @override
  State<InputFieldPrice> createState() => _InputFieldPrice();
}

class _InputFieldPrice extends State<InputFieldPrice> {
  final _formKey = GlobalKey<FormState>();
  final _priceController = TextEditingController();

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

  @override
  Widget build(BuildContext context) => SizedBox(
        width: 100,
        child: Form(
          key: _formKey,
          child: TextFormField(
            keyboardType: TextInputType.number,
            controller: _priceController
              ..text = widget.price.toStringAsFixed(2),
            style: widget.textStyle,
            textAlign: TextAlign.center,
            decoration: InputDecoration(
              prefix: Text('JC', style: widget.textStyle),
              suffix: const Text(
                'KWh',
                style: constants.Styles.smallerBookTextStyleWhite,
              ),
              contentPadding: EdgeInsets.zero,
              border: InputBorder.none,
            ),
            inputFormatters: [
              LengthLimitingTextInputFormatter(4),
              FilteringTextInputFormatter.allow(RegExp("[0-9.]")),
            ],
          ),
        ),
      );
}
故障根因
  • 直接修改状态实例属性:Bloc要求状态为不可变类型,代码在BlocBuilder构建流程中直接修改FilterPriceInitial对象的fitlerPrice字段,没有通过emit方法提交新状态,Bloc无法感知状态变更,不会触发UI重建。
  • 状态分支判断错误:BlocBuilder仅在状态为FilterPriceInitial类型时返回计数器UI,一旦Cubit提交其他类型的状态,会直接返回空SizedBox,计数器整体消失;即使提交同类型状态,因为是同一对象引用,Bloc默认的相等性校验会判定状态无变化,跳过重建流程。
  • 增减逻辑耦合UI层:按钮点击时将当前状态的价格值传入Cubit增减方法,Cubit没有独立维护状态值,计算逻辑和UI层强耦合,极易出现值传递错误。
  • 输入框同步逻辑错误:InputFieldPrice为StatefulWidget,在build方法中反复给TextEditingController赋值,会导致光标位置异常、父组件传入新价格时无法正确同步展示内容。
修复方案

按以下顺序修改代码即可实现价格正常联动:

  1. 重构Bloc状态与Cubit逻辑
    将状态类改为不可变类型,建议配合equatable做值相等性判断,Cubit内部独立维护价格计算逻辑,增减操作后提交全新状态实例:
// 状态基类,继承Equatable保证值比较正确
class FilterPriceState extends Equatable {
  final double filterPrice;
  const FilterPriceState({required this.filterPrice});

  @override
  List<Object> get props => [filterPrice];
}

// 初始状态
class FilterPriceInitial extends FilterPriceState {
  const FilterPriceInitial({required double initialPrice}) : super(filterPrice: initialPrice);
}

// Cubit实现
class FilterPriceCubit extends Cubit<FilterPriceState> {
  FilterPriceCubit({double defaultPrice = 0.13}) : super(FilterPriceInitial(initialPrice: defaultPrice));

  // 减价逻辑,步长默认0.01,做边界校验防止价格为负
  void priceFilterDecrement() {
    final newPrice = (state.filterPrice - 0.01).clamp(0, double.infinity);
    emit(FilterPriceState(filterPrice: newPrice));
  }

  // 加价逻辑
  void priceFilterIncrement() {
    final newPrice = state.filterPrice + 0.01;
    emit(FilterPriceState(filterPrice: newPrice));
  }
}
  1. 修正PriceCounter组件
    移除直接修改状态属性的代码,删除错误的空容器返回分支,按钮调用增减方法时不需要传参:
class PriceCounter extends StatelessWidget {
  const PriceCounter({Key? key, required this.price}) : super(key: key);
  final double price;

  @override
  Widget build(BuildContext context) {
    final FilterPriceCubit cubit = BlocProvider.of<FilterPriceCubit>(context);

    return BlocBuilder<FilterPriceCubit, FilterPriceState>(
      builder: (context, state) {
        return Padding(
          padding: const EdgeInsets.symmetric(horizontal: 21),
          child: Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              IconButton(
                onPressed: () => cubit.priceFilterDecrement(),
                icon: SvgPicture.asset(constants.Assets.minus),
                constraints: const BoxConstraints(),
                padding: EdgeInsets.zero,
              ),
              const SizedBox(width: 20),
              InputFieldPrice(
                  price: state.filterPrice,
                  textStyle: constants.Styles.normalBookTextStyleWhite),
              const SizedBox(width: 20),
              IconButton(
                onPressed: () => cubit.priceFilterIncrement(),
                icon: SvgPicture.asset(constants.Assets.plus),
                constraints: const BoxConstraints(),
                padding: EdgeInsets.zero,
              ),
            ],
          ),
        );
      },
    );
  }
}
  1. 修正InputFieldPrice同步逻辑
    移除build方法中直接给controller赋值的代码,通过initState做初始化赋值,通过didUpdateWidget生命周期监听父组件传入的价格变化,同步更新输入框内容:
class InputFieldPrice extends StatefulWidget {
  final double price;
  final TextStyle textStyle;

  const InputFieldPrice(
      {Key? key, required this.price, required this.textStyle})
      : super(key: key);

  @override
  State<InputFieldPrice> createState() => _InputFieldPriceState();
}

class _InputFieldPriceState extends State<InputFieldPrice> {
  final _formKey = GlobalKey<FormState>();
  final _priceController = TextEditingController();

  @override
  void initState() {
    super.initState();
    _priceController.text = widget.price.toStringAsFixed(2);
  }

  @override
  void didUpdateWidget(covariant InputFieldPrice oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (oldWidget.price != widget.price) {
      _priceController.text = widget.price.toStringAsFixed(2);
      // 同步光标位置到文本末尾
      _priceController.selection = TextSelection.fromPosition(
        TextPosition(offset: _priceController.text.length)
      );
    }
  }

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

  @override
  Widget build(BuildContext context) => SizedBox(
        width: 100,
        child: Form(
          key: _formKey,
          child: TextFormField(
            keyboardType: TextInputType.number,
            controller: _priceController,
            style: widget.textStyle,
            textAlign: TextAlign.center,
            decoration: InputDecoration(
              prefix: Text('JC', style: widget.textStyle),
              suffix: const Text(
                'KWh',
                style: constants.Styles.smallerBookTextStyleWhite,
              ),
              contentPadding: EdgeInsets.zero,
              border: InputBorder.none,
            ),
            inputFormatters: [
              LengthLimitingTextInputFormatter(4),
              FilteringTextInputFormatter.allow(RegExp("[0-9.]")),
            ],
          ),
        ),
      );
}
验证标准
  • 点击加减按钮时,Cubit会提交全新的状态实例,状态类props包含价格字段,Bloc能正确识别状态变化触发UI重建
  • 输入框内容和状态值实时同步,不会出现展示值和实际状态不一致、光标错位问题
  • 状态更新后计数器UI不会消失,价格数值随按钮点击正常增减
  • 价格减到0时不会出现负数,边界逻辑正常

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.01 20:45:52