Compare commits
2 Commits
8b5fdaa36d
...
feb01c81ca
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
feb01c81ca | ||
|
|
f62a43823c |
@ -10,7 +10,8 @@
|
||||
"Bash(flutter run:*)",
|
||||
"Bash(flutter config:*)",
|
||||
"Bash(flutter:*)",
|
||||
"Bash(echo:*)"
|
||||
"Bash(echo:*)",
|
||||
"Bash(nc:*)"
|
||||
],
|
||||
"deny": [],
|
||||
"ask": []
|
||||
|
||||
41
CLAUDE.md
41
CLAUDE.md
@ -3,7 +3,7 @@
|
||||
## 📖 项目概述
|
||||
**项目名称:** Readful(读ful)
|
||||
**项目类型:** Flutter跨平台电子书阅读器应用
|
||||
**开发阶段:** UI基础架构完成,准备顶部导航开发 🚀
|
||||
**开发阶段:** 搜索功能开发完成,准备文件导入开发 📁
|
||||
**当前版本:** v1.0.0+1
|
||||
|
||||
## 🎯 项目目标
|
||||
@ -20,7 +20,7 @@
|
||||
- **Flutter SDK** >=3.0.0 - 跨平台UI框架
|
||||
- **Dart** - 编程语言(空值安全)
|
||||
- **Hive** - 轻量级NoSQL数据库(数据持久化)
|
||||
- **Provider/Riverpod** - 状态管理(待定)
|
||||
- **Provider** - 状态管理(实时搜索、状态同步)
|
||||
- **File Picker** - 文件选择和导入
|
||||
|
||||
### 项目结构
|
||||
@ -34,9 +34,13 @@ e:\readful\
|
||||
│ │ ├── main_navigation.dart # 底部Tab主导航
|
||||
│ │ ├── home_page.dart # 首页页面
|
||||
│ │ ├── library_page.dart # 书库页面
|
||||
│ │ ├── search_page.dart # 搜索页面
|
||||
│ │ ├── stats_page.dart # 统计页面
|
||||
│ │ └── profile_page.dart # 我的页面
|
||||
│ ├── components/ # 可复用组件目录(新增)
|
||||
│ ├── components/ # 可复用组件目录
|
||||
│ │ └── app_header.dart # 顶部导航组件
|
||||
│ ├── providers/ # 状态管理目录
|
||||
│ │ └── search_provider.dart # 搜索状态管理
|
||||
│ ├── models/ # 数据模型目录
|
||||
│ │ ├── book.dart # 电子书模型 + Hive注解
|
||||
│ │ ├── book.g.dart # Book TypeAdapter (自动生成)
|
||||
@ -199,16 +203,14 @@ Bookshelf (书架) ──┬── Book (书籍)
|
||||
## 📚 学习成果
|
||||
|
||||
### 已掌握的Flutter技能
|
||||
- ✅ **空值安全语法** - `?`、`!`、`required`、`??`
|
||||
- ✅ **不可变对象设计** - `final`字段、`copyWith`模式
|
||||
- ✅ **枚举类型使用** - 类型安全的选项管理
|
||||
- ✅ **序列化模式** - `toMap()`/`fromMap()`实现
|
||||
- ✅ **工厂构造函数** - 对象创建的最佳实践
|
||||
- ✅ **计算属性** - `get`方法的灵活应用
|
||||
- ✅ **对象比较** - `operator ==`和`hashCode`
|
||||
- ✅ **字符串处理** - 插值、正则表达式、格式化
|
||||
- ✅ **异步编程** - `async/await`、Future处理
|
||||
- ✅ **数据库操作** - Hive CRUD、TypeAdapter、Repository模式
|
||||
- ✅ **数据层技能**:空值安全、不可变对象、序列化、异步编程、Hive数据库
|
||||
- ✅ **UI布局技能**:Container、Row/Column、Expanded、SizedBox、SafeArea
|
||||
- ✅ **Material Design**:InkWell、Theme系统、ColorScheme、TextTheme
|
||||
- ✅ **组件设计**:StatelessWidget、参数化设计、回调函数、组件组合
|
||||
- ✅ **代码质量**:Flutter分析零警告、注释规范、const优化
|
||||
- ✅ **状态管理**:Provider模式、Consumer、响应式编程
|
||||
- ✅ **实时搜索**:防抖机制、文本控制器、焦点管理
|
||||
- ✅ **用户体验**:即时反馈、智能交互、状态同步
|
||||
|
||||
### 设计模式实践
|
||||
- **不可变对象模式** - 确保数据安全性
|
||||
@ -283,10 +285,10 @@ dev_dependencies:
|
||||
- **枚举类型:** 7个类型安全枚举
|
||||
- **数据字段:** 45+个字段
|
||||
- **CRUD方法:** 20+个数据操作方法
|
||||
- **UI组件:** 完整的Tab导航和主题系统
|
||||
- **UI组件:** Tab导航 + AppHeader组件 + 主题系统
|
||||
|
||||
### 文档产出
|
||||
- **学习文档:** 8篇详细教程文档
|
||||
- **学习文档:** 10篇详细教程文档
|
||||
- **文档总字数:** 25000+字
|
||||
- **代码示例:** 200+个
|
||||
- **知识点覆盖:** 完整覆盖Flutter数据建模、UI开发、主题系统
|
||||
@ -305,12 +307,15 @@ dev_dependencies:
|
||||
- **主题系统切换测试** ✅
|
||||
- **底部Tab导航测试** ✅
|
||||
- **Flutter代码质量分析** ✅
|
||||
- **SafeArea系统集成** ✅
|
||||
- **实时搜索功能测试** ✅
|
||||
- **状态管理集成测试** ✅
|
||||
|
||||
---
|
||||
|
||||
**项目状态:** 🎉 UI基础架构完成,准备顶部导航组件开发
|
||||
**下一里程碑:** 🚀 开始顶部导航组件开发(搜索栏+导入按钮)
|
||||
**项目状态:** 🎉 搜索功能开发完成,准备文件导入功能开发
|
||||
**下一里程碑:** 📁 开始文件导入和解析功能实现
|
||||
**当前技术债务:** ✅ 无,所有架构代码已完成并通过质量检查
|
||||
**代码质量:** 📊 企业级,遵循Flutter最佳实践和设计模式
|
||||
**代码质量:** 📊 企业级,Flutter分析零警告
|
||||
**产品定位:** 📱 类似微信读书的Material Design电子书阅读器
|
||||
**主题系统:** 🎨 完整的Material Design 3 + 暗夜模式支持
|
||||
380
learning_docs/09_Flutter布局系统详解.md
Normal file
380
learning_docs/09_Flutter布局系统详解.md
Normal file
@ -0,0 +1,380 @@
|
||||
# Flutter布局系统详解
|
||||
|
||||
## 📚 学习目标
|
||||
|
||||
深入理解Flutter的布局系统,掌握常用的布局组件和设计原则,能够创建响应式的、可复用的UI组件。
|
||||
|
||||
## 🎯 核心概念
|
||||
|
||||
### 1. Widget树结构
|
||||
Flutter应用由一棵Widget树构成,每个Widget都有父Widget和子Widget的关系:
|
||||
|
||||
```
|
||||
MaterialApp (根节点)
|
||||
└── Scaffold (页面骨架)
|
||||
└── AppHeader (自定义组件)
|
||||
├── Container (搜索栏容器)
|
||||
│ ├── Material (Material容器)
|
||||
│ └── InkWell (点击响应)
|
||||
└── Container (按钮容器)
|
||||
├── Material (Material容器)
|
||||
└── InkWell (点击响应)
|
||||
```
|
||||
|
||||
## 📦 基础布局组件详解
|
||||
|
||||
### Container - 万能容器
|
||||
Container是最常用的布局组件,可以设置尺寸、内边距、装饰等。
|
||||
|
||||
```dart
|
||||
Container({
|
||||
height: 60, // 固定高度
|
||||
width: 100, // 固定宽度
|
||||
padding: EdgeInsets.all(16), // 内边距
|
||||
margin: EdgeInsets.symmetric( // 外边距
|
||||
horizontal: 16,
|
||||
vertical: 8,
|
||||
),
|
||||
decoration: BoxDecoration( // 装饰属性
|
||||
color: Colors.blue,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: Colors.grey),
|
||||
),
|
||||
child: Text('内容'), // 子组件
|
||||
})
|
||||
```
|
||||
|
||||
**关键知识点:**
|
||||
- **尺寸控制**:width、height控制容器大小
|
||||
- **间距管理**:padding(内边距)、margin(外边距)
|
||||
- **装饰属性**:通过BoxDecoration设置背景色、边框、圆角等
|
||||
- **布局影响**:Container会尽可能小,除非有父容器约束
|
||||
|
||||
### Row - 水平布局
|
||||
Row将其子组件沿水平轴排列。
|
||||
|
||||
```dart
|
||||
Row(
|
||||
children: [
|
||||
Text('左侧'), // 固定宽度
|
||||
Expanded( // 占据剩余空间
|
||||
child: Text('中间'),
|
||||
),
|
||||
Container(width: 50), // 固定宽度
|
||||
Text('右侧'), // 固定宽度
|
||||
],
|
||||
)
|
||||
```
|
||||
|
||||
**关键知识点:**
|
||||
- **Expanded**:占据剩余可用空间
|
||||
- **Flexible**:按比例分配空间
|
||||
- **MainAxisAlignment**:主轴(水平)对齐方式
|
||||
- **CrossAxisAlignment**:交叉轴(垂直)对齐方式
|
||||
|
||||
### Column - 垂直布局
|
||||
Column将其子组件沿垂直轴排列,与Row功能相同但方向相反。
|
||||
|
||||
```dart
|
||||
Column(
|
||||
children: [
|
||||
Text('顶部'), // 固定高度
|
||||
Expanded( // 占据剩余垂直空间
|
||||
child: Text('中间内容'),
|
||||
),
|
||||
Container(height: 50), // 固定高度
|
||||
Text('底部'), // 固定高度
|
||||
],
|
||||
)
|
||||
```
|
||||
|
||||
### SizedBox - 空白间距
|
||||
用于创建固定大小的空白区域,常用于组件间距。
|
||||
|
||||
```dart
|
||||
SizedBox(width: 16), // 水平间距
|
||||
SizedBox(height: 8), // 垂直间距
|
||||
SizedBox.square(20), // 正方形空白
|
||||
```
|
||||
|
||||
## 🎨 Material Design组件
|
||||
|
||||
### Material - Material容器
|
||||
提供Material Design的基础功能,如墨水纹效果、颜色系统支持。
|
||||
|
||||
```dart
|
||||
Material(
|
||||
color: Colors.blue, // 背景色
|
||||
child: InkWell( // 点击响应
|
||||
onTap: () {},
|
||||
child: Text('按钮'),
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
### InkWell - 点击响应
|
||||
提供水波纹点击效果,是Material Design的推荐交互组件。
|
||||
|
||||
```dart
|
||||
InkWell(
|
||||
onTap: () { // 点击事件
|
||||
print('被点击了');
|
||||
},
|
||||
child: Container(
|
||||
width: 100,
|
||||
height: 50,
|
||||
color: Colors.red,
|
||||
child: Text('可点击区域'),
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
### Text - 文本显示
|
||||
用于显示文本,支持丰富的样式定制。
|
||||
|
||||
```dart
|
||||
Text(
|
||||
'Hello World',
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
## 🎯 AppHeader组件布局分析
|
||||
|
||||
### 整体布局结构
|
||||
|
||||
```dart
|
||||
return Scaffold(
|
||||
body: Column(
|
||||
children: [
|
||||
// 1. 固定高度的顶部导航
|
||||
AppHeader(...),
|
||||
|
||||
// 2. 占据剩余空间的内容区域
|
||||
Expanded(
|
||||
child: 页面内容,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
```
|
||||
|
||||
### AppHeader内部布局
|
||||
|
||||
```dart
|
||||
return Container(
|
||||
height: 60, // 固定高度
|
||||
child: Row(
|
||||
children: [
|
||||
// 1. 可选标题(条件渲染)
|
||||
if (title != null) ...[
|
||||
Text(title),
|
||||
SizedBox(width: 16),
|
||||
],
|
||||
|
||||
// 2. 搜索栏(占据主要空间)
|
||||
if (showSearchBar) ...[
|
||||
Expanded(child: 搜索栏),
|
||||
SizedBox(width: 12),
|
||||
],
|
||||
|
||||
// 3. 导入按钮(固定大小)
|
||||
导入按钮,
|
||||
],
|
||||
),
|
||||
);
|
||||
```
|
||||
|
||||
**布局设计要点:**
|
||||
1. **Row水平布局**:标题、搜索栏、按钮水平排列
|
||||
2. **Expanded搜索栏**:使用Expanded让搜索栏自适应宽度
|
||||
3. **条件渲染**:通过`if (condition) ...[...]`控制组件显示
|
||||
4. **固定间距**:使用SizedBox创建统一的组件间距
|
||||
|
||||
## 🎨 装饰与样式
|
||||
|
||||
### BoxDecoration详解
|
||||
BoxDecoration提供了丰富的装饰选项:
|
||||
|
||||
```dart
|
||||
BoxDecoration(
|
||||
// 背景色
|
||||
color: Colors.white,
|
||||
|
||||
// 渐变色
|
||||
gradient: LinearGradient(
|
||||
colors: [Colors.blue, Colors.purple],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
|
||||
// 边框
|
||||
border: Border(
|
||||
top: BorderSide(color: Colors.grey),
|
||||
bottom: BorderSide(color: Colors.grey),
|
||||
),
|
||||
|
||||
// 圆角
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
|
||||
// 阴影
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.1),
|
||||
blurRadius: 8,
|
||||
offset: Offset(0, 2),
|
||||
),
|
||||
],
|
||||
|
||||
// 背景图片
|
||||
image: DecorationImage(
|
||||
image: AssetImage('assets/image.png'),
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
### Theme.of(context) 主题系统
|
||||
|
||||
Flutter提供了完整的主题系统,支持亮色和暗色模式:
|
||||
|
||||
```dart
|
||||
// 获取当前主题配置
|
||||
ThemeData theme = Theme.of(context);
|
||||
|
||||
// 使用主题颜色
|
||||
Color primaryColor = theme.colorScheme.primary;
|
||||
Color surfaceColor = theme.colorScheme.surface;
|
||||
|
||||
// 使用文本样式
|
||||
TextStyle headlineStyle = theme.textTheme.headlineLarge;
|
||||
TextStyle bodyStyle = theme.textTheme.bodyLarge;
|
||||
|
||||
// 获取主题图标配色
|
||||
Color iconColor = theme.iconTheme.color;
|
||||
```
|
||||
|
||||
## 🎓 响应式设计原则
|
||||
|
||||
### 尺寸适配
|
||||
```dart
|
||||
// 使用MediaQuery获取屏幕信息
|
||||
double screenWidth = MediaQuery.of(context).size.width;
|
||||
|
||||
// 根据屏幕尺寸调整布局
|
||||
double padding = screenWidth > 600 ? 24.0 : 16.0;
|
||||
|
||||
// 使用LayoutBuilder根据父容器约束调整
|
||||
LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
if (constraints.maxWidth > 600) {
|
||||
return Row(...); // 平板布局
|
||||
} else {
|
||||
return Column(...); // 手机布局
|
||||
}
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
### Flex布局
|
||||
```dart
|
||||
Row(
|
||||
children: [
|
||||
Flexible(
|
||||
flex: 2, // 占据2份空间
|
||||
child: Container(color: Colors.red),
|
||||
),
|
||||
Flexible(
|
||||
flex: 1, // 占据1份空间
|
||||
child: Container(color: Colors.blue),
|
||||
),
|
||||
],
|
||||
)
|
||||
```
|
||||
|
||||
## 💡 最佳实践
|
||||
|
||||
### 1. 组件化设计
|
||||
- 将重复的UI抽取为可复用组件
|
||||
- 通过参数配置组件的行为
|
||||
- 保持组件的单一职责原则
|
||||
|
||||
### 2. 布局优化
|
||||
- 合理使用Expanded和Flexible
|
||||
- 避免不必要的嵌套
|
||||
- 使用约束而不是硬编码尺寸
|
||||
|
||||
### 3. 主题适配
|
||||
- 始终使用Theme.of(context)获取主题配置
|
||||
- 避免硬编码颜色值
|
||||
- 支持亮色/暗色模式自动切换
|
||||
|
||||
### 4. 交互设计
|
||||
- 使用InkWell提供Material Design交互
|
||||
- 设置合适的最小触摸目标(44x44dp)
|
||||
- 提供视觉反馈(颜色变化、动画等)
|
||||
|
||||
## 🔧 常见问题解决
|
||||
|
||||
### 问题1:Container大小不符合预期
|
||||
```dart
|
||||
// 错误:Container没有约束时会尽可能小
|
||||
Container(child: Text('文本')) // 可能显示不完整
|
||||
|
||||
// 正确:给Container设置明确的约束或让父容器约束
|
||||
Container(
|
||||
width: 200,
|
||||
height: 100,
|
||||
child: Text('文本'),
|
||||
)
|
||||
```
|
||||
|
||||
### 问题2:Row/Column中的布局异常
|
||||
```dart
|
||||
// 错误:Row中的子组件过大导致溢出
|
||||
Row(children: [Container(width: 500)]) // 可能溢出屏幕
|
||||
|
||||
// 正确:使用Expanded或Flexible自适应空间
|
||||
Row(
|
||||
children: [
|
||||
Expanded(child: Container()), // 自适应宽度
|
||||
Container(width: 100), // 固定宽度
|
||||
],
|
||||
)
|
||||
```
|
||||
|
||||
### 问题3:文本样式不生效
|
||||
```dart
|
||||
// 错误:在父Widget上设置文本样式不会被子组件继承
|
||||
Container(
|
||||
child: Text('文本'),
|
||||
style: TextStyle(fontSize: 16), // ❌ 这不会生效
|
||||
)
|
||||
|
||||
// 正确:直接在Text组件上设置样式
|
||||
Container(
|
||||
child: Text(
|
||||
'文本',
|
||||
style: TextStyle(fontSize: 16), // ✅ 这会生效
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**学习要点总结:**
|
||||
1. **理解Widget树结构**:掌握父子组件关系和约束传递
|
||||
2. **掌握基础布局组件**:Container、Row、Column、SizedBox
|
||||
3. **学会响应式设计**:使用Expanded、Flexible、MediaQuery
|
||||
4. **遵循Material Design**:使用Material、InkWell、Theme
|
||||
5. **组件化思维**:创建可复用的UI组件
|
||||
|
||||
---
|
||||
|
||||
*文档创建时间:2025年1月*
|
||||
*Flutter版本:>=3.0.0*
|
||||
296
learning_docs/10_顶部导航组件开发学习总结.md
Normal file
296
learning_docs/10_顶部导航组件开发学习总结.md
Normal file
@ -0,0 +1,296 @@
|
||||
# Readful 顶部导航组件开发学习总结
|
||||
|
||||
## 📚 学习目标达成
|
||||
|
||||
成功完成了AppHeader顶部导航组件的开发和用户交互优化,掌握了Flutter布局系统和Material Design组件的实践应用。
|
||||
|
||||
## 🎯 核心知识点掌握
|
||||
|
||||
### 1. Flutter布局组件系统实践
|
||||
|
||||
#### Container - 万能布局容器
|
||||
```dart
|
||||
Container({
|
||||
height: 60, // 固定高度
|
||||
padding: EdgeInsets.symmetric(...), // 内边距
|
||||
decoration: BoxDecoration(...), // 装饰属性
|
||||
child: Row(...), // 子组件
|
||||
})
|
||||
```
|
||||
|
||||
**学习要点:**
|
||||
- **尺寸控制**:width、height设置容器大小
|
||||
- **间距管理**:padding(内边距)控制子元素与容器的距离
|
||||
- **装饰系统**:通过BoxDecoration实现背景色、边框、圆角等视觉效果
|
||||
|
||||
#### Row - 水平布局核心组件
|
||||
```dart
|
||||
Row(
|
||||
children: [
|
||||
if (title != null) ...[ // 条件渲染
|
||||
Text(title),
|
||||
SizedBox(width: 16), // 固定间距
|
||||
],
|
||||
Expanded( // 占据剩余空间
|
||||
child: _buildSearchBar(context),
|
||||
),
|
||||
],
|
||||
)
|
||||
```
|
||||
|
||||
**学习要点:**
|
||||
- **条件渲染**:使用`if (condition) ...[...]`语法控制组件显示
|
||||
- **Expanded**:让子组件占据剩余可用空间,实现自适应布局
|
||||
- **SizedBox**:创建固定大小的空白间距,保持布局整洁
|
||||
|
||||
### 2. Material Design交互组件
|
||||
|
||||
#### InkWell - Material点击响应
|
||||
```dart
|
||||
InkWell(
|
||||
borderRadius: BorderRadius.circular(22), // 圆角效果
|
||||
onTap: onSearchPressed, // 点击回调
|
||||
child: Container(...), // 视觉内容
|
||||
)
|
||||
```
|
||||
|
||||
**学习要点:**
|
||||
- **水波纹效果**:提供Material Design的标准点击反馈
|
||||
- **圆角裁剪**:borderRadius与容器圆角保持一致
|
||||
- **回调处理**:通过VoidCallback实现事件传递
|
||||
|
||||
#### Material - Material容器
|
||||
```dart
|
||||
Material(
|
||||
color: Colors.transparent, // 透明背景
|
||||
child: InkWell(...), // 交互效果
|
||||
)
|
||||
```
|
||||
|
||||
**学习要点:**
|
||||
- **墨水纹效果**:Material是InkWell正常工作的必要条件
|
||||
- **性能优化**:透明颜色避免不必要的绘制开销
|
||||
|
||||
### 3. 主题系统集成
|
||||
|
||||
#### Theme.of(context) 动态主题访问
|
||||
```dart
|
||||
Theme.of(context).colorScheme.surface // 表面颜色
|
||||
Theme.of(context).colorScheme.primaryContainer // 主容器颜色
|
||||
Theme.of(context).colorScheme.onPrimaryContainer // 主容器上的内容色
|
||||
Theme.of(context).textTheme.bodyMedium // 文本样式
|
||||
```
|
||||
|
||||
**学习要点:**
|
||||
- **颜色系统**:使用ColorScheme实现亮色/暗色模式适配
|
||||
- **文本主题**:通过textTheme保持字体样式统一
|
||||
- **动态获取**:运行时从Widget树获取主题配置
|
||||
|
||||
### 4. 系统UI适配 - SafeArea实践
|
||||
|
||||
#### SafeArea组件使用
|
||||
```dart
|
||||
SafeArea(
|
||||
bottom: false, // 只处理顶部安全区域
|
||||
child: AppHeader(...), // 需要保护的内容
|
||||
)
|
||||
```
|
||||
|
||||
**学习要点:**
|
||||
- **系统UI避让**:自动避开状态栏、导航栏等系统UI
|
||||
- **精确控制**:通过bottom参数控制底部边距
|
||||
- **最佳实践**:包装顶部导航组件避免重叠问题
|
||||
|
||||
## 🏗️ 组件设计模式实践
|
||||
|
||||
### 1. 可复用组件设计
|
||||
```dart
|
||||
class AppHeader extends StatelessWidget {
|
||||
// 可选参数设计
|
||||
final String? title; // 标题可选
|
||||
final bool showSearchBar = true; // 默认显示搜索栏
|
||||
|
||||
// 回调函数设计
|
||||
final VoidCallback? onSearchPressed;
|
||||
final VoidCallback? onImportPressed;
|
||||
}
|
||||
```
|
||||
|
||||
**设计要点:**
|
||||
- **灵活配置**:通过可选参数适应不同页面需求
|
||||
- **事件传递**:使用回调函数实现与父组件的通信
|
||||
- **默认值设置**:合理的默认配置减少使用复杂度
|
||||
|
||||
### 2. 组件拆分与组合
|
||||
```dart
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
child: Row(
|
||||
children: [
|
||||
if (title != null) ...[title],
|
||||
if (showSearchBar) ...[searchBar],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSearchBar(BuildContext context) {
|
||||
return Container(
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(...), // 搜索图标
|
||||
Text(...), // 占位符文本
|
||||
_buildImportButton(...), // 导入按钮
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
**拆分原则:**
|
||||
- **单一职责**:每个方法负责构建特定功能区域
|
||||
- **可读性优先**:复杂UI拆分为多个小方法
|
||||
- **复用性考虑**:独立的子组件便于未来复用
|
||||
|
||||
## 🎨 用户体验优化
|
||||
|
||||
### 1. 交互设计改进
|
||||
**用户需求:** 将导入按钮放入搜索框内
|
||||
|
||||
**实现方案:**
|
||||
- 在搜索栏Row中添加`_buildImportButton()`
|
||||
- 导入按钮使用圆角Container设计
|
||||
- 保持与搜索栏的视觉协调性
|
||||
|
||||
### 2. 视觉层次设计
|
||||
```dart
|
||||
// 外层容器 - 背景
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
|
||||
// 搜索栏容器 - 次要背景
|
||||
color: Theme.of(context).colorScheme.surfaceVariant,
|
||||
|
||||
// 导入按钮容器 - 强调色
|
||||
color: Theme.of(context).colorScheme.primaryContainer,
|
||||
```
|
||||
|
||||
**层次要点:**
|
||||
- **背景层次**:surface → surfaceVariant → primaryContainer
|
||||
- **对比度控制**:确保文本在各个背景层上清晰可读
|
||||
- **主题一致性**:所有颜色来自主题系统,支持暗色模式
|
||||
|
||||
## 🔧 代码质量优化
|
||||
|
||||
### 1. 注释规范化
|
||||
- **文件级注释**:说明组件用途和主要功能
|
||||
- **类级注释**:描述组件职责和使用场景
|
||||
- **方法级注释**:解释方法功能和参数含义
|
||||
- **关键逻辑注释**:解释复杂业务逻辑
|
||||
|
||||
### 2. 代码分析通过
|
||||
```bash
|
||||
flutter analyze # 无警告、无错误
|
||||
```
|
||||
|
||||
**优化成果:**
|
||||
- ✅ prefer_single_quotes:使用单引号
|
||||
- ✅ unused_import:移除未使用的导入
|
||||
- ✅ 分析零警告:代码质量达到企业级标准
|
||||
|
||||
### 3. 组件参数设计
|
||||
```dart
|
||||
const AppHeader({
|
||||
super.key, // Key参数
|
||||
this.title, // 可选标题
|
||||
this.onSearchPressed, // 可选搜索回调
|
||||
this.onImportPressed, // 可选导入回调
|
||||
this.showSearchBar = true, // 默认显示搜索栏
|
||||
this.searchHint = '搜索书名或内容...', // 默认占位符
|
||||
});
|
||||
```
|
||||
|
||||
**设计原则:**
|
||||
- **参数可选性**:大部分参数为可选,提高组件易用性
|
||||
- **合理默认值**:提供符合使用场景的默认配置
|
||||
- **const构造函数**:支持编译时常量,提升性能
|
||||
|
||||
## 📈 技能掌握统计
|
||||
|
||||
### Flutter布局系统 - 100%掌握
|
||||
- ✅ **Container**:尺寸、内边距、装饰属性应用
|
||||
- ✅ **Row/Column**:水平/垂直布局、Expanded使用
|
||||
- ✅ **SizedBox**:固定间距创建、空白区域控制
|
||||
- ✅ **条件渲染**:if语句在Widget树中的使用
|
||||
|
||||
### Material Design组件 - 100%掌握
|
||||
- ✅ **InkWell**:点击响应、圆角效果、回调处理
|
||||
- ✅ **Material**:Material容器、墨水纹效果
|
||||
- ✅ **SafeArea**:系统UI避让、参数控制
|
||||
|
||||
### 主题系统集成 - 100%掌握
|
||||
- ✅ **Theme.of(context)**:动态主题访问、颜色系统
|
||||
- ✅ **ColorScheme**:亮色/暗色模式颜色适配
|
||||
- ✅ **TextTheme**:文本样式系统、字体配置
|
||||
|
||||
### 组件设计模式 - 100%掌握
|
||||
- ✅ **可复用设计**:参数配置、回调函数、默认值
|
||||
- ✅ **组件拆分**:单一职责、可读性优化
|
||||
- ✅ **组合模式**:复杂UI的分层构建
|
||||
|
||||
### 代码质量 - 企业级标准
|
||||
- ✅ **注释规范**:完整的文档注释体系
|
||||
- ✅ **代码分析**:Flutter lint零警告
|
||||
- ✅ **命名规范**:清晰的变量和方法命名
|
||||
- ✅ **性能优化**:const构造函数、合理的状态管理
|
||||
|
||||
## 💡 最佳实践总结
|
||||
|
||||
### 1. 布局设计原则
|
||||
- **约束驱动**:使用Expanded和Flexible实现自适应布局
|
||||
- **层次清晰**:通过Container嵌套创建清晰的视觉层次
|
||||
- **响应式设计**:考虑不同屏幕尺寸的适配
|
||||
|
||||
### 2. 组件开发原则
|
||||
- **单一职责**:每个组件专注特定功能
|
||||
- **参数化设计**:通过参数控制组件行为和外观
|
||||
- **组合优于继承**:使用Widget组合构建复杂界面
|
||||
|
||||
### 3. 用户体验原则
|
||||
- **视觉反馈**:InkWell提供即时的点击反馈
|
||||
- **主题一致性**:所有组件使用统一的颜色和字体系统
|
||||
- **系统适配**:SafeArea确保内容不被系统UI遮挡
|
||||
|
||||
### 4. 代码质量原则
|
||||
- **可读性优先**:清晰的方法命名和完整的注释
|
||||
- **性能考虑**:合理使用const和避免不必要的重建
|
||||
- **维护性**:模块化设计便于后续修改和扩展
|
||||
|
||||
## 🎯 下一步学习重点
|
||||
|
||||
### 即将进入的技术领域
|
||||
1. **状态管理进阶** - Provider/Riverpod状态管理模式
|
||||
2. **文件处理功能** - file_picker集成和文件导入
|
||||
3. **搜索功能实现** - 文本搜索和过滤逻辑
|
||||
4. **列表组件开发** - ListView和滚动优化
|
||||
5. **路由系统** - 页面导航和参数传递
|
||||
|
||||
### 功能开发规划
|
||||
1. **搜索页面开发** - 搜索框UI和搜索逻辑
|
||||
2. **文件导入功能** - EPUB/MOBI/TXT/PDF文件处理
|
||||
3. **书籍列表展示** - 可滚动的书籍卡片列表
|
||||
4. **用户交互完善** - 长按菜单、滑动操作
|
||||
5. **数据绑定集成** - Repository层数据与UI组件集成
|
||||
|
||||
---
|
||||
|
||||
**阶段状态:** 顶部导航组件开发完成 ✅
|
||||
**代码质量:** 企业级,Flutter分析零警告 📊
|
||||
**用户体验:** Material Design标准,支持主题切换 🎨
|
||||
**下一里程碑:** 搜索功能和文件导入开发 🔍
|
||||
|
||||
---
|
||||
|
||||
*文档创建时间:2025年1月*
|
||||
*Flutter版本:>=3.0.0*
|
||||
*Material Design:3.0*
|
||||
364
learning_docs/11_Provider状态管理学习指南.md
Normal file
364
learning_docs/11_Provider状态管理学习指南.md
Normal file
@ -0,0 +1,364 @@
|
||||
# Provider状态管理学习指南
|
||||
|
||||
## 📚 什么是Provider?
|
||||
|
||||
Provider是Flutter官方推荐的状态管理解决方案,它提供了一种简单、高效的方式来在应用中共享和传递数据。
|
||||
|
||||
### 核心概念
|
||||
- **Provider**: 数据提供者,封装状态数据
|
||||
- **Consumer**: 数据消费者,监听数据变化并重建UI
|
||||
- **ChangeNotifier**: 状态变化通知器,当数据改变时通知监听者
|
||||
|
||||
## 🎯 为什么需要状态管理?
|
||||
|
||||
### 问题场景
|
||||
```dart
|
||||
// 问题:多个页面需要共享搜索状态
|
||||
class HomePage extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
String searchText = ''; // 局部状态,无法跨页面共享
|
||||
return AppHeader(onSearchPressed: () {
|
||||
// 无法将搜索文本传递给其他页面
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class SearchPage extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// 无法获取首页的搜索状态
|
||||
return Text('搜索结果');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Provider解决方案
|
||||
```dart
|
||||
// 解决方案:使用Provider全局共享状态
|
||||
class SearchProvider extends ChangeNotifier {
|
||||
String _searchText = '';
|
||||
List<Book> _searchResults = [];
|
||||
|
||||
String get searchText => _searchText;
|
||||
List<Book> get searchResults => _searchResults;
|
||||
|
||||
void updateSearchText(String text) {
|
||||
_searchText = text;
|
||||
notifyListeners(); // 通知所有监听者重建
|
||||
}
|
||||
|
||||
void performSearch() {
|
||||
// 执行搜索逻辑
|
||||
_searchResults = _searchBooks(_searchText);
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 🔧 Provider使用步骤
|
||||
|
||||
### 步骤1: 添加依赖
|
||||
```yaml
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
provider: ^6.0.5 # 添加provider依赖
|
||||
```
|
||||
|
||||
### 步骤2: 创建状态管理类
|
||||
```dart
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
class SearchProvider extends ChangeNotifier {
|
||||
// 私有状态变量
|
||||
String _searchQuery = '';
|
||||
bool _isSearching = false;
|
||||
List<Book> _results = [];
|
||||
|
||||
// 公共getter方法
|
||||
String get searchQuery => _searchQuery;
|
||||
bool get isSearching => _isSearching;
|
||||
List<Book> get results => _results;
|
||||
|
||||
// 状态更新方法
|
||||
void updateQuery(String query) {
|
||||
_searchQuery = query;
|
||||
notifyListeners(); // 通知UI更新
|
||||
}
|
||||
|
||||
void searchBooks(String query) {
|
||||
_isSearching = true;
|
||||
notifyListeners();
|
||||
|
||||
// 模拟异步搜索
|
||||
Future.delayed(const Duration(milliseconds: 500), () {
|
||||
_results = _performSearch(query);
|
||||
_isSearching = false;
|
||||
notifyListeners();
|
||||
});
|
||||
}
|
||||
|
||||
void clearSearch() {
|
||||
_searchQuery = '';
|
||||
_results.clear();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// 私有搜索方法
|
||||
List<Book> _performSearch(String query) {
|
||||
if (query.isEmpty) return [];
|
||||
// 实际搜索逻辑
|
||||
return [];
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 步骤3: 在应用根部提供Provider
|
||||
```dart
|
||||
void main() {
|
||||
runApp(
|
||||
ChangeNotifierProvider(
|
||||
create: (context) => SearchProvider(),
|
||||
child: const ReadfulApp(),
|
||||
),
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### 步骤4: 在UI中消费Provider数据
|
||||
```dart
|
||||
// 方法1: 使用Consumer Widget
|
||||
Consumer<SearchProvider>(
|
||||
builder: (context, searchProvider, child) {
|
||||
return Text('搜索词: ${searchProvider.searchQuery}');
|
||||
},
|
||||
)
|
||||
|
||||
// 方法2: 使用Provider.of
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
final searchProvider = Provider.of<SearchProvider>(context, listen: false);
|
||||
searchProvider.updateQuery('Flutter');
|
||||
},
|
||||
child: Text('搜索'),
|
||||
)
|
||||
|
||||
// 方法3: 使用Selector(性能优化)
|
||||
Selector<SearchProvider, List<Book>>(
|
||||
selector: (context, provider) => provider.results,
|
||||
builder: (context, results, child) {
|
||||
return ListView.builder(
|
||||
itemCount: results.length,
|
||||
itemBuilder: (context, index) {
|
||||
return BookTile(book: results[index]);
|
||||
},
|
||||
);
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
## 🎨 实际应用:搜索功能实现
|
||||
|
||||
### 搜索状态管理
|
||||
```dart
|
||||
class SearchProvider extends ChangeNotifier {
|
||||
String _query = '';
|
||||
List<Book> _filteredBooks = [];
|
||||
List<Book> _allBooks = []; // 数据源
|
||||
|
||||
// Getters
|
||||
String get query => _query;
|
||||
List<Book> get filteredBooks => _filteredBooks;
|
||||
bool get hasResults => _filteredBooks.isNotEmpty;
|
||||
bool get isEmpty => _query.isEmpty;
|
||||
|
||||
// 初始化数据
|
||||
void loadBooks(List<Book> books) {
|
||||
_allBooks = books;
|
||||
_filteredBooks = books;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// 更新搜索查询
|
||||
void updateQuery(String newQuery) {
|
||||
_query = newQuery.toLowerCase();
|
||||
_filterBooks();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// 过滤书籍
|
||||
void _filterBooks() {
|
||||
if (_query.isEmpty) {
|
||||
_filteredBooks = List.from(_allBooks);
|
||||
} else {
|
||||
_filteredBooks = _allBooks.where((book) {
|
||||
return book.title.toLowerCase().contains(_query) ||
|
||||
book.author.toLowerCase().contains(_query) ||
|
||||
book.description.toLowerCase().contains(_query);
|
||||
}).toList();
|
||||
}
|
||||
}
|
||||
|
||||
// 清空搜索
|
||||
void clearSearch() {
|
||||
_query = '';
|
||||
_filteredBooks = List.from(_allBooks);
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 搜索页面UI
|
||||
```dart
|
||||
class SearchPage extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: Column(
|
||||
children: [
|
||||
// 搜索输入框
|
||||
_buildSearchBar(context),
|
||||
|
||||
// 搜索结果
|
||||
Expanded(
|
||||
child: Consumer<SearchProvider>(
|
||||
builder: (context, provider, child) {
|
||||
if (provider.isEmpty) {
|
||||
return _buildEmptyState();
|
||||
} else if (provider.hasResults) {
|
||||
return _buildResultsList(provider.filteredBooks);
|
||||
} else {
|
||||
return _buildNoResults();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSearchBar(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: TextField(
|
||||
onChanged: (value) {
|
||||
Provider.of<SearchProvider>(context, listen: false)
|
||||
.updateQuery(value);
|
||||
},
|
||||
decoration: InputDecoration(
|
||||
hintText: '搜索书名、作者或内容...',
|
||||
prefixIcon: const Icon(Icons.search),
|
||||
suffixIcon: Consumer<SearchProvider>(
|
||||
builder: (context, provider, child) {
|
||||
return provider.query.isNotEmpty
|
||||
? IconButton(
|
||||
icon: const Icon(Icons.clear),
|
||||
onPressed: () {
|
||||
Provider.of<SearchProvider>(context, listen: false)
|
||||
.clearSearch();
|
||||
},
|
||||
)
|
||||
: const SizedBox.shrink();
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 💡 Provider最佳实践
|
||||
|
||||
### 1. 性能优化
|
||||
```dart
|
||||
// ❌ 错误:每次重建都执行复杂计算
|
||||
Consumer<MyProvider>(
|
||||
builder: (context, provider, child) {
|
||||
return HeavyComputationWidget(data: provider.complexData);
|
||||
},
|
||||
)
|
||||
|
||||
// ✅ 正确:使用Selector优化
|
||||
Selector<MyProvider, ComputedData>(
|
||||
selector: (context, provider) => provider.computedData,
|
||||
builder: (context, data, child) {
|
||||
return HeavyComputationWidget(data: data);
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
### 2. 合理拆分Provider
|
||||
```dart
|
||||
// ❌ 错误:一个Provider管理太多状态
|
||||
class AppProvider extends ChangeNotifier {
|
||||
// 搜索状态
|
||||
String searchQuery = '';
|
||||
List<Book> searchResults = [];
|
||||
|
||||
// 用户状态
|
||||
User? currentUser;
|
||||
bool isLoggedIn = false;
|
||||
|
||||
// 主题状态
|
||||
bool isDarkMode = false;
|
||||
Color accentColor = Colors.blue;
|
||||
}
|
||||
|
||||
// ✅ 正确:按功能拆分Provider
|
||||
class SearchProvider extends ChangeNotifier { /* 搜索相关 */ }
|
||||
class UserProvider extends ChangeNotifier { /* 用户相关 */ }
|
||||
class ThemeProvider extends ChangeNotifier { /* 主题相关 */ }
|
||||
```
|
||||
|
||||
### 3. 错误处理
|
||||
```dart
|
||||
class SearchProvider extends ChangeNotifier {
|
||||
bool _isLoading = false;
|
||||
String? _errorMessage;
|
||||
|
||||
bool get isLoading => _isLoading;
|
||||
String? get errorMessage => _errorMessage;
|
||||
|
||||
Future<void> searchAsync(String query) async {
|
||||
_isLoading = true;
|
||||
_errorMessage = null;
|
||||
notifyListeners();
|
||||
|
||||
try {
|
||||
final results = await _apiService.search(query);
|
||||
_updateResults(results);
|
||||
} catch (e) {
|
||||
_errorMessage = e.toString();
|
||||
} finally {
|
||||
_isLoading = false;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 🎯 学习总结
|
||||
|
||||
### 关键概念
|
||||
- **ChangeNotifier**: 状态管理基类,提供notifyListeners()方法
|
||||
- **Consumer**: 监听Provider变化并自动重建UI
|
||||
- **Selector**: 性能优化的Consumer,只监听特定数据变化
|
||||
- **Provider.of**: 手动获取Provider实例的方式
|
||||
|
||||
### 使用场景
|
||||
- ✅ **全局状态**: 用户信息、主题设置
|
||||
- ✅ **页面间共享**: 搜索状态、购物车数据
|
||||
- ✅ **复杂数据**: 表单状态、异步加载状态
|
||||
|
||||
### 注意事项
|
||||
- 避免在build方法中创建Provider实例
|
||||
- 合理使用listen参数控制是否需要重建
|
||||
- 及时dispose防止内存泄漏
|
||||
- 使用Selector优化UI重建性能
|
||||
|
||||
---
|
||||
|
||||
*下一步:我们将使用Provider来实现Readful的搜索功能!*
|
||||
441
learning_docs/12_搜索功能开发实战总结.md
Normal file
441
learning_docs/12_搜索功能开发实战总结.md
Normal file
@ -0,0 +1,441 @@
|
||||
# Flutter搜索功能开发实战总结
|
||||
|
||||
## 📚 学习目标达成
|
||||
|
||||
成功完成了完整的搜索功能开发,掌握了Provider状态管理、实时搜索实现、用户体验优化等核心Flutter技能。
|
||||
|
||||
## 🎯 核心技术栈掌握
|
||||
|
||||
### 1. Provider状态管理系统 ⭐⭐⭐⭐⭐
|
||||
|
||||
#### ChangeNotifier核心概念
|
||||
```dart
|
||||
class SearchProvider extends ChangeNotifier {
|
||||
String _searchQuery = '';
|
||||
bool _isSearching = false;
|
||||
List<Book> _searchResults = [];
|
||||
|
||||
// 状态更新时通知所有监听者
|
||||
void updateQuery(String query) {
|
||||
_searchQuery = query;
|
||||
notifyListeners(); // 核心方法:通知UI重建
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**学习要点:**
|
||||
- **观察者模式**:状态变化自动通知UI更新
|
||||
- **单一职责**:Provider只负责状态管理,UI在Widget中处理
|
||||
- **生命周期管理**:在Widget销毁时自动清理监听关系
|
||||
|
||||
#### Consumer Widget使用
|
||||
```dart
|
||||
Consumer<SearchProvider>(
|
||||
builder: (context, searchProvider, child) {
|
||||
// searchProvider是Provider实例
|
||||
// 当notifyListeners()调用时,这里自动重建
|
||||
return Text('找到 ${searchProvider.searchResults.length} 本书');
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
**使用场景对比:**
|
||||
```dart
|
||||
// Consumer - 用于UI构建(推荐)
|
||||
Consumer<SearchProvider>(
|
||||
builder: (context, provider, child) {
|
||||
return ListView.builder(...); // 自动重建整个列表
|
||||
},
|
||||
)
|
||||
|
||||
// Provider.of - 用于事件处理(不监听变化)
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
Provider.of<SearchProvider>(context, listen: false)
|
||||
.clearSearch(); // 只调用方法,不重建UI
|
||||
},
|
||||
child: Icon(Icons.clear),
|
||||
)
|
||||
```
|
||||
|
||||
**设计优势:**
|
||||
- ✅ **精确控制**:只重建需要的Widget部分
|
||||
- ✅ **代码清晰**:明确表达UI依赖关系
|
||||
- ✅ **性能优化**:避免不必要的Widget重建
|
||||
|
||||
### 2. StatefulWidget状态管理 ⭐⭐⭐⭐⭐
|
||||
|
||||
#### 控制器和焦点管理
|
||||
```dart
|
||||
class _SearchPageState extends State<SearchPage> {
|
||||
late TextEditingController _textController;
|
||||
late FocusNode _focusNode;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_textController = TextEditingController(); // 输入框控制器
|
||||
_focusNode = FocusNode(); // 焦点管理器
|
||||
|
||||
// 监听文本变化,同步到Provider
|
||||
_textController.addListener(_onTextChanged);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_textController.removeListener(_onTextChanged); // 移除监听
|
||||
_textController.dispose(); // 销毁控制器
|
||||
_focusNode.dispose(); // 销毁焦点管理器
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**关键概念:**
|
||||
- **TextEditingController**:控制TextField的文本内容和选择状态
|
||||
- **FocusNode**:管理输入框的焦点获取和失去
|
||||
- **生命周期方法**:initState初始化,dispose清理资源
|
||||
- **监听器模式**:addListener/removeListener避免内存泄漏
|
||||
|
||||
#### 状态同步策略
|
||||
```dart
|
||||
void _onTextChanged() {
|
||||
// 避免循环更新:检查文本是否真的改变了
|
||||
if (_textController.text !=
|
||||
Provider.of<SearchProvider>(context, listen: false).searchQuery) {
|
||||
Provider.of<SearchProvider>(context, listen: false)
|
||||
.updateQuery(_textController.text);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**技术要点:**
|
||||
- **循环更新防护**:防止TextField ↔ Provider无限循环
|
||||
- **listen: false**:获取Provider实例但不监听变化
|
||||
- **状态一致性**:确保TextField和Provider状态始终同步
|
||||
|
||||
### 3. 实时搜索实现 ⭐⭐⭐⭐⭐
|
||||
|
||||
#### 防抖搜索优化
|
||||
```dart
|
||||
void _performSearch() {
|
||||
_isSearching = true;
|
||||
notifyListeners(); // 立即显示搜索中状态
|
||||
|
||||
// 防抖:300ms延迟执行搜索
|
||||
Future.delayed(const Duration(milliseconds: 300), () {
|
||||
if (_searchQuery.isEmpty) {
|
||||
_searchResults = List.from(_allBooks);
|
||||
} else {
|
||||
final query = _searchQuery.toLowerCase();
|
||||
_searchResults = _allBooks.where((book) {
|
||||
return book.title.toLowerCase().contains(query) ||
|
||||
(book.author?.toLowerCase().contains(query) ?? false);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
_isSearching = false;
|
||||
notifyListeners(); // 搜索完成,更新UI
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
**优化策略:**
|
||||
- **防抖机制**:避免每次输入都触发搜索,300ms延迟提升体验
|
||||
- **预处理优化**:查询词转换为小写,避免重复转换
|
||||
- **状态反馈**:立即显示搜索中状态,提供即时反馈
|
||||
- **异步执行**:不阻塞UI线程,保持界面响应性
|
||||
|
||||
#### 多字段搜索算法
|
||||
```dart
|
||||
// 支持书名和作者的模糊搜索
|
||||
_searchResults = _allBooks.where((book) {
|
||||
return book.title.toLowerCase().contains(query) || // 书名匹配
|
||||
(book.author?.toLowerCase().contains(query) ?? false); // 作者匹配(空值安全)
|
||||
}).toList();
|
||||
```
|
||||
|
||||
**算法特点:**
|
||||
- **多字段支持**:同时搜索书名和作者
|
||||
- **不区分大小写**:toLowerCase()提供大小写不敏感搜索
|
||||
- **空值安全**:?.和??操作符处理作者为null的情况
|
||||
- **模糊匹配**:使用contains进行部分匹配,提高搜索灵活性
|
||||
|
||||
### 4. 用户体验设计 ⭐⭐⭐⭐
|
||||
|
||||
#### 智能清空功能
|
||||
```dart
|
||||
void _clearSearch() {
|
||||
final searchProvider = Provider.of<SearchProvider>(context, listen: false);
|
||||
searchProvider.clearSearch(); // 清空搜索状态
|
||||
_textController.clear(); // 清空输入框文本
|
||||
_focusNode.requestFocus(); // 重新获取焦点
|
||||
}
|
||||
```
|
||||
|
||||
**UX设计要点:**
|
||||
- **一键清空**:点击清空按钮同时清理所有状态
|
||||
- **焦点回归**:清空后自动重新获取输入焦点
|
||||
- **状态一致性**:确保UI状态和数据状态完全同步
|
||||
|
||||
#### 页面状态管理
|
||||
```dart
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
// 页面进入时重置搜索状态
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
final searchProvider = Provider.of<SearchProvider>(context, listen: false);
|
||||
searchProvider.clearSearch();
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
**生命周期管理:**
|
||||
- **页面重置**:每次进入搜索页面都重置到初始状态
|
||||
- **延迟执行**:使用addPostFrameCallback确保Widget完全构建后再执行
|
||||
- **状态清洁**:避免残留状态影响用户体验
|
||||
|
||||
## 🏗️ 架构设计模式
|
||||
|
||||
### 1. Repository模式回顾
|
||||
```
|
||||
UI Layer (SearchPage)
|
||||
↓ (调用)
|
||||
Provider Layer (SearchProvider)
|
||||
↓ (使用)
|
||||
Service Layer (BookRepository)
|
||||
↓ (操作)
|
||||
Data Layer (Hive Database)
|
||||
```
|
||||
|
||||
**设计优势:**
|
||||
- **分层解耦**:每层职责明确,易于维护和测试
|
||||
- **依赖注入**:Provider通过构造函数或工厂模式创建
|
||||
- **数据流单向**:数据从下往上传递,事件从上往下传递
|
||||
|
||||
### 2. 观察者模式应用
|
||||
```dart
|
||||
// Provider作为被观察者
|
||||
class SearchProvider extends ChangeNotifier {
|
||||
void updateQuery(String query) {
|
||||
_searchQuery = query;
|
||||
_performSearch();
|
||||
}
|
||||
|
||||
void _performSearch() {
|
||||
// 搜索逻辑...
|
||||
notifyListeners(); // 通知所有观察者
|
||||
}
|
||||
}
|
||||
|
||||
// UI作为观察者
|
||||
Consumer<SearchProvider>(
|
||||
builder: (context, provider, child) {
|
||||
// 当notifyListeners()调用时自动重建
|
||||
return Text('结果数量: ${provider.searchResults.length}');
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
**模式优势:**
|
||||
- **松耦合**:UI不直接操作数据,只响应状态变化
|
||||
- **自动化**:状态变化时UI自动更新,无需手动刷新
|
||||
- **多观察者**:多个Widget可以同时监听同一个Provider
|
||||
|
||||
### 3. 防抖模式实现
|
||||
```dart
|
||||
Timer? _debounceTimer;
|
||||
|
||||
void onSearchChanged(String query) {
|
||||
_debounceTimer?.cancel(); // 取消之前的定时器
|
||||
|
||||
_debounceTimer = Timer(const Duration(milliseconds: 300), () {
|
||||
_performSearch(query); // 延迟执行搜索
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
**防抖原理:**
|
||||
- **延迟执行**:用户停止输入300ms后才执行搜索
|
||||
- **取消重置**:每次新输入都取消之前的定时器
|
||||
- **性能优化**:减少不必要的搜索请求
|
||||
|
||||
## 🔧 代码质量优化
|
||||
|
||||
### 1. 性能优化技巧
|
||||
|
||||
#### 避免循环更新
|
||||
```dart
|
||||
// ❌ 错误:会导致循环更新
|
||||
void _onTextChanged() {
|
||||
Provider.of<SearchProvider>(context, listen: false)
|
||||
.updateQuery(_textController.text); // 触发notifyListeners()
|
||||
}
|
||||
|
||||
// ✅ 正确:避免循环更新
|
||||
void _onTextChanged() {
|
||||
if (_textController.text !=
|
||||
Provider.of<SearchProvider>(context, listen: false).searchQuery) {
|
||||
Provider.of<SearchProvider>(context, listen: false)
|
||||
.updateQuery(_textController.text);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 减少重建范围
|
||||
```dart
|
||||
// ✅ 使用Consumer精确控制重建范围
|
||||
Consumer<SearchProvider>(
|
||||
builder: (context, provider, child) {
|
||||
return ListView.builder( // 只重建列表,不重建整个页面
|
||||
itemCount: provider.searchResults.length,
|
||||
itemBuilder: (context, index) {
|
||||
return BookTile(book: provider.searchResults[index]);
|
||||
},
|
||||
);
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
### 2. 内存管理最佳实践
|
||||
|
||||
#### 资源清理
|
||||
```dart
|
||||
@override
|
||||
void dispose() {
|
||||
// 按相反顺序清理资源
|
||||
_textController.removeListener(_onTextChanged);
|
||||
_textController.dispose();
|
||||
_focusNode.dispose();
|
||||
super.dispose(); // 最后调用父类dispose
|
||||
}
|
||||
```
|
||||
|
||||
#### 避免内存泄漏
|
||||
```dart
|
||||
// ✅ 正确:移除监听器
|
||||
_textController.addListener(_onTextChanged);
|
||||
|
||||
// ❌ 错误:忘记移除监听器
|
||||
_textController.addListener(_onTextChanged); // 没有removeListener
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_textController.removeListener(_onTextChanged); // 必须移除!
|
||||
_textController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 错误处理策略
|
||||
|
||||
#### 异步操作错误处理
|
||||
```dart
|
||||
Future<void> performAsyncSearch(String query) async {
|
||||
try {
|
||||
_isSearching = true;
|
||||
notifyListeners();
|
||||
|
||||
final results = await _searchService.search(query);
|
||||
_searchResults = results;
|
||||
|
||||
} catch (e) {
|
||||
_searchResults = []; // 搜索失败时返回空结果
|
||||
debugPrint('搜索失败: $e');
|
||||
} finally {
|
||||
_isSearching = false;
|
||||
notifyListeners(); // 无论成功失败都要更新UI
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**错误处理原则:**
|
||||
- **优雅降级**:搜索失败时显示友好的错误信息
|
||||
- **状态一致性**:确保UI状态始终正确
|
||||
- **日志记录**:记录错误信息便于调试
|
||||
|
||||
## 💡 学习成果统计
|
||||
|
||||
### 技术技能掌握
|
||||
- ✅ **Provider状态管理** - ChangeNotifier、Consumer、Provider.of
|
||||
- ✅ **StatefulWidget** - 控制器、焦点管理、生命周期
|
||||
- ✅ **实时搜索实现** - 防抖、多字段匹配、异步处理
|
||||
- ✅ **用户体验优化** - 即时反馈、智能清空、状态重置
|
||||
- ✅ **性能优化** - 防止循环更新、精确重建、内存管理
|
||||
|
||||
### 设计模式实践
|
||||
- ✅ **观察者模式** - Provider/Consumer响应式更新
|
||||
- ✅ **防抖模式** - 延迟搜索优化用户体验
|
||||
- ✅ **Repository模式** - 数据访问层抽象
|
||||
- ✅ **依赖注入** - Provider在应用根部的注入
|
||||
|
||||
### 代码质量指标
|
||||
- ✅ **零编译警告** - Flutter analyze通过
|
||||
- ✅ **内存安全** - 正确的资源清理和监听器管理
|
||||
- ✅ **性能优化** - 避免不必要的Widget重建
|
||||
- ✅ **错误处理** - 完善的异常捕获和状态管理
|
||||
|
||||
## 🎯 核心学习要点
|
||||
|
||||
### 1. 状态管理核心思想
|
||||
- **单一数据源**:状态集中管理,避免多处同步
|
||||
- **响应式更新**:状态变化自动驱动UI更新
|
||||
- **数据流向明确**:事件向下传递,数据向上流动
|
||||
|
||||
### 2. 用户体验设计原则
|
||||
- **即时反馈**:用户操作立即得到视觉反馈
|
||||
- **智能交互**:减少用户操作步骤,提升效率
|
||||
- **状态一致**:UI状态与数据状态保持同步
|
||||
|
||||
### 3. 性能优化策略
|
||||
- **防抖处理**:避免频繁的重复操作
|
||||
- **精确重建**:只重建需要更新的UI部分
|
||||
- **资源管理**:及时清理不需要的资源
|
||||
|
||||
### 4. 代码质量保证
|
||||
- **类型安全**:充分利用Dart的类型系统
|
||||
- **空值安全**:正确处理nullable类型
|
||||
- **内存安全**:防止内存泄漏和资源浪费
|
||||
|
||||
## 📈 技能成长路径
|
||||
|
||||
### 初级 → 中级跨越
|
||||
- **从静态页面到动态交互**:掌握StatefulWidget和状态管理
|
||||
- **从单页面到复杂应用**:学习Provider等状态管理方案
|
||||
- **从同步操作到异步处理**:掌握Future、async/await
|
||||
|
||||
### 中级 → 高级进阶
|
||||
- **性能优化技巧**:Widget重建优化、内存管理
|
||||
- **架构设计能力**:分层架构、设计模式应用
|
||||
- **用户体验设计**:交互设计、状态反馈优化
|
||||
|
||||
## 🚀 下一阶段学习方向
|
||||
|
||||
### 即将进入的技术领域
|
||||
1. **文件处理功能** - file_picker、文件格式解析
|
||||
2. **书籍详情页** - 路由参数、页面传值
|
||||
3. **书架管理** - 拖拽排序、分类管理
|
||||
4. **阅读器核心** - EPUB解析、翻页效果
|
||||
5. **数据同步** - 云同步、备份恢复
|
||||
|
||||
### 技术栈扩展
|
||||
- **文件处理** - file_picker、path_provider
|
||||
- **UI动画** - Hero动画、页面转场
|
||||
- **网络请求** - HTTP客户端、API集成
|
||||
- **数据持久化** - SQLite、云存储
|
||||
- **国际化** - 多语言支持、本地化
|
||||
|
||||
---
|
||||
|
||||
**阶段状态:** 搜索功能开发完成 ✅
|
||||
**代码质量:** 企业级,Flutter分析零警告 📊
|
||||
**用户体验:** 实时响应,智能交互 🎨
|
||||
**下一里程碑:** 文件导入功能开发 📁
|
||||
|
||||
---
|
||||
|
||||
*文档创建时间:2025年1月*
|
||||
*Flutter版本:>=3.0.0*
|
||||
*Provider版本:^6.0.5*
|
||||
159
lib/components/app_header.dart
Normal file
159
lib/components/app_header.dart
Normal file
@ -0,0 +1,159 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../pages/search_page.dart';
|
||||
|
||||
/// 应用顶部导航组件
|
||||
///
|
||||
/// 可复用的顶部导航栏组件,集成在搜索栏内的导入按钮。
|
||||
/// 提供统一的搜索和文件导入功能,支持主题自适应。
|
||||
class AppHeader extends StatelessWidget {
|
||||
/// 标题文本(可选),某些页面可以显示标题而非搜索栏
|
||||
final String? title;
|
||||
|
||||
/// 搜索按钮点击回调函数
|
||||
final VoidCallback? onSearchPressed;
|
||||
|
||||
/// 导入按钮点击回调函数
|
||||
final VoidCallback? onImportPressed;
|
||||
|
||||
/// 是否显示搜索栏(默认值为true)
|
||||
final bool showSearchBar;
|
||||
|
||||
/// 搜索栏占位符文本
|
||||
final String searchHint;
|
||||
|
||||
const AppHeader({
|
||||
super.key,
|
||||
this.title,
|
||||
this.onSearchPressed,
|
||||
this.onImportPressed,
|
||||
this.showSearchBar = true,
|
||||
this.searchHint = '搜索书名或内容...',
|
||||
});
|
||||
|
||||
/// 构建组件的UI结构
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
height: 60,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
color: Theme.of(context).colorScheme.outline.withOpacity(0.2),
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
// 标题区域(条件渲染)
|
||||
if (title != null) ...[
|
||||
Text(
|
||||
title!,
|
||||
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
],
|
||||
|
||||
// 搜索栏区域(占据剩余空间)
|
||||
if (showSearchBar) ...[
|
||||
Expanded(
|
||||
child: _buildSearchBar(context),
|
||||
),
|
||||
]
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 构建搜索栏组件,包含搜索图标、占位符文本和导入按钮
|
||||
Widget _buildSearchBar(BuildContext context) {
|
||||
return Container(
|
||||
height: 44,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surfaceVariant,
|
||||
borderRadius: BorderRadius.circular(22),
|
||||
border: Border.all(
|
||||
color: Theme.of(context).colorScheme.outline.withOpacity(0.3),
|
||||
),
|
||||
),
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(22),
|
||||
onTap: onSearchPressed ?? () => _navigateToSearchPage(context),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.search,
|
||||
size: 24,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
searchHint,
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSurfaceVariant
|
||||
.withOpacity(0.6),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
_buildImportButton(context),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 构建导入按钮组件
|
||||
Widget _buildImportButton(BuildContext context) {
|
||||
return Container(
|
||||
height: 32,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.primaryContainer,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
onTap: onImportPressed,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
'导入',
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 添加这个新方法
|
||||
void _navigateToSearchPage(BuildContext context) {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const SearchPage(),
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
import "package:flutter/material.dart";
|
||||
import "package:hive_flutter/hive_flutter.dart";
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
// import 'package:hive_flutter/hive_flutter.dart';
|
||||
|
||||
// 数据层服务
|
||||
import 'services/database_service.dart';
|
||||
@ -13,6 +14,9 @@ import 'models/bookshelf.dart';
|
||||
import 'models/bookmark.dart';
|
||||
import 'models/highlight.dart';
|
||||
|
||||
// providers
|
||||
import 'providers/search_provider.dart';
|
||||
|
||||
// UI层
|
||||
import 'pages/main_navigation.dart';
|
||||
import 'theme/app_theme.dart';
|
||||
@ -235,7 +239,6 @@ Future<void> runHighlightRepositoryTest() async {
|
||||
print('📚 添加后高亮数量: ${updatedHighlights.length}');
|
||||
|
||||
print('\n✅ HighlightRepository测试完成!所有功能正常工作\n');
|
||||
|
||||
} catch (e) {
|
||||
print('❌ HighlightRepository测试失败: $e\n');
|
||||
}
|
||||
@ -247,7 +250,7 @@ class MyApp extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: "Readful",
|
||||
title: 'Readful',
|
||||
theme: ThemeData(primarySwatch: Colors.blue),
|
||||
home: Scaffold(
|
||||
appBar: AppBar(title: const Text('readful 电子书阅读器')),
|
||||
@ -279,9 +282,88 @@ class MyApp extends StatelessWidget {
|
||||
class ReadfulApp extends StatelessWidget {
|
||||
const ReadfulApp({super.key});
|
||||
|
||||
// 创建测试数据
|
||||
List<Book> _createTestData() {
|
||||
return [
|
||||
Book(
|
||||
id: '1',
|
||||
title: 'Flutter开发实战',
|
||||
author: '张三',
|
||||
description: '一本关于Flutter移动开发的实战指南',
|
||||
format: BookFormat.epub,
|
||||
filePath: '/path/to/flutter.epub',
|
||||
fileSize: 2048000,
|
||||
addedDate: DateTime.now().subtract(const Duration(days: 30)),
|
||||
status:ReadingStatus.reading,
|
||||
totalPages: 100
|
||||
),
|
||||
Book(
|
||||
id: '2',
|
||||
title: 'Dart编程语言',
|
||||
author: '李四',
|
||||
description: '深入理解Dart编程语言的核心概念',
|
||||
format: BookFormat.epub,
|
||||
filePath: '/path/to/dart.epub',
|
||||
fileSize: 1536000,
|
||||
addedDate: DateTime.now().subtract(const Duration(days: 60)),
|
||||
status: ReadingStatus.completed,
|
||||
totalPages: 333
|
||||
),
|
||||
Book(
|
||||
id: '3',
|
||||
title: '移动应用设计指南',
|
||||
author: '王五',
|
||||
description: '移动应用UI/UX设计的最佳实践',
|
||||
format: BookFormat.pdf,
|
||||
filePath: '/path/to/design.pdf',
|
||||
fileSize: 5120000,
|
||||
addedDate: DateTime.now().subtract(const Duration(days: 10)),
|
||||
tags: ['设计', '移动应用', 'UI'],
|
||||
status: ReadingStatus.pending,
|
||||
totalPages: 1000
|
||||
),
|
||||
Book(
|
||||
id: '4',
|
||||
title: '人工智能简史',
|
||||
author: null, // 测试作者为null的情况
|
||||
description: '人工智能发展历程的详细介绍',
|
||||
format: BookFormat.mobi,
|
||||
filePath: '/path/to/ai.mobi',
|
||||
fileSize: 1024000,
|
||||
addedDate: DateTime.now().subtract(const Duration(days: 5)),
|
||||
status: ReadingStatus.reading,
|
||||
totalPages: 192
|
||||
),
|
||||
Book(
|
||||
id: '5',
|
||||
title: '算法与数据结构',
|
||||
author: '赵六',
|
||||
description: '程序员必备的算法和数据结构知识',
|
||||
format: BookFormat.epub,
|
||||
filePath: '/path/to/algorithm.epub',
|
||||
fileSize: 3072000,
|
||||
addedDate: DateTime.now().subtract(const Duration(days: 20)),
|
||||
status: ReadingStatus.completed,
|
||||
totalPages: 250
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
// 在MaterialApp外面包裹ChangeNotifierProvider
|
||||
return ChangeNotifierProvider(
|
||||
create: (context) {
|
||||
final searchProvider = SearchProvider();
|
||||
|
||||
// 加载测试数据
|
||||
final testData = _createTestData();
|
||||
searchProvider.loadBooks(testData);
|
||||
|
||||
|
||||
return searchProvider;
|
||||
},
|
||||
child: MaterialApp(
|
||||
title: 'Readful',
|
||||
debugShowCheckedModeBanner: false, // 隐藏调试横幅
|
||||
|
||||
@ -292,6 +374,7 @@ class ReadfulApp extends StatelessWidget {
|
||||
|
||||
// 主页面:底部Tab导航
|
||||
home: const MainNavigation(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../components/app_header.dart';
|
||||
|
||||
/// 首页页面
|
||||
///
|
||||
@ -14,12 +15,34 @@ class HomePage extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: Center(
|
||||
body: Column(
|
||||
children: [
|
||||
// 顶部导航组件
|
||||
SafeArea(
|
||||
bottom: false, // 只处理顶部安全区域
|
||||
child: AppHeader(
|
||||
// onSearchPressed: () {
|
||||
// // TODO: 实现搜索功能
|
||||
// print('搜索按钮被点击');
|
||||
// },
|
||||
onImportPressed: () {
|
||||
// TODO: 实现导入功能
|
||||
print('导入按钮被点击');
|
||||
},
|
||||
),
|
||||
),
|
||||
|
||||
// 页面内容区域
|
||||
Expanded(
|
||||
child: Center(
|
||||
child: Text(
|
||||
'首页',
|
||||
'首页内容区域',
|
||||
style: Theme.of(context).textTheme.headlineMedium,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,13 +1,46 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../components/app_header.dart';
|
||||
|
||||
/// 书库页面
|
||||
///
|
||||
/// 用户管理和浏览所有导入的电子书
|
||||
/// 功能规划:
|
||||
/// - 书籍列表展示
|
||||
/// - 分类筛选功能
|
||||
/// - 排序选项
|
||||
/// - 批量操作功能
|
||||
class LibraryPage extends StatelessWidget {
|
||||
const LibraryPage({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: Center(
|
||||
child: Text('书库', style: Theme.of(context).textTheme.headlineMedium),
|
||||
body: Column(
|
||||
children: [
|
||||
// 顶部导航组件
|
||||
SafeArea(
|
||||
bottom: false,
|
||||
child: AppHeader(
|
||||
showSearchBar: true,
|
||||
// onSearchPressed: () {
|
||||
// print('书库搜索按钮被点击');
|
||||
// },
|
||||
onImportPressed: () {
|
||||
print('书库导入按钮被点击');
|
||||
},
|
||||
),
|
||||
),
|
||||
|
||||
// 书籍列表内容区域
|
||||
Expanded(
|
||||
child: Center(
|
||||
child: Text(
|
||||
'书库列表区域',
|
||||
style: Theme.of(context).textTheme.headlineMedium,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
209
lib/pages/search_page.dart
Normal file
209
lib/pages/search_page.dart
Normal file
@ -0,0 +1,209 @@
|
||||
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}');
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
78
lib/providers/search_provider.dart
Normal file
78
lib/providers/search_provider.dart
Normal file
@ -0,0 +1,78 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import '../models/book.dart';
|
||||
|
||||
/// 搜索状态管理类
|
||||
///
|
||||
/// 提供实时搜索功能,支持书名和作者的模糊搜索
|
||||
/// 使用防抖机制优化性能,避免频繁搜索请求
|
||||
class SearchProvider with ChangeNotifier {
|
||||
String _searchQuery = '';
|
||||
bool _isSearching = false;
|
||||
List<Book> _searchResults = [];
|
||||
List<Book> _allBooks = [];
|
||||
|
||||
// 公开访问器
|
||||
String get searchQuery => _searchQuery;
|
||||
bool get isSearching => _isSearching;
|
||||
List<Book> get searchResults => _searchResults;
|
||||
bool get hasQuery => _searchQuery.isNotEmpty;
|
||||
bool get hasResults => _searchResults.isNotEmpty;
|
||||
|
||||
/// 初始化书籍数据源
|
||||
///
|
||||
/// [books] 完整的书籍列表,用于搜索过滤
|
||||
void loadBooks(List<Book> books) {
|
||||
_allBooks = books;
|
||||
_searchResults = books;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// 更新搜索关键词并触发搜索
|
||||
///
|
||||
/// [query] 新的搜索关键词,支持部分匹配
|
||||
void updateQuery(String query) {
|
||||
if (_searchQuery == query) return;
|
||||
_searchQuery = query;
|
||||
_performSearch();
|
||||
}
|
||||
|
||||
/// 清空搜索状态
|
||||
///
|
||||
/// 重置搜索关键词并显示所有书籍
|
||||
void clearSearch() {
|
||||
_searchQuery = '';
|
||||
_searchResults = _allBooks;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// 执行搜索逻辑
|
||||
///
|
||||
/// 使用300ms防抖延迟,避免频繁搜索请求
|
||||
/// 支持书名和作者的不区分大小写模糊匹配
|
||||
void _performSearch() {
|
||||
_isSearching = true;
|
||||
notifyListeners();
|
||||
|
||||
if (_searchQuery.isEmpty) {
|
||||
_searchResults = List.from(_allBooks);
|
||||
_isSearching = false;
|
||||
notifyListeners();
|
||||
return;
|
||||
}
|
||||
|
||||
Future.delayed(const Duration(milliseconds: 300), () {
|
||||
if (_searchQuery.isEmpty) {
|
||||
_searchResults = List.from(_allBooks);
|
||||
} else {
|
||||
final query = _searchQuery.toLowerCase();
|
||||
_searchResults = _allBooks.where((book) {
|
||||
return book.title.toLowerCase().contains(query) ||
|
||||
(book.author?.toLowerCase().contains(query) ?? false);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
_isSearching = false;
|
||||
notifyListeners();
|
||||
});
|
||||
}
|
||||
}
|
||||
60
pubspec.lock
60
pubspec.lock
@ -101,10 +101,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: built_value
|
||||
sha256: a30f0a0e38671e89a492c44d005b5545b830a961575bbd8336d42869ff71066d
|
||||
sha256: "426cf75afdb23aa74bd4e471704de3f9393f3c7b04c1e2d9c6f1073ae0b8b139"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "8.12.0"
|
||||
version: "8.12.1"
|
||||
characters:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -504,6 +504,62 @@ packages:
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
shared_preferences:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: shared_preferences
|
||||
sha256: d3bbe5553a986e83980916ded2f0b435ef2e1893dfaa29d5a7a790d0eca12180
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "2.2.3"
|
||||
shared_preferences_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_android
|
||||
sha256: "1ee8bf911094a1b592de7ab29add6f826a7331fb854273d55918693d5364a1f2"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "2.2.2"
|
||||
shared_preferences_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_foundation
|
||||
sha256: "671e7a931f55a08aa45be2a13fe7247f2a41237897df434b30d2012388191833"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "2.5.0"
|
||||
shared_preferences_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_linux
|
||||
sha256: "2ba0510d3017f91655b7543e9ee46d48619de2a2af38e5c790423f7007c7ccc1"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "2.4.0"
|
||||
shared_preferences_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_platform_interface
|
||||
sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
shared_preferences_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_web
|
||||
sha256: "7b15ffb9387ea3e237bb7a66b8a23d2147663d391cafc5c8f37b2e7b4bde5d21"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "2.2.2"
|
||||
shared_preferences_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_windows
|
||||
sha256: "398084b47b7f92110683cac45c6dc4aae853db47e470e5ddcd52cab7f7196ab2"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "2.4.0"
|
||||
shelf:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@ -44,6 +44,9 @@ dependencies:
|
||||
# 状态管理(后续使用)
|
||||
provider: ^6.0.5 # Provider状态管理
|
||||
|
||||
#存储
|
||||
shared_preferences: ^2.2.2 # 本地存储简单数据
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user