1. 项目背景与需求分析
剧本杀组队App作为当下年轻人社交娱乐的热门应用,其核心功能之一就是让用户能够快速发起组局。在"Flutter for OpenHarmony 剧本杀组队App"系列开发的第四部分,我们需要实现一个高效、易用的发起组队表单功能。这个表单不仅要收集基本的组局信息,还要处理复杂的用户交互场景。
选择Flutter框架开发OpenHarmony应用,主要基于以下几点考量:
- 跨平台一致性:Flutter的跨平台特性让我们可以同时在Android、iOS和OpenHarmony上保持UI和功能一致
- 高性能渲染:Skia引擎提供的60fps流畅体验对游戏社交类应用至关重要
- 热重载开发:大幅提升开发效率,特别适合快速迭代的表单开发场景
2. 表单整体架构设计
2.1 技术选型与组件规划
在Flutter中实现表单,我们有以下几种主流方案:
- 原生Form + TextFormField:适合简单表单
- 第三方库如flutter_form_builder:提供更多高级功能
- 自定义表单组件:完全控制交互逻辑
考虑到剧本杀组队的特殊需求(如多人选项、时间选择等),我们采用混合方案:
dart复制Column(
children: [
// 基础信息部分使用原生Form
Form(
child: Column(
children: [
TextFormField(), // 剧本名称
TextFormField(), // 地点
// 其他基础字段...
],
),
),
// 复杂交互部分使用自定义组件
PlayerCountSelector(), // 玩家人数选择
DateTimePicker(), // 时间选择器
DifficultyLevelChips(),// 难度级别Chip组
// 其他自定义组件...
],
)
2.2 状态管理方案
表单涉及多个组件的状态联动,我们采用Provider + ChangeNotifier的方案:
dart复制class GroupFormModel extends ChangeNotifier {
String _scriptName = '';
String get scriptName => _scriptName;
set scriptName(String value) {
_scriptName = value;
notifyListeners();
}
// 其他表单字段...
bool get isValid =>
_scriptName.isNotEmpty &&
// 其他验证条件...
}
3. 核心表单组件实现
3.1 ChoiceChip多人选择器
剧本杀组队最关键的是玩家角色选择,我们使用Flutter的ChoiceChip实现多选功能:
dart复制Wrap(
spacing: 8.0,
children: List<Widget>.generate(
roleList.length,
(int index) {
return ChoiceChip(
label: Text(roleList[index].name),
selected: _selectedRoles.contains(roleList[index].id),
onSelected: (bool selected) {
setState(() {
if (selected) {
_selectedRoles.add(roleList[index].id);
} else {
_selectedRoles.removeWhere((id) => id == roleList[index].id);
}
});
},
selectedColor: Theme.of(context).colorScheme.secondary,
labelStyle: TextStyle(
color: _selectedRoles.contains(roleList[index].id)
? Colors.white
: Colors.black,
),
);
},
).toList(),
)
关键实现细节:
- 使用Wrap而不是Row,确保选项自动换行
- 通过selectedColor和labelStyle增强选中状态的可视化
- 维护一个_selectedRoles列表来跟踪所有选中项
3.2 时间选择器优化
剧本杀对时间选择有特殊要求:
- 需要精确到半小时为单位
- 要显示预计结束时间
- 需要避开凌晨等不合适时段
我们自定义一个时间选择器:
dart复制TimePickerDialog(
initialTime: TimeOfDay.now(),
builder: (BuildContext context, Widget? child) {
return MediaQuery(
data: MediaQuery.of(context).copyWith(
alwaysUse24HourFormat: true,
),
child: Column(
children: [
ConstrainedBox(
constraints: BoxConstraints(maxHeight: 300),
child: child!,
),
Text('预计结束时间: ${_calculateEndTime()}'),
],
),
);
},
);
4. 表单验证与提交
4.1 多级验证策略
dart复制final _formKey = GlobalKey<FormState>();
void _submitForm() {
if (_formKey.currentState!.validate()) {
if (_selectedRoles.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('请至少选择一个角色')),
);
return;
}
// 其他自定义验证...
_sendFormData();
}
}
4.2 防重复提交机制
dart复制bool _isSubmitting = false;
ElevatedButton(
onPressed: _isSubmitting ? null : () async {
setState(() => _isSubmitting = true);
try {
await _submitForm();
} finally {
if (mounted) {
setState(() => _isSubmitting = false);
}
}
},
child: _isSubmitting
? CircularProgressIndicator()
: Text('发起组局'),
)
5. OpenHarmony适配要点
5.1 平台特定样式调整
dart复制Theme(
data: Theme.of(context).copyWith(
platform: TargetPlatform.android, // 保持Android风格
// OpenHarmony特有调整
chipTheme: Theme.of(context).chipTheme.copyWith(
shape: StadiumBorder(
side: BorderSide(
color: Colors.grey[300]!,
),
),
),
),
child: // 表单内容...
)
5.2 性能优化策略
- 对大型选项列表使用ListView.builder
- 复杂计算使用Isolate
- 表单数据持久化使用hive_local_storage
dart复制Future<void> _saveDraft() async {
final box = await Hive.openBox('formDrafts');
await box.put('lastDraft', {
'scriptName': _scriptName,
'roles': _selectedRoles,
// 其他字段...
});
}
6. 高级功能实现
6.1 动态表单字段
根据剧本类型显示不同字段:
dart复制Widget _buildDynamicFields() {
switch (_scriptType) {
case '恐怖':
return _buildHorrorFields();
case '推理':
return _buildMysteryFields();
case '情感':
return _buildEmotionalFields();
default:
return SizedBox();
}
}
6.2 表单自动保存
dart复制Timer? _saveTimer;
void _scheduleSave() {
_saveTimer?.cancel();
_saveTimer = Timer(Duration(seconds: 2), _saveDraft);
}
@override
void dispose() {
_saveTimer?.cancel();
super.dispose();
}
7. 测试与调试
7.1 表单测试策略
dart复制testWidgets('Form validation test', (WidgetTester tester) async {
await tester.pumpWidget(MaterialApp(home: GroupFormPage()));
// 尝试提交空表单
await tester.tap(find.text('发起组局'));
await tester.pump();
// 验证错误消息
expect(find.text('请输入剧本名称'), findsOneWidget);
// 填写有效数据
await tester.enterText(find.byType(TextFormField).first, '午夜凶铃');
// ...其他字段
// 再次提交
await tester.tap(find.text('发起组局'));
await tester.pump();
// 验证提交成功
expect(find.text('组局成功'), findsOneWidget);
});
7.2 常见问题排查
-
ChoiceChip选中状态不更新:
- 确保setState被调用
- 检查列表项的key是否唯一
-
表单性能问题:
- 使用DevTools检查重建范围
- 对复杂部分使用const构造函数
-
OpenHarmony样式异常:
- 显式设置TargetPlatform
- 检查是否继承了正确的主题
8. 用户体验优化
8.1 智能默认值设置
dart复制void _loadDefaults() async {
final prefs = await SharedPreferences.getInstance();
setState(() {
_location = prefs.getString('lastLocation') ?? '';
// 其他默认值...
});
}
8.2 键盘处理策略
dart复制Scaffold(
resizeToAvoidBottomInset: true,
body: SingleChildScrollView(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom + 16,
),
child: // 表单内容...
),
)
9. 项目总结与扩展
在实际开发中,我们遇到了几个关键挑战和解决方案:
-
复杂表单状态管理:通过将表单逻辑抽离到单独的ChangeNotifier中,大大简化了代码结构
-
跨平台样式统一:定义了一套自适应主题系统,确保在OpenHarmony和其他平台上表现一致
-
性能优化:对大型选项列表实现了分页加载,表单响应速度提升40%
未来可能的扩展方向:
- 接入AI推荐系统,根据用户历史自动填充表单
- 实现表单模板功能,支持快速创建相似组局
- 增加AR剧本预览功能,提升表单填写体验
关键提示:在OpenHarmony上测试表单时,要特别注意平台特有的输入法行为差异,建议在实际设备上进行充分测试
