如何在Flutter应用中添加搜索历史功能?
Flutter搜索历史功能实现方案
步骤1:添加本地存储依赖
在pubspec.yaml中引入shared_preferences用于轻量存储搜索历史:
dependencies: flutter: sdk: flutter shared_preferences: ^2.2.2
执行flutter pub get完成依赖安装。
步骤2:封装搜索历史操作逻辑
在页面类中添加以下方法,处理历史的增删查操作:
// 存储搜索历史的key const String _searchHistoryKey = 'search_history'; // 获取本地存储的搜索历史 Future<List<String>> getSearchHistory() async { final prefs = await SharedPreferences.getInstance(); return prefs.getStringList(_searchHistoryKey) ?? []; } // 添加搜索历史(自动去重,最多保留10条) Future<void> addSearchHistory(String query) async { if (query.trim().isEmpty) return; final prefs = await SharedPreferences.getInstance(); List<String> history = prefs.getStringList(_searchHistoryKey) ?? []; // 移除重复项,将新查询放在最顶部 history.remove(query); history.insert(0, query); // 限制历史数量,避免存储过多 if (history.length > 10) history.removeLast(); await prefs.setStringList(_searchHistoryKey, history); } // 删除单条搜索历史 Future<void> deleteSingleHistory(String query) async { final prefs = await SharedPreferences.getInstance(); List<String> history = prefs.getStringList(_searchHistoryKey) ?? []; history.remove(query); await prefs.setStringList(_searchHistoryKey, history); } // 清空所有搜索历史 Future<void> clearAllHistory() async { final prefs = await SharedPreferences.getInstance(); await prefs.remove(_searchHistoryKey); }
步骤3:改造搜索框与历史UI显示
修改原有搜索框,添加搜索提交逻辑,并在搜索框下方增加历史列表展示:
// 新增状态变量控制历史列表显示 bool _showHistory = true; @override Widget build(BuildContext context) { return Column( children: [ TextFormField( keyboardType: TextInputType.text, textInputAction: TextInputAction.search, controller: searchcontroller, focusNode: fousnode, // 输入内容时隐藏历史,清空时显示 onChanged: (query) { setState(() { _showHistory = query.isEmpty; }); }, // 用户提交搜索时添加历史 onSubmitted: (query) async { await addSearchHistory(query); setState(() { _showHistory = false; }); // 这里可触发搜索逻辑(比如调用下方的_performSearch) }, decoration: InputDecoration( hintText: "Search Product", border: OutlineInputBorder( borderRadius: BorderRadius.all(Radius.circular(11)), ), // 输入内容时显示清除按钮 suffixIcon: searchcontroller.text.isNotEmpty ? IconButton( icon: Icon(Icons.clear), onPressed: () { searchcontroller.clear(); setState(() { _showHistory = true; }); }, ) : null, ), ), // 搜索历史列表区域 if (_showHistory) FutureBuilder<List<String>>( future: getSearchHistory(), builder: (context, snapshot) { if (!snapshot.hasData || snapshot.data!.isEmpty) { return SizedBox(height: 0); } return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( '搜索历史', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500), ), TextButton( onPressed: () async { await clearAllHistory(); setState(() {}); }, child: Text('清空'), ), ], ), ), ListView.builder( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), itemCount: snapshot.data!.length, itemBuilder: (context, index) { final history = snapshot.data![index]; return ListTile( title: Text(history), trailing: IconButton( icon: Icon(Icons.close, size: 18), onPressed: () async { await deleteSingleHistory(history); setState(() {}); }, ), onTap: () { // 点击历史填充搜索框并触发搜索 searchcontroller.text = history; setState(() { _showHistory = false; }); _performSearch(history); }, ); }, ), ], ); }, ), // 原有搜索结果区域(修正了索引越界问题) Expanded( child: StreamBuilder<QuerySnapshot>( stream: _searchStream, // 替换为下方优化后的流 builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) { if (!snapshot.hasData) { return Center(child: CircularProgressIndicator()); } List<QueryDocumentSnapshot> data = snapshot.data!.docs; if (searchcontroller.text.isNotEmpty) { data = data.where((doc) => doc["product_name"] .toLowerCase() .contains(searchcontroller.text.toLowerCase())) .toList(); } return GridView.builder( itemCount: data.length, gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, crossAxisSpacing: 8, mainAxisSpacing: 8, ), itemBuilder: (context, index) { final doc = data[index]; return InkWell( onTap: () { Navigator.push(context, MaterialPageRoute(builder: (builder)=>detail( url: doc["url"], productName: doc["product_name"], productPrice: doc["product_price"], ))); }, child: Container( padding: EdgeInsets.all(8), child: Column( children: [ CircleAvatar( backgroundImage: NetworkImage(doc["url"]), radius: 30, ), SizedBox(height: 8), Text(doc["product_name"]), ], ), ), ); }, ); }, ), ), ], ); }
步骤4:优化搜索逻辑(可选但推荐)
原来的onChanged中Firestore查询未实际使用,可改为动态更新查询流,提升大数据量下的性能:
Stream<QuerySnapshot>? _searchStream; @override void initState() { super.initState(); // 初始加载全部商品 _searchStream = FirebaseFirestore.instance.collectionGroup("Add_product").snapshots(); } // 执行搜索的方法 void _performSearch(String query) { if (query.isEmpty) { _searchStream = FirebaseFirestore.instance.collectionGroup("Add_product").snapshots(); } else { final lowerQuery = query.toLowerCase(); _searchStream = FirebaseFirestore.instance .collectionGroup("Add_product") .where("product_name", isGreaterThanOrEqualTo: lowerQuery) .where("product_name", isLessThan: lowerQuery + 'z') .snapshots(); } setState(() {}); }
内容的提问来源于stack exchange,提问作者Muhammad Zohaib
相关产品推荐
相关产品推荐

