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

Flutter中三TextFormField仅填两项的表单验证与取值实现

Hey there! Let's get your Low Pass Filter form working exactly as you need it—only two fields editable at a time, real-time updates, and the button enabled only when the condition is met. Here's a polished implementation with explanations:

Key Improvements Made:

  • Real-time Field State Updates: Added listeners to each TextEditingController to trigger UI updates whenever any field's content changes.
  • Field Disable Logic: Calculates how many fields are filled, then disables the third one once two are non-empty.
  • Button Enable/Disable: The deprecated RaisedButton is replaced with ElevatedButton, and it's only enabled when exactly two fields are filled.
  • Deprecated API Fix: Swapped outdated WhitelistingTextInputFormatter with FilteringTextInputFormatter.digitsOnly.
  • Proper Cleanup: Added listener removal in dispose to prevent memory leaks.

Updated Code:

class LPFilterCalculator extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => _LPFilterCalculatorState();
}

class _LPFilterCalculatorState extends State<LPFilterCalculator> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: PersistentAppBar("Low Pass Filter").build(context),
      drawer: DrawerMenu(),
      body: Column(
        children: const <Widget>[
          LowPassInputForm(),
        ],
      ),
    );
  }
}

/// Inputform class for Calculators
class LowPassInputForm extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => _LowPassInputFormState();
}

class _LowPassInputFormState extends State<LowPassInputForm> {
  final _formKey = GlobalKey<FormState>();
  final resistanceTextController = TextEditingController();
  final capacitorTextController = TextEditingController();
  final frequencyTextController = TextEditingController();

  @override
  void initState() {
    super.initState();
    // Add listeners to trigger UI updates on text change
    resistanceTextController.addListener(_updateFormState);
    capacitorTextController.addListener(_updateFormState);
    frequencyTextController.addListener(_updateFormState);
  }

  void _updateFormState() {
    setState(() {});
  }

  // Count how many fields have non-empty values
  int get _filledFieldCount {
    int count = 0;
    if (resistanceTextController.text.isNotEmpty) count++;
    if (capacitorTextController.text.isNotEmpty) count++;
    if (frequencyTextController.text.isNotEmpty) count++;
    return count;
  }

  @override
  Widget build(BuildContext context) {
    final filledCount = _filledFieldCount;

    return Form(
      key: _formKey,
      child: Padding(
        padding: const EdgeInsets.all(8.0),
        child: Column(
          children: <Widget>[
            TextFormField(
              controller: resistanceTextController,
              inputFormatters: const <TextInputFormatter>[
                FilteringTextInputFormatter.digitsOnly,
              ],
              keyboardType: TextInputType.number,
              decoration: const InputDecoration(hintText: 'Enter resistance value'),
              // Disable if two other fields are already filled
              enabled: filledCount < 2 || 
                      (filledCount == 2 && 
                       (capacitorTextController.text.isEmpty || 
                        frequencyTextController.text.isEmpty)),
            ),
            const SizedBox(height: 8),
            TextFormField(
              controller: capacitorTextController,
              inputFormatters: const <TextInputFormatter>[
                FilteringTextInputFormatter.digitsOnly,
              ],
              keyboardType: TextInputType.number,
              decoration: const InputDecoration(hintText: 'Enter capacitor value'),
              enabled: filledCount < 2 || 
                      (filledCount == 2 && 
                       (resistanceTextController.text.isEmpty || 
                        frequencyTextController.text.isEmpty)),
            ),
            const SizedBox(height: 8),
            TextFormField(
              controller: frequencyTextController,
              inputFormatters: const <TextInputFormatter>[
                FilteringTextInputFormatter.digitsOnly,
              ],
              keyboardType: TextInputType.number,
              decoration: const InputDecoration(hintText: 'Enter desired cutoff frequency'),
              enabled: filledCount < 2 || 
                      (filledCount == 2 && 
                       (resistanceTextController.text.isEmpty || 
                        capacitorTextController.text.isEmpty)),
            ),
            const SizedBox(height: 16),
            ElevatedButton(
              elevation: 5.0,
              // Enable only when exactly two fields are filled
              onPressed: filledCount == 2 ? calculateLowPass : null,
              child: const Text('Calculate'),
            ),
          ],
        ),
      ),
    );
  }

  void calculateLowPass() {
    final resistance = double.tryParse(resistanceTextController.text);
    final capacitor = double.tryParse(capacitorTextController.text);
    final frequency = double.tryParse(frequencyTextController.text);

    // Handle each combination of filled fields to compute the missing value
    if (resistance != null && capacitor != null) {
      final computedFreq = 1 / (2 * 3.14159 * resistance * capacitor);
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Cutoff Frequency: ${computedFreq.toStringAsFixed(2)} Hz')),
      );
    } else if (resistance != null && frequency != null) {
      final computedCap = 1 / (2 * 3.14159 * resistance * frequency);
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Capacitor Value: ${computedCap.toStringAsFixed(6)} F')),
      );
    } else if (capacitor != null && frequency != null) {
      final computedRes = 1 / (2 * 3.14159 * capacitor * frequency);
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Resistance Value: ${computedRes.toStringAsFixed(2)} Ω')),
      );
    }
  }

  @override
  void dispose() {
    // Clean up listeners and controllers to avoid memory leaks
    resistanceTextController.removeListener(_updateFormState);
    capacitorTextController.removeListener(_updateFormState);
    frequencyTextController.removeListener(_updateFormState);
    
    resistanceTextController.dispose();
    capacitorTextController.dispose();
    frequencyTextController.dispose();
    super.dispose();
  }
}

Breakdown of Key Logic:

  1. Controller Listeners: In initState, we attach listeners to each text controller. Whenever the user types or deletes text, _updateFormState calls setState to refresh the UI immediately.
  2. Filled Field Count: The _filledFieldCount getter tracks how many fields have content, which drives both the field disable logic and button state.
  3. Field Enable Rules: Each field stays enabled unless exactly two other fields are filled. This ensures users can only edit two fields at a time.
  4. Button State: The calculate button is only clickable when exactly two fields are filled—setting onPressed to null automatically disables the button.
  5. Calculation Logic: The calculateLowPass method safely parses input values and computes the missing parameter based on which two fields are filled, then shows the result via a snackbar (you can replace this with your own logic).

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.07 22:22:48