如何实现带下拉选择功能的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
相关产品推荐
相关产品推荐

