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

Flutter下拉框旁显示破损X图标问题求助

问题:DropdownButtonFormField旁出现破损X形图标

我用Flutter开发的贷款计算器功能正常,但还款方式选择的DropdownButtonFormField下拉框旁边始终显示一个带X的破损占位符图标,Chrome浏览器和Android设备上都存在这个问题,功能不受影响但UI显示异常。

下拉框代码:

DropdownButtonFormField<String>(
  value: _selectedRepaymentOption,
  decoration: const InputDecoration(
    labelText: 'Repayment Method',
    border: OutlineInputBorder(),
  ),
  isExpanded: true,
  onChanged: (String? newValue) {
    setState(() {
      _selectedRepaymentOption = newValue!;
    });
  },
  items: _repaymentOptions.map((String option) {
    return DropdownMenuItem<String>(
      value: option,
      child: Text(option),
    );
  }).toList(),
),

已尝试的修复操作:

  • 移除decoration的const关键字排查构造函数问题
  • 检查并调整布局,排除溢出或尺寸适配问题

完整代码:

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

class LoanCalculator extends StatefulWidget {
  const LoanCalculator({Key? key}) : super(key: key);

  @override
  LoanCalculatorState createState() => LoanCalculatorState();
}

class LoanCalculatorState extends State<LoanCalculator> {
  final TextEditingController principalController = TextEditingController();
  final TextEditingController interestRateController = TextEditingController();
  final TextEditingController yearsController = TextEditingController();
  final TextEditingController gracePeriodController = TextEditingController(); // Grace period input field


 String _selectedRepaymentOption = 'Equal Payment'; // Default repayment method
  String _result = '';

  final List<String> _repaymentOptions = [
    'Equal Payment',
    'Deferred Equal Payment',
    'Bullet Payment'
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Loan Calculator'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          children: <Widget>[
            TextField(
              controller: principalController,
              decoration: const InputDecoration(
                labelText: 'Loan Amount (KRW)',
              ),
              keyboardType: TextInputType.number,
            ),
            const SizedBox(height: 20),
            TextField(
              controller: interestRateController,
              decoration: const InputDecoration(
                labelText: 'Annual Interest Rate (%)',
              ),
              keyboardType: TextInputType.number,
            ),
            const SizedBox(height: 20),
            TextField(
              controller: yearsController,
              decoration: const InputDecoration(
                labelText: 'Loan Term (years)',
              ),
              keyboardType: TextInputType.number,
            ),
            const SizedBox(height: 20),
            DropdownButtonFormField<String>(
              value: _selectedRepaymentOption,
              decoration: const InputDecoration(
                labelText: 'Repayment Method',
                border: OutlineInputBorder(),
              ),
              isExpanded: true,
              onChanged: (String? newValue) {
                setState(() {
                  _selectedRepaymentOption = newValue!;
                });
              },
              items: _repaymentOptions.map((String option) {
                return DropdownMenuItem<String>(
                  value: option,
                  child: Text(option),
                );
              }).toList(),
            ),
            const SizedBox(height: 20),

            // Show grace period input field when "Deferred Equal Payment" is selected
            if (_selectedRepaymentOption == 'Deferred Equal Payment') ...[
              TextField(
                controller: gracePeriodController,
                decoration: const InputDecoration(
                  labelText: 'Grace Period (years)',
                ),
                keyboardType: TextInputType.number,
              ),
              const SizedBox(height: 20),
            ],

            ElevatedButton(
              onPressed: _calculateLoan,
              child: const Text('Calculate'),
            ),
            const SizedBox(height: 20),
            Text(
              _result,
              style: const TextStyle(fontSize: 18),
            ),
          ],
        ),
      ),
    );
  }

  void _calculateLoan() {
    double principal = double.tryParse(principalController.text) ?? 0;
    double annualInterestRate = double.tryParse(interestRateController.text) ?? 0 / 100;
    int years = int.tryParse(yearsController.text) ?? 0;

    if (principal == 0 || annualInterestRate == 0 || years == 0) {
      setState(() {
        _result = 'Please enter valid values in all fields.';
      });
      return;
    }

    double result = 0;

        if (_selectedRepaymentOption == 'Equal Payment') {
          result = _equalPayment(principal, annualInterestRate, years);
        } else if (_selectedRepaymentOption == 'Deferred Equal Payment') {
          int gracePeriod = int.tryParse(gracePeriodController.text) ?? 0;
          result = _deferredEqualPayment(principal, annualInterestRate, years, gracePeriod);
        } else if (_selectedRepaymentOption == 'Bullet Payment') {
          result = _bulletPayment(principal, annualInterestRate, years);
        }

        setState(() {
          _result = 'Monthly Payment: ₩${result.toStringAsFixed(2)}';
        });
      }

      // Equal Payment calculation
      double _equalPayment(double principal, double annualInterestRate, int years) {
        double monthlyInterestRate = annualInterestRate / 12;
        int totalPayments = years * 12;
        return (principal * monthlyInterestRate) /
            (1 - pow(1 + monthlyInterestRate, -totalPayments));
      }

      // Deferred Equal Payment calculation
      double _deferredEqualPayment(
          double principal, double annualInterestRate, int years, int gracePeriod) {
        double monthlyInterestRate = annualInterestRate / 12;
        int totalPayments = (years - gracePeriod) * 12;
        return (principal * monthlyInterestRate) /
            (1 - pow(1 + monthlyInterestRate, -totalPayments));
      }

      // Bullet Payment calculation
      double _bulletPayment(
          double principal, double annualInterestRate, int years) {
        return principal * pow(1 + annualInterestRate, years);
      }
    }

成因与修复方法

这个破损X图标是DropdownButtonFormField默认自带的清除图标(clearIcon):当组件绑定了可变更的value时,Flutter会默认显示该图标用于清除选中值,但如果图标资源加载失败(比如Web端未引入Material Icons字体、或Android端图标资源缺失),就会显示为破损占位符。

修复方案1:直接隐藏清除图标

修改DropdownButtonFormField的decoration,显式设置clearIcon为空白组件即可隐藏:

DropdownButtonFormField<String>(
  value: _selectedRepaymentOption,
  decoration: const InputDecoration(
    labelText: 'Repayment Method',
    border: OutlineInputBorder(),
    // 添加这行隐藏清除图标
    clearIcon: SizedBox.shrink(),
  ),
  isExpanded: true,
  onChanged: (String? newValue) {
    setState(() {
      _selectedRepaymentOption = newValue!;
    });
  },
  items: _repaymentOptions.map((String option) {
    return DropdownMenuItem<String>(
      value: option,
      child: Text(option),
    );
  }).toList(),
),

修复方案2:确保Material Icons资源正常加载(通用)

  • Web端:在web/index.html的<head>标签中添加Material Icons字体链接:
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
  • Android端:检查android/app/build.gradle中是否包含Material Icons依赖,确保Flutter SDK资源正常同步。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 17:35:54