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

Flutter中用GetX实现amount、rate、total字段动态计算及自定义键盘

Flutter GetX实现金额、汇率、总计的动态联动计算

GetX控制器核心实现

控制器负责处理响应式变量管理、联动计算逻辑以及自定义键盘输入处理,核心代码如下:

import 'package:get/get.dart';

class ForexController extends GetxController {
  // 响应式变量存储字段值,适配自定义键盘的字符串输入
  final RxString amount = ''.obs;
  final RxString rate = ''.obs;
  final RxString total = ''.obs;

  // 标记当前更新字段,避免循环计算触发死循环
  String? updatingField;
  // 记录当前焦点字段,用于自定义键盘输入定位
  final RxString currentFocusField = 'amount'.obs;

  @override
  void onInit() {
    super.onInit();
    // 监听金额/汇率变化,自动计算总计
    amount.listen((_) => updatingField != 'total' ? calculateTotal() : null);
    rate.listen((_) => updatingField != 'total' ? calculateTotal() : null);
    // 监听总计变化,自动计算金额
    total.listen((_) => updatingField != 'amount' ? calculateAmount() : null);
  }

  // 计算总计:Total = Amount * Rate
  void calculateTotal() {
    final num? amountNum = num.tryParse(amount.value);
    final num? rateNum = num.tryParse(rate.value);

    if (amountNum != null && rateNum != null && rateNum != 0) {
      updatingField = 'total';
      total.value = (amountNum * rateNum).toStringAsFixed(2); // 保留两位小数
      updatingField = null;
    } else {
      total.value = '';
    }
  }

  // 计算金额:Amount = Total / Rate
  void calculateAmount() {
    final num? totalNum = num.tryParse(total.value);
    final num? rateNum = num.tryParse(rate.value);

    if (totalNum != null && rateNum != null && rateNum != 0) {
      updatingField = 'amount';
      amount.value = (totalNum / rateNum).toStringAsFixed(2);
      updatingField = null;
    } else {
      amount.value = '';
    }
  }

  // 自定义键盘输入处理
  void onKeyboardInput(String input) {
    switch (input) {
      case 'delete':
        if (currentFocusField.value == 'amount') {
          amount.value = amount.value.isNotEmpty ? amount.value.substring(0, amount.value.length - 1) : '';
        } else if (currentFocusField.value == 'rate') {
          rate.value = rate.value.isNotEmpty ? rate.value.substring(0, rate.value.length - 1) : '';
        } else if (currentFocusField.value == 'total') {
          total.value = total.value.isNotEmpty ? total.value.substring(0, total.value.length - 1) : '';
        }
        break;
      case '.':
        // 限制每个字段仅能输入一个小数点
        if (currentFocusField.value == 'amount' && !amount.value.contains('.')) {
          amount.value += '.';
        } else if (currentFocusField.value == 'rate' && !rate.value.contains('.')) {
          rate.value += '.';
        } else if (currentFocusField.value == 'total' && !total.value.contains('.')) {
          total.value += '.';
        }
        break;
      default:
        // 数字输入处理
        if (currentFocusField.value == 'amount') {
          amount.value += input;
        } else if (currentFocusField.value == 'rate') {
          rate.value += input;
        } else if (currentFocusField.value == 'total') {
          total.value += input;
        }
    }
  }
}

ForexForm页面组件实现

页面负责UI渲染、绑定控制器变量以及自定义键盘交互:

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

class ForexForm extends StatelessWidget {
  final ForexController controller = Get.put(ForexController());

  ForexForm({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('外汇计算')),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          children: [
            _buildInputField(
              label: '金额',
              value: controller.amount,
              onTap: () => controller.currentFocusField.value = 'amount',
            ),
            const SizedBox(height: 16),
            _buildInputField(
              label: '汇率',
              value: controller.rate,
              onTap: () => controller.currentFocusField.value = 'rate',
            ),
            const SizedBox(height: 16),
            _buildInputField(
              label: '总计',
              value: controller.total,
              onTap: () => controller.currentFocusField.value = 'total',
            ),
            const SizedBox(height: 24),
            _buildCustomKeyboard(),
          ],
        ),
      ),
    );
  }

  // 构建输入字段容器
  Widget _buildInputField({
    required String label,
    required RxString value,
    required VoidCallback onTap,
  }) {
    return GestureDetector(
      onTap: onTap,
      child: Container(
        padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 16),
        decoration: BoxDecoration(
          border: Border.all(color: Colors.grey),
          borderRadius: BorderRadius.circular(8),
        ),
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: [
            Text(label),
            Obx(() => Text(value.value.isEmpty ? '0' : value.value)),
          ],
        ),
      ),
    );
  }

  // 构建自定义数字键盘
  Widget _buildCustomKeyboard() {
    return GridView.count(
      shrinkWrap: true,
      crossAxisCount: 3,
      childAspectRatio: 1.5,
      children: [
        _buildKeyboardButton('1'),
        _buildKeyboardButton('2'),
        _buildKeyboardButton('3'),
        _buildKeyboardButton('4'),
        _buildKeyboardButton('5'),
        _buildKeyboardButton('6'),
        _buildKeyboardButton('7'),
        _buildKeyboardButton('8'),
        _buildKeyboardButton('9'),
        _buildKeyboardButton('.'),
        _buildKeyboardButton('0'),
        _buildKeyboardButton('delete', icon: Icons.backspace),
      ],
    );
  }

  // 构建键盘按钮
  Widget _buildKeyboardButton(String text, {IconData? icon}) {
    return InkWell(
      onTap: () => controller.onKeyboardInput(text),
      child: Center(
        child: icon != null ? Icon(icon) : Text(text, style: const TextStyle(fontSize: 20)),
      ),
    );
  }
}

关键注意事项

  • 除数为0校验:计算时必须判断汇率不为0,避免抛出异常
  • 循环计算拦截:通过updatingField标记当前更新的字段,防止变量互相触发计算导致死循环
  • 输入合法性控制:自定义键盘限制小数点仅能输入一次,避免无效格式
  • 精度处理:使用toStringAsFixed(2)保留两位小数,符合金融场景需求
  • 空值友好处理:输入为空时显示0,提升用户体验

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 21:08:12