flutter_for_openharmony家庭相册app实战+创建相册实现
本文介绍了Flutter家庭相册App中创建相册页面的实现方法。页面采用StatefulWidget管理表单状态,包含相册名称(必填)、描述(可选)、分类选择和私密设置等功能。通过TextFormField实现输入验证,ChoiceChip实现分类单选,SwitchListTile实现私密开关。页面布局使用Scaffold+ListView,顶部设有保存按钮。文章详细讲解了状态管理、表单验证、UI

家庭相册App里,创建相册是一个高频操作。
用户拍了新照片,想归类整理,第一步就是建个相册。
这篇来讲讲创建相册页面的实现。
功能规划
创建相册需要收集几个信息:相册名称、描述、分类、是否私密。
名称是必填的,其他都可选。
表单验证、状态管理、数据提交,这些是这个页面的核心。
StatefulWidget选择
这个页面有表单输入,需要管理多个状态,所以用StatefulWidget:
class CreateAlbumScreen extends StatefulWidget {
const CreateAlbumScreen({super.key});
State<CreateAlbumScreen> createState() => _CreateAlbumScreenState();
}
表单页面通常都用
StatefulWidget,因为输入框的内容、选中状态这些都需要本地管理。如果用
StatelessWidget配合Provider也行,但对于这种简单表单有点杀鸡用牛刀。
状态变量定义
在State类里定义需要的变量:
class _CreateAlbumScreenState extends State<CreateAlbumScreen> {
final _formKey = GlobalKey<FormState>();
final _nameController = TextEditingController();
final _descriptionController = TextEditingController();
String _selectedCategory = '其他';
bool _isPrivate = false;
final List<String> _categories = ['旅行', '生日', '节日', '日常', '纪念日', '其他'];
_formKey用于表单验证,调用validate()时会触发所有字段的校验。两个
TextEditingController分别控制名称和描述输入框。
_selectedCategory默认选"其他",_isPrivate默认不私密。分类列表写死在这里,实际项目可以从配置或接口获取。
资源释放
TextEditingController用完要释放:
void dispose() {
_nameController.dispose();
_descriptionController.dispose();
super.dispose();
}
这是Flutter的标准做法,防止内存泄漏。
页面销毁时
dispose会被调用,在这里清理资源。
页面结构
整体用Scaffold包裹,顶部有保存按钮:
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('创建相册'),
actions: [
TextButton(
onPressed: _saveAlbum,
child: const Text('保存', style: TextStyle(color: Colors.white)),
),
],
),
保存按钮放在
AppBar右侧,这是表单页面的常见布局。用
TextButton而不是IconButton,文字比图标更直观。点击后调用
_saveAlbum方法处理提交逻辑。
表单容器
用Form组件包裹所有输入字段:
body: Form(
key: _formKey,
child: ListView(
padding: EdgeInsets.all(16.w),
children: [
Form配合_formKey可以统一管理表单验证。内容用
ListView而不是Column,这样内容多了可以滚动。四周加16的内边距,不会贴着屏幕边缘。
名称输入框
相册名称是必填字段:
TextFormField(
controller: _nameController,
decoration: const InputDecoration(
labelText: '相册名称',
hintText: '请输入相册名称',
border: OutlineInputBorder(),
),
validator: (value) {
if (value == null || value.isEmpty) {
return '请输入相册名称';
}
return null;
},
),
SizedBox(height: 16.h),
TextFormField比普通TextField多了验证功能。
labelText是浮动标签,hintText是占位提示。
OutlineInputBorder让输入框有边框,视觉上更清晰。
validator返回错误信息时会显示在输入框下方。
描述输入框
描述是可选的,支持多行输入:
TextFormField(
controller: _descriptionController,
decoration: const InputDecoration(
labelText: '相册描述',
hintText: '请输入相册描述(可选)',
border: OutlineInputBorder(),
),
maxLines: 3,
),
SizedBox(height: 16.h),
maxLines: 3让输入框默认显示3行高度,适合输入较长的描述。没有
validator,因为这个字段不是必填的。提示文字里标注了"可选",用户一眼就知道可以跳过。
分类选择标题
分类选择区域先放个标题:
Text(
'选择分类',
style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.w500),
),
SizedBox(height: 8.h),
标题字体稍大一点,和下面的选项区分开。
间距8比较紧凑,标题和选项视觉上是一组。
分类选项
用Wrap和ChoiceChip实现分类选择:
Wrap(
spacing: 8.w,
runSpacing: 8.h,
children: _categories.map((category) {
final isSelected = category == _selectedCategory;
return ChoiceChip(
label: Text(category),
selected: isSelected,
onSelected: (_) => setState(() => _selectedCategory = category),
selectedColor: const Color(0xFFE91E63),
labelStyle: TextStyle(
color: isSelected ? Colors.white : Colors.black87,
),
);
}).toList(),
),
SizedBox(height: 16.h),
Wrap会自动换行,屏幕窄的时候分类选项不会挤在一起。
spacing是水平间距,runSpacing是行间距。
ChoiceChip自带单选逻辑,选中后背景变粉色。点击时用
setState更新_selectedCategory,触发UI刷新。
私密开关
用SwitchListTile实现开关选项:
SwitchListTile(
title: const Text('设为私密相册'),
subtitle: const Text('私密相册需要密码才能查看'),
value: _isPrivate,
onChanged: (value) => setState(() => _isPrivate = value),
activeColor: const Color(0xFFE91E63),
),
],
),
),
);
}
SwitchListTile把标题、副标题、开关整合在一起,布局很规整。
subtitle解释这个选项的作用,用户不用猜。
activeColor设成主题色,开关打开时是粉色。
保存逻辑
点击保存按钮后的处理:
void _saveAlbum() {
if (_formKey.currentState!.validate()) {
final album = AlbumModel(
name: _nameController.text,
coverUrl: 'new_album',
description: _descriptionController.text,
category: _selectedCategory,
isPrivate: _isPrivate,
);
先调用
validate()触发表单验证,所有字段都通过才继续。创建
AlbumModel对象,把表单数据填进去。
coverUrl暂时用固定值,实际项目中应该让用户选择封面图。
context.read<AlbumProvider>().addAlbum(album);
Navigator.pop(context);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('相册创建成功')),
);
}
}
}
调用
AlbumProvider的addAlbum方法保存数据。
Navigator.pop返回上一页,用户能看到新建的相册。
SnackBar在底部弹出提示,告诉用户操作成功了。
AlbumModel结构
创建相册时用到的数据模型:
class AlbumModel {
final String id;
final String name;
final String coverUrl;
final String description;
final String category;
final bool isPrivate;
final DateTime createdAt;
final int photoCount;
id在构造函数里自动生成,不需要用户填。
createdAt默认取当前时间。
photoCount新相册默认是0,添加照片后会更新。
Provider中的添加方法
AlbumProvider里的addAlbum实现:
void addAlbum(AlbumModel album) {
_albums.add(album);
notifyListeners();
}
把新相册加到列表末尾,然后通知监听者刷新。
实际项目中这里还要调接口同步到服务器。
表单验证细节
Form的验证机制值得多说几句:
if (_formKey.currentState!.validate()) {
// 验证通过
}
validate()会遍历所有TextFormField的validator。只要有一个返回非null的错误信息,整体验证就失败。
验证失败时错误信息会显示在对应输入框下方,红色文字很醒目。
用户体验优化
几个小细节提升体验:
decoration: const InputDecoration(
labelText: '相册名称',
hintText: '请输入相册名称',
border: OutlineInputBorder(),
),
labelText在输入时会浮动到上方,不会挡住用户输入的内容。
hintText在没输入时显示,给用户提示该填什么。边框样式让输入区域边界清晰,比下划线样式更正式。
小结
创建相册页面的核心是表单处理。
用Form统一管理验证,TextEditingController控制输入,ChoiceChip处理单选。
保存时先验证再提交,成功后返回并提示用户。
这套模式可以复用到其他表单页面。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐

所有评论(0)