## 新增功能 - 实时搜索:支持书名和作者的模糊搜索,300ms防抖优化 - Provider状态管理:使用ChangeNotifier模式管理搜索状态 - 搜索页面:完整的搜索UI,包括空状态、搜索中、无结果和结果列表 - 智能交互:一键清空搜索、焦点管理、状态同步 ## 技术实现 - SearchProvider:防抖搜索、状态管理、多字段匹配 - SearchPage:StatefulWidget管理、控制器协调、生命周期优化 - 状态同步:TextEditingController与Provider协调,避免循环更新 - 用户体验:即时反馈、智能清空、页面状态重置 ## 代码质量 - Flutter分析零警告 - 完整的代码注释和文档 - 内存安全:正确的资源清理 - 性能优化:防抖机制和精确UI重建 ## 文档完善 - Provider状态管理学习指南 - 搜索功能开发实战总结 - 顶部导航组件开发总结 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
210 lines
5.6 KiB
Dart
210 lines
5.6 KiB
Dart
import 'package:flutter/material.dart';
|
||
import 'package:provider/provider.dart';
|
||
import '../providers/search_provider.dart';
|
||
import '../models/book.dart';
|
||
|
||
/// 搜索页面
|
||
///
|
||
/// 提供实时搜索功能,支持书名和作者的模糊搜索
|
||
/// 使用StatefulWidget管理输入框状态和焦点控制
|
||
class SearchPage extends StatefulWidget {
|
||
const SearchPage({super.key});
|
||
|
||
@override
|
||
State<SearchPage> createState() => _SearchPageState();
|
||
}
|
||
|
||
class _SearchPageState extends State<SearchPage> {
|
||
late TextEditingController _textController;
|
||
late FocusNode _focusNode;
|
||
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
_textController = TextEditingController();
|
||
_focusNode = FocusNode();
|
||
|
||
// 页面初始化时重置搜索状态
|
||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||
final searchProvider = Provider.of<SearchProvider>(context, listen: false);
|
||
searchProvider.clearSearch();
|
||
});
|
||
|
||
// 监听文本变化,同步到Provider
|
||
_textController.addListener(_onTextChanged);
|
||
}
|
||
|
||
@override
|
||
void dispose() {
|
||
_textController.removeListener(_onTextChanged);
|
||
_textController.dispose();
|
||
_focusNode.dispose();
|
||
super.dispose();
|
||
}
|
||
|
||
/// 文本变化处理
|
||
///
|
||
/// 同步TextField文本到SearchProvider,避免循环更新
|
||
void _onTextChanged() {
|
||
final currentQuery = Provider.of<SearchProvider>(context, listen: false).searchQuery;
|
||
if (_textController.text != currentQuery) {
|
||
Provider.of<SearchProvider>(context, listen: false).updateQuery(_textController.text);
|
||
}
|
||
}
|
||
|
||
/// 清空搜索
|
||
///
|
||
/// 同时清空Provider状态、TextField文本,并重新获取焦点
|
||
void _clearSearch() {
|
||
final searchProvider = Provider.of<SearchProvider>(context, listen: false);
|
||
searchProvider.clearSearch();
|
||
_textController.clear();
|
||
_focusNode.requestFocus();
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return Scaffold(
|
||
body: Column(
|
||
children: [
|
||
SafeArea(
|
||
bottom: false,
|
||
child: Padding(
|
||
padding: const EdgeInsets.all(16.0),
|
||
child: _buildSearchInput(context),
|
||
),
|
||
),
|
||
Expanded(
|
||
child: _buildSearchResults(context),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
/// 构建搜索输入框
|
||
///
|
||
/// 使用TextEditingController控制文本状态
|
||
/// 使用Consumer响应搜索状态变化,动态显示清空按钮
|
||
Widget _buildSearchInput(BuildContext context) {
|
||
return Consumer<SearchProvider>(
|
||
builder: (context, searchProvider, child) {
|
||
return TextField(
|
||
controller: _textController,
|
||
focusNode: _focusNode,
|
||
autofocus: true,
|
||
decoration: InputDecoration(
|
||
hintText: '输入书名、作者或关键词...',
|
||
prefixIcon: const Icon(Icons.search),
|
||
suffixIcon: searchProvider.hasQuery
|
||
? IconButton(
|
||
icon: const Icon(Icons.clear),
|
||
onPressed: _clearSearch,
|
||
)
|
||
: null,
|
||
),
|
||
);
|
||
},
|
||
);
|
||
}
|
||
|
||
/// 构建搜索结果区域
|
||
///
|
||
/// 根据搜索状态显示不同的UI:
|
||
/// - 搜索中:显示加载指示器
|
||
/// - 无搜索词:显示空状态提示
|
||
/// - 无结果:显示未找到提示
|
||
/// - 有结果:显示书籍列表
|
||
Widget _buildSearchResults(BuildContext context) {
|
||
return Consumer<SearchProvider>(
|
||
builder: (context, searchProvider, child) {
|
||
if (searchProvider.isSearching) {
|
||
return const Center(
|
||
child: CircularProgressIndicator(),
|
||
);
|
||
}
|
||
|
||
if (!searchProvider.hasQuery) {
|
||
return _buildEmptyState();
|
||
}
|
||
|
||
if (!searchProvider.hasResults) {
|
||
return _buildNoResults();
|
||
}
|
||
|
||
return _buildBookList(searchProvider.searchResults);
|
||
},
|
||
);
|
||
}
|
||
|
||
/// 构建空状态界面
|
||
///
|
||
/// 当用户未输入搜索词时显示的引导界面
|
||
Widget _buildEmptyState() {
|
||
return const Center(
|
||
child: Column(
|
||
mainAxisAlignment: MainAxisAlignment.center,
|
||
children: [
|
||
Icon(Icons.search, size: 64, color: Colors.grey),
|
||
SizedBox(height: 16),
|
||
Text(
|
||
'输入关键词开始搜索',
|
||
style: TextStyle(fontSize: 16, color: Colors.grey),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
/// 构建无结果界面
|
||
///
|
||
/// 当搜索没有找到匹配书籍时显示的提示界面
|
||
Widget _buildNoResults() {
|
||
return const Center(
|
||
child: Column(
|
||
mainAxisAlignment: MainAxisAlignment.center,
|
||
children: [
|
||
Icon(Icons.search_off, size: 64, color: Colors.grey),
|
||
SizedBox(height: 16),
|
||
Text(
|
||
'没有找到相关书籍',
|
||
style: TextStyle(fontSize: 16, color: Colors.grey),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
/// 构建书籍列表
|
||
///
|
||
/// [books] 搜索结果书籍列表
|
||
/// 使用ListView.builder实现高效的滚动性能
|
||
Widget _buildBookList(List<Book> books) {
|
||
return ListView.builder(
|
||
itemCount: books.length,
|
||
cacheExtent: 500,
|
||
itemBuilder: (context, index) {
|
||
final book = books[index];
|
||
return ListTile(
|
||
leading: Container(
|
||
width: 50,
|
||
height: 70,
|
||
decoration: BoxDecoration(
|
||
color: Colors.grey[300],
|
||
borderRadius: BorderRadius.circular(8),
|
||
),
|
||
child: const Icon(Icons.book, color: Colors.grey),
|
||
),
|
||
title: Text(book.title),
|
||
subtitle: Text(book.author ?? '未知作者'),
|
||
trailing: const Icon(Icons.chevron_right),
|
||
onTap: () {
|
||
// TODO: 实现跳转到书籍详情页面的功能
|
||
print('点击了书籍: ${book.title}');
|
||
},
|
||
);
|
||
},
|
||
);
|
||
}
|
||
}
|