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

如何实现带下拉选择功能的TextField,展示SharedPreferences存储的历史输入

实现方案

要实现输入框获得焦点弹出历史输入下拉的效果,我们可以通过监听输入框焦点状态、本地存储历史记录、动态控制下拉列表显隐的方式完成,具体修改步骤如下:


第一步:补充状态变量与初始化逻辑

在你的State类中新增以下变量,并在initState中绑定焦点监听:

import 'dart:math';

// 账号输入框专属焦点监听
late final FocusNode usernameFocusNode;
// 用户名历史列表
List<String> _usernameHistory = [];
// 是否显示下拉列表
bool _showUsernameDropdown = false;

@override
void initState() {
  super.initState();
  usernameFocusNode = FocusNode();
  // 监听焦点变化
  usernameFocusNode.addListener(() async {
    if (usernameFocusNode.hasFocus) {
      // 获得焦点时读取历史记录
      final prefs = await SharedPreferences.getInstance();
      _usernameHistory = prefs.getStringList('username_history') ?? [];
      if(_usernameHistory.isNotEmpty) {
        setState(() => _showUsernameDropdown = true);
      }
    } else {
      // 失去焦点时隐藏下拉
      setState(() => _showUsernameDropdown = false);
    }
  });
}

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

第二步:修改账号输入区域的UI结构

将原来的账号TextFormField替换为带下拉的结构,绑定刚定义的usernameFocusNode:

Row(
  children: const [
    Expanded(
      child: Padding(
        padding: EdgeInsets.symmetric(horizontal: 16.0),
        child: Text('帳號', style: MyFonts.inputText),
      ),
    ),
  ],
),
// 用Stack包裹输入框和下拉列表,保证下拉悬浮在输入框下方
Stack(
  children: [
    Column(
      mainAxisSize: MainAxisSize.min,
      children: [
        TextFormField(
          // 绑定焦点监听
          focusNode: usernameFocusNode,
          validator: RequiredValidator(errorText: 'Username is required'),
          controller: usernameEdit,
          style: MyFonts.inputText,
          keyboardType: TextInputType.text,
          cursorColor: Colors.black,
          cursorHeight: 20.0,
          decoration: InputDecoration(
            isDense: true,
            hintText: '請輸入帳號',
            hintStyle: MyFonts.hintText,
            contentPadding: const EdgeInsets.symmetric(vertical: 12.0, horizontal: 16.0),
            focusedBorder: OutlineInputBorder(
              borderRadius: BorderRadius.circular(32.0),
              borderSide: const BorderSide(color: MyColors.theme),
            ),
            border: OutlineInputBorder(
              borderRadius: BorderRadius.circular(32.0),
            ),
          ),
          textInputAction: TextInputAction.next,
          onEditingComplete: () => focusNode.nextFocus(),
        ),
        // 占位高度,避免下拉列表显示时挤压下方控件
        if(_showUsernameDropdown) SizedBox(height: min(_usernameHistory.length * 50, 200) + 8),
      ],
    ),
    // 下拉列表
    if(_showUsernameDropdown)
      Positioned(
        top: 56, // 可自行调整数值匹配输入框高度
        left: 0,
        right: 0,
        child: Container(
          margin: const EdgeInsets.symmetric(horizontal: 16),
          decoration: BoxDecoration(
            color: Colors.white,
            borderRadius: BorderRadius.circular(16),
            boxShadow: const [
              BoxShadow(color: Colors.black12, blurRadius: 4, offset: Offset(0, 2))
            ]
          ),
          constraints: const BoxConstraints(maxHeight: 200),
          child: ListView.builder(
            padding: EdgeInsets.zero,
            shrinkWrap: true,
            itemCount: _usernameHistory.length,
            itemBuilder: (context, index) {
              final username = _usernameHistory[index];
              return ListTile(
                title: Text(username),
                onTap: () {
                  // 选中后填充到输入框
                  usernameEdit.text = username;
                  // 隐藏下拉
                  setState(() => _showUsernameDropdown = false);
                  // 跳转到密码输入框
                  focusNode.nextFocus();
                },
              );
            },
          ),
        ),
      )
  ],
),
const SizedBox(height: 16.0),

第三步:修改登录按钮的存储逻辑

登录时更新历史用户名列表,做去重处理后存回SharedPreferences:

onPressed: () async {
  var sharedPrefs = await SharedPreferences.getInstance();
  // 更新用户名历史记录
  List<String> newHistory = sharedPrefs.getStringList('username_history') ?? [];
  // 先移除已存在的相同用户名,避免重复
  newHistory.remove(usernameEdit.text);
  // 把最新的用户名插入到最前面
  newHistory.insert(0, usernameEdit.text);
  // 最多保留10条历史记录,可自行调整
  if(newHistory.length > 10) {
    newHistory = newHistory.sublist(0, 10);
  }
  // 存回本地
  await sharedPrefs.setStringList('username_history', newHistory);
  // 原有存储逻辑保留
  sharedPrefs.setString('username', usernameEdit.text);
  sharedPrefs.setString('pwd', pwdEdit.text);
  focusNode.unfocus();
},

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.23 23:45:10