flutter_for_openharmony家庭相册app实战+照片详情实现

点开一张照片,进入详情页,这是相册App里最基础也最重要的交互。

今天来聊聊照片详情页的实现思路和具体代码。

设计思考

照片详情页的核心是让用户专注于照片本身。

所以我把背景设成纯黑色,让照片内容更突出。

顶部保留必要的操作按钮,底部可以弹出照片信息。

页面基础结构

先看整体框架:

dart 复制代码
class PhotoDetailScreen extends StatelessWidget {
  final PhotoModel photo;

  const PhotoDetailScreen({super.key, required this.photo});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.black,
      appBar: AppBar(
        backgroundColor: Colors.black,
        iconTheme: const IconThemeData(color: Colors.white),

页面接收一个PhotoModel对象,包含照片的所有信息。

背景和AppBar都设成黑色,营造沉浸式的观看体验。

iconTheme设成白色,确保返回按钮在黑色背景上能看清。

收藏功能实现

收藏按钮放在右上角,需要实时显示当前状态:

dart 复制代码
        actions: [
          Consumer<AlbumProvider>(
            builder: (context, provider, _) {
              final currentPhoto = provider.photos.firstWhere((p) => p.id == photo.id);
              return IconButton(
                icon: Icon(
                  currentPhoto.isFavorite ? Icons.favorite : Icons.favorite_border,
                  color: currentPhoto.isFavorite ? Colors.red : Colors.white,
                ),
                onPressed: () => provider.toggleFavorite(photo.id),
              );
            },
          ),

Consumer监听AlbumProvider,这样收藏状态变化时图标会自动更新。

provider.photos里重新查找当前照片,确保拿到最新的isFavorite状态。

收藏后图标变成实心红色爱心,未收藏是空心白色,区分很明显。

信息按钮

点击信息按钮可以查看照片的详细信息:

dart 复制代码
          IconButton(
            icon: const Icon(Icons.info_outline, color: Colors.white),
            onPressed: () => _showPhotoInfo(context),
          ),

info_outline图标,轮廓风格和整体设计统一。

点击后调用_showPhotoInfo方法弹出底部面板。

更多操作菜单

删除这种危险操作放在菜单里,避免误触:

dart 复制代码
          PopupMenuButton<String>(
            iconColor: Colors.white,
            onSelected: (value) {
              if (value == 'delete') {
                _showDeleteDialog(context);
              }
            },
            itemBuilder: (context) => [
              const PopupMenuItem(value: 'delete', child: Text('删除照片')),
            ],
          ),
        ],
      ),

PopupMenuButton点击后展开菜单,目前只有删除一个选项。

后续可以在这里加分享、编辑等功能。

删除不是直接执行,而是先弹确认对话框。

照片展示区域

页面主体就是照片本身:

dart 复制代码
      body: Center(
        child: Container(
          color: _getColorForPhoto(photo.url),
          child: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Icon(Icons.photo, color: Colors.white.withOpacity(0.5), size: 100.sp),
                SizedBox(height: 16.h),
                Text(
                  photo.description,
                  style: TextStyle(color: Colors.white, fontSize: 18.sp),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }

这里用颜色块加图标来模拟照片显示,实际项目中换成Image组件加载真实图片。

照片描述显示在下方,字体稍大一些方便阅读。

整体居中显示,上下左右留有空间。

颜色生成方法

每张照片的背景色根据URL哈希值动态生成:

dart 复制代码
  Color _getColorForPhoto(String url) {
    final colors = [
      Colors.pink[300]!,
      Colors.purple[300]!,
      Colors.blue[300]!,
      Colors.teal[300]!,
      Colors.green[300]!,
      Colors.orange[300]!,
    ];
    return colors[url.hashCode % colors.length];
  }

准备了6种柔和的颜色,用哈希取模的方式选择。

同一张照片每次打开颜色都一样,不会随机变化。

颜色选的都是300色阶,不会太亮也不会太暗。

照片信息面板

点击信息按钮后从底部弹出详情面板:

dart 复制代码
  void _showPhotoInfo(BuildContext context) {
    final familyProvider = context.read<FamilyProvider>();
    final taggedMembers = familyProvider.getMembersByIds(photo.taggedMembers);

    showModalBottomSheet(
      context: context,
      backgroundColor: Colors.white,
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.vertical(top: Radius.circular(16.r)),
      ),

先从FamilyProvider获取照片中标记的家人信息。

showModalBottomSheet从底部弹出,用户可以下滑关闭。

顶部圆角让面板看起来更柔和。

dart 复制代码
      builder: (context) => Container(
        padding: EdgeInsets.all(24.w),
        child: Column(
          mainAxisSize: MainAxisSize.min,
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              '照片信息',
              style: TextStyle(fontSize: 18.sp, fontWeight: FontWeight.bold),
            ),
            SizedBox(height: 16.h),

mainAxisSize: MainAxisSize.min让面板高度自适应内容,不会占满整个屏幕。

标题加粗显示,和下面的信息项区分开。

信息项展示

展示照片的各种属性:

dart 复制代码
            _buildInfoRow('描述', photo.description),
            _buildInfoRow('拍摄时间', DateFormat('yyyy-MM-dd HH:mm').format(photo.createdAt)),
            if (photo.location != null) _buildInfoRow('地点', photo.location!),
            if (photo.tags.isNotEmpty) _buildInfoRow('标签', photo.tags.join(', ')),
            if (taggedMembers.isNotEmpty)
              _buildInfoRow('相关家人', taggedMembers.map((m) => m.name).join(', ')),
            SizedBox(height: 16.h),
          ],
        ),
      ),
    );
  }

描述和拍摄时间是必显示的,其他信息有才显示。

时间用DateFormat格式化成易读的格式。

标签和家人名字用逗号连接成字符串。

信息行组件

把信息行的构建抽成单独方法:

dart 复制代码
  Widget _buildInfoRow(String label, String value) {
    return Padding(
      padding: EdgeInsets.symmetric(vertical: 8.h),
      child: Row(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          SizedBox(
            width: 80.w,
            child: Text(
              label,
              style: TextStyle(color: Colors.grey, fontSize: 14.sp),
            ),
          ),

标签固定宽度80,这样所有行的值都能对齐。

标签用灰色,和右边的值形成对比。

crossAxisAlignment.start让多行文本顶部对齐。

dart 复制代码
          Expanded(
            child: Text(
              value,
              style: TextStyle(fontSize: 14.sp),
            ),
          ),
        ],
      ),
    );
  }

值用Expanded占据剩余空间,文本过长会自动换行。

上下间距8,信息项之间不会太挤。

删除确认对话框

删除操作需要二次确认:

dart 复制代码
  void _showDeleteDialog(BuildContext context) {
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: const Text('删除照片'),
        content: const Text('确定要删除这张照片吗?'),
        actions: [
          TextButton(
            onPressed: () => Navigator.pop(context),
            child: const Text('取消'),
          ),

AlertDialog弹出确认框,标题和内容说明清楚要做什么。

取消按钮只是关闭对话框,不做其他操作。

dart 复制代码
          TextButton(
            onPressed: () {
              context.read<AlbumProvider>().deletePhoto(photo.id);
              Navigator.pop(context);
              Navigator.pop(context);
            },
            child: const Text('删除', style: TextStyle(color: Colors.red)),
          ),
        ],
      ),
    );
  }
}

确认删除后调用providerdeletePhoto方法。

删除按钮用红色文字,提醒用户这是危险操作。

连续两次Navigator.pop,先关对话框再退出详情页。

数据模型配合

照片详情页依赖PhotoModel的数据结构:

dart 复制代码
class PhotoModel {
  final String id;
  final String albumId;
  final String url;
  final String description;
  final DateTime createdAt;
  final String? location;
  final List<String> tags;
  final List<String> taggedMembers;
  final bool isFavorite;

id用于唯一标识照片,删除和收藏操作都需要它。

taggedMembers存的是家人ID列表,需要通过FamilyProvider转换成名字。

isFavorite控制收藏状态,由AlbumProvider统一管理。

Provider方法

收藏和删除功能在AlbumProvider里实现:

dart 复制代码
void toggleFavorite(String photoId) {
  final index = _photos.indexWhere((p) => p.id == photoId);
  if (index != -1) {
    _photos[index] = _photos[index].copyWith(
      isFavorite: !_photos[index].isFavorite,
    );
    notifyListeners();
  }
}

找到对应照片,用copyWith创建新对象并切换收藏状态。

notifyListeners通知所有监听者更新UI。

dart 复制代码
void deletePhoto(String photoId) {
  _photos.removeWhere((p) => p.id == photoId);
  notifyListeners();
}

删除就是从列表里移除对应ID的照片。

实际项目中还需要同步删除服务器数据和本地缓存。

小结

照片详情页的实现不复杂,核心是沉浸式的黑色背景和清晰的操作入口。

收藏用Consumer实时监听状态,信息用底部面板展示,删除有二次确认。

这些细节加起来,用户体验就会好很多。


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

相关推荐
小哥Mark3 小时前
使用Flutter导航组件TabBar、AppBar等为鸿蒙应用程序构建完整的应用导航体系
flutter·harmonyos·鸿蒙
4311媒体网3 小时前
Libvio.link 页面布局与数据分布
java·php
fundroid3 小时前
Kotlin 泛型进阶:in、out 与 reified 实战
android·开发语言·kotlin
奋斗的小方3 小时前
01 一文读懂UML类图:核心概念与关系详解
java·uml
Android系统攻城狮3 小时前
Android tinyalsa深度解析之pcm_open调用流程与实战(一百零三)
android·pcm·tinyalsa·音频进阶·音频性能实战·android hal
2501_944448003 小时前
Flutter for OpenHarmony衣橱管家App实战:预算管理实现
前端·javascript·flutter
长安城没有风3 小时前
Java 高并发核心编程 ----- 线程池原理与实践(上)
java·juc
Remember_9933 小时前
Spring 核心原理深度解析:Bean 作用域、生命周期与 Spring Boot 自动配置
java·前端·spring boot·后端·spring·面试
2501_944448003 小时前
Flutter for OpenHarmony衣橱管家App实战:意见反馈功能实现
android·javascript·flutter