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

如何实现双向联动且可编辑的加密货币TextField?

加密货币APP双向联动输入框实现方案

需求说明

我正在开发一款加密货币移动端APP,需要实现两个可编辑的TextField(USDT和BTC),要求双向联动:

  • 假设BTC单价为15美元,USDT单价为1美元
  • 在BTC输入框输入1时,USDT输入框自动显示15
  • 在USDT输入框输入30时,BTC输入框自动显示2
  • 删除USDT输入框的0时,BTC输入框需实时更新为对应的小数数值

目前已实现USDT输入、BTC输出的单向联动,希望改为双向均可输入输出,现有代码如下:

现有USDTInput代码

import 'package:cryptx/Constants/app_colors.dart';
import 'package:cryptx/Providers/basic_providers.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

class USDTInput extends ConsumerWidget {
  const USDTInput({
    Key? key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 12.0),
      child: TextField(
        decoration: InputDecoration(
          icon: SizedBox(
              height: 30,
              child: Image.network(
                  "https://assets.coingecko.com/coins/images/325/small/Tether.png?1668148663")),
          hintText: "USDT",
          border: InputBorder.none,
        ),
        onChanged: (value) {
          ref
              .read(usdProvider.notifier)
              .update((state) => value != "" ? num.parse(value) : 0);
        },
        autocorrect: false,
        keyboardType: const TextInputType.numberWithOptions(decimal: true),
      ),
    );
  }
}

现有CoinOutput代码

import 'package:cryptx/Objects/coin.dart';
import 'package:cryptx/Providers/basic_providers.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

class CoinOutput extends ConsumerWidget {
  const CoinOutput({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    Coin coin = ref.watch(coinDetailProvider) as Coin;
    num usd = ref.watch(usdProvider);
    num amount = usd != 0 ? usd / coin.current_price : 0;

    //return Text(amount.toString());
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 12.0),
      child: TextField(
        decoration: InputDecoration(
          icon: SizedBox(height: 30, child: Image.network(coin.image)),
          hintText: "Coin",
          border: InputBorder.none,
        ),
        controller:
            TextEditingController(text: "$amount ${coin.symbol.toUpperCase()}"),
        readOnly: true,
        autocorrect: false,
        keyboardType: const TextInputType.numberWithOptions(decimal: true),
        onChanged: (value) {
          ref.watch(coin_usdProvider.notifier).update((state) =>
              value != "" ? num.parse(value) / coin.current_price : 0);
        },
      ),
    );
  }
}

修改方案

1. 调整状态管理(basic_providers.dart)

添加双向联动所需的状态和价格常量:

import 'package:flutter_riverpod/flutter_riverpod.dart';

// USDT金额状态
final usdtAmountProvider = StateProvider<num>((ref) => 0);
// BTC金额状态
final btcAmountProvider = StateProvider<num>((ref) => 0);
// 当前编辑的输入框标识,避免循环更新
final editingFieldProvider = StateProvider<String?>((ref) => null);
// BTC单价(实际可从API动态获取)
final btcPriceProvider = Provider<num>((ref) => 15);

2. 修改USDTInput为可联动组件

改为ConsumerStatefulWidget,使用控制器监听状态变化:

import 'package:cryptx/Constants/app_colors.dart';
import 'package:cryptx/Providers/basic_providers.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

class USDTInput extends ConsumerStatefulWidget {
  const USDTInput({Key? key}) : super(key: key);

  @override
  ConsumerState<USDTInput> createState() => _USDTInputState();
}

class _USDTInputState extends ConsumerState<USDTInput> {
  final TextEditingController _controller = TextEditingController();

  @override
  void initState() {
    super.initState();
    // 监听USDT状态变化,同步输入框内容(非当前编辑时)
    ref.listen<num>(usdtAmountProvider, (previous, next) {
      if (ref.read(editingFieldProvider) != 'usdt') {
        _controller.text = next.toString();
      }
    });
  }

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

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 12.0),
      child: TextField(
        controller: _controller,
        decoration: InputDecoration(
          icon: SizedBox(
              height: 30,
              child: Image.network(
                  "https://assets.coingecko.com/coins/images/325/small/Tether.png?1668148663")),
          hintText: "USDT",
          border: InputBorder.none,
        ),
        onChanged: (value) {
          final usdtAmount = value.isNotEmpty ? num.tryParse(value) ?? 0 : 0;
          ref.read(usdtAmountProvider.notifier).state = usdtAmount;
          // 标记当前编辑USDT,避免BTC输入框反向触发更新
          ref.read(editingFieldProvider.notifier).state = 'usdt';
          // 计算并更新BTC金额
          final btcPrice = ref.read(btcPriceProvider);
          ref.read(btcAmountProvider.notifier).state = usdtAmount / btcPrice;
          // 延迟重置编辑标识,确保状态更新完成
          Future.delayed(const Duration(milliseconds: 100), () {
            ref.read(editingFieldProvider.notifier).state = null;
          });
        },
        autocorrect: false,
        keyboardType: const TextInputType.numberWithOptions(decimal: true),
      ),
    );
  }
}

3. 修改CoinOutput为可编辑的BTCInput组件

同样改为ConsumerStatefulWidget,实现反向联动:

import 'package:cryptx/Objects/coin.dart';
import 'package:cryptx/Providers/basic_providers.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

class BTCInput extends ConsumerStatefulWidget {
  const BTCInput({super.key});

  @override
  ConsumerState<BTCInput> createState() => _BTCInputState();
}

class _BTCInputState extends ConsumerState<BTCInput> {
  final TextEditingController _controller = TextEditingController();

  @override
  void initState() {
    super.initState();
    // 监听BTC状态变化,同步输入框内容(非当前编辑时)
    ref.listen<num>(btcAmountProvider, (previous, next) {
      if (ref.read(editingFieldProvider) != 'btc') {
        _controller.text = next.toString();
      }
    });
  }

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

  @override
  Widget build(BuildContext context) {
    Coin coin = ref.watch(coinDetailProvider) as Coin;
    final btcPrice = ref.read(btcPriceProvider);

    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 12.0),
      child: TextField(
        controller: _controller,
        decoration: InputDecoration(
          icon: SizedBox(height: 30, child: Image.network(coin.image)),
          hintText: "BTC",
          border: InputBorder.none,
        ),
        onChanged: (value) {
          final btcAmount = value.isNotEmpty ? num.tryParse(value) ?? 0 : 0;
          ref.read(btcAmountProvider.notifier).state = btcAmount;
          // 标记当前编辑BTC,避免USDT输入框反向触发更新
          ref.read(editingFieldProvider.notifier).state = 'btc';
          // 计算并更新USDT金额
          ref.read(usdtAmountProvider.notifier).state = btcAmount * btcPrice;
          // 延迟重置编辑标识
          Future.delayed(const Duration(milliseconds: 100), () {
            ref.read(editingFieldProvider.notifier).state = null;
          });
        },
        autocorrect: false,
        keyboardType: const TextInputType.numberWithOptions(decimal: true),
      ),
    );
  }
}

关键修改点

  • 使用ConsumerStatefulWidget和TextEditingController,避免build时重复创建控制器导致光标异常
  • 添加editingFieldProvider标记当前编辑的输入框,防止双向更新时出现循环触发的问题
  • 用num.tryParse处理无效输入,避免解析崩溃
  • 通过延迟重置编辑标识,确保状态更新完成后再允许双向同步

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 16:40:39