在这里插入图片描述

家庭相册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比较紧凑,标题和选项视觉上是一组。

分类选项

WrapChoiceChip实现分类选择:

            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('相册创建成功')),
      );
    }
  }
}

调用AlbumProvideraddAlbum方法保存数据。

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()会遍历所有TextFormFieldvalidator

只要有一个返回非null的错误信息,整体验证就失败。

验证失败时错误信息会显示在对应输入框下方,红色文字很醒目。

用户体验优化

几个小细节提升体验:

decoration: const InputDecoration(
  labelText: '相册名称',
  hintText: '请输入相册名称',
  border: OutlineInputBorder(),
),

labelText在输入时会浮动到上方,不会挡住用户输入的内容。

hintText在没输入时显示,给用户提示该填什么。

边框样式让输入区域边界清晰,比下划线样式更正式。

小结

创建相册页面的核心是表单处理。

Form统一管理验证,TextEditingController控制输入,ChoiceChip处理单选。

保存时先验证再提交,成功后返回并提示用户。

这套模式可以复用到其他表单页面。


欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐