flutter_for_openharmony家庭相册app实战+相册详情实现
本文介绍了Flutter相册详情页的实现,主要包含以下内容:1)页面接收相册对象参数展示基本信息;2)使用AppBar显示相册名称并提供编辑、删除功能;3)主体采用CustomScrollView布局,包含相册信息区和照片网格;4)根据有无照片显示不同状态,空相册展示提示信息,有照片则用3列网格展示;5)相册信息区显示分类标签和描述文本。整个页面采用响应式设计,通过Consumer监听数据变化实现

点击相册卡片后,需要进入相册详情页查看里面的照片。
今天来实现这个页面,主要是展示照片网格和相册信息。
页面接收参数
相册详情页需要接收一个相册对象:
class AlbumDetailScreen extends StatelessWidget {
final AlbumModel album;
const AlbumDetailScreen({super.key, required this.album});
通过构造函数接收
AlbumModel对象。这个对象包含相册的名称、分类、描述等信息。
从上一个页面跳转时传过来。
AlbumDetailScreen是个StatelessWidget,因为页面本身不需要管理状态。album参数用required标记,表示必须传入,不能为null。从相册列表页跳转时,会把点击的相册对象传过来。这个对象包含id、name、category、description、photoCount等字段,详情页会用这些信息展示相册的基本信息和照片列表。
页面基础结构
搭建页面框架:
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(album.name),
actions: [
IconButton(
icon: const Icon(Icons.edit),
onPressed: () => Navigator.push(
context,
MaterialPageRoute(builder: (_) => EditAlbumScreen(album: album)),
),
),
标题直接用相册名称,用户一眼就知道在看哪个相册。
右上角放编辑按钮,点击跳转到编辑页面。
编辑页面可以修改相册名称、分类、描述等。
AppBar的title用Text(album.name),动态显示相册名称。这样用户打开页面就知道当前在查看哪个相册,不需要额外的提示。actions数组包含两个按钮,第一个是编辑按钮,用edit图标。点击后跳转到EditAlbumScreen,把album对象传过去,编辑页面可以预填充当前的信息,用户修改后保存。
PopupMenuButton<String>(
onSelected: (value) {
if (value == 'delete') {
_showDeleteDialog(context);
}
},
itemBuilder: (context) => [
const PopupMenuItem(value: 'delete', child: Text('删除相册')),
],
),
],
),
还有一个更多菜单,用
PopupMenuButton实现。目前只有删除功能,点击后弹出确认对话框。
后续可以在这里加更多功能,比如分享、导出等。
PopupMenuButton是Material Design的弹出菜单组件,点击后显示一个下拉菜单。onSelected回调接收选中的值,这里判断如果是’delete’,调用_showDeleteDialog显示删除确认对话框。itemBuilder返回菜单项列表,目前只有一个"删除相册"选项。后续可以添加更多选项,比如"分享相册"、“导出照片”、"设为封面"等。这种设计把不常用的操作收起来,保持界面简洁。
主体内容区域
主体用CustomScrollView实现:
body: Consumer<AlbumProvider>(
builder: (context, provider, _) {
final photos = provider.getPhotosByAlbum(album.id);
return CustomScrollView(
slivers: [
SliverToBoxAdapter(
child: _buildAlbumInfo(),
),
用
Consumer监听数据变化,照片增删后会自动刷新。通过
getPhotosByAlbum方法获取当前相册的照片列表。第一个Sliver放相册信息区域。
Consumer监听AlbumProvider的变化,当照片被添加、删除或修改时,页面会自动刷新。provider.getPhotosByAlbum(album.id)查询当前相册的所有照片,返回List。CustomScrollView是一个高级的滚动组件,可以包含多个Sliver子组件,每个Sliver可以有不同的滚动行为。SliverToBoxAdapter把普通Widget包装成Sliver,这里用来显示相册信息区域。
照片网格展示
根据照片数量显示不同内容:
SliverPadding(
padding: EdgeInsets.all(16.w),
sliver: photos.isEmpty
? SliverToBoxAdapter(
child: Center(
child: Column(
children: [
SizedBox(height: 50.h),
Icon(Icons.photo_library, size: 64.sp, color: Colors.grey[300]),
SizedBox(height: 16.h),
Text('暂无照片', style: TextStyle(color: Colors.grey, fontSize: 16.sp)),
],
),
),
)
如果没有照片,显示空状态提示。
用一个大图标加文字,比空白页面友好。
提示用户这个相册还没有照片。
SliverPadding给Sliver添加内边距,这里设为16像素。如果photos列表为空,显示空状态。photo_library图标表示"照片库",尺寸64像素,颜色用浅灰色。下方显示"暂无照片"文字,也用灰色。这种空状态设计友好,让用户知道不是出错了,只是相册还没有照片。可以加个副标题,比如"点击右下角+号添加照片",引导用户下一步操作。
: SliverGrid(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
mainAxisSpacing: 4.w,
crossAxisSpacing: 4.w,
),
delegate: SliverChildBuilderDelegate(
(context, index) => _buildPhotoItem(context, photos[index]),
childCount: photos.length,
),
),
),
],
);
},
),
);
}
有照片时用三列网格展示。
间距设成4,照片之间紧凑一些,像相册的感觉。
用
SliverChildBuilderDelegate按需构建,性能更好。
SliverGrid是网格布局的Sliver版本,gridDelegate设置网格参数。crossAxisCount设为3,每行显示3张照片。mainAxisSpacing和crossAxisSpacing都设为4像素,照片之间有细小的间距,既能区分不同照片,又保持紧凑的布局,像真实相册的感觉。delegate用SliverChildBuilderDelegate,这是懒加载的构建方式,只构建可见区域的照片,性能好。childCount是照片数量,builder函数调用_buildPhotoItem构建每张照片。
相册信息区域
显示相册的分类和描述:
Widget _buildAlbumInfo() {
return Container(
padding: EdgeInsets.all(16.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 4.h),
decoration: BoxDecoration(
color: const Color(0xFFE91E63).withOpacity(0.1),
borderRadius: BorderRadius.circular(16.r),
),
child: Text(
album.category,
style: TextStyle(
color: const Color(0xFFE91E63),
fontSize: 12.sp,
),
),
),
分类用标签的形式显示,粉色背景加粉色文字。
圆角16,看起来像一个小标签。
比如"旅行"、"生日"这样的分类。
_buildAlbumInfo构建相册信息区域。Container用16像素的padding,Column垂直排列分类和描述。分类标签用Container包装Text,padding设为水平12像素、垂直4像素,让标签有一定的大小。decoration设置背景色和圆角,背景色用粉色10%透明度,圆角16像素,看起来像一个圆润的小标签。文字用粉色,字号12像素。这种标签设计在很多App中都能看到,视觉上很清晰。
SizedBox(width: 8.w),
Text(
'${album.photoCount}张照片',
style: TextStyle(
color: Colors.grey,
fontSize: 12.sp,
),
),
],
),
分类旁边显示照片数量。
用灰色小字,作为辅助信息。
分类标签右边用SizedBox添加8像素间距,然后显示照片数量。格式是"X张照片",字号12像素,颜色用灰色,作为辅助信息。这个数量是实时的,如果用户添加或删除照片,这里会自动更新。
if (album.description.isNotEmpty) ...[
SizedBox(height: 12.h),
Text(
album.description,
style: TextStyle(
fontSize: 14.sp,
color: Colors.grey[700],
),
),
],
],
),
);
}
如果有描述就显示,没有就不显示。
用
if加展开运算符...[]来实现条件渲染。描述用深灰色,比标签颜色深一点。
如果album.description不为空,显示描述文字。用if表达式加展开运算符…[],这是Dart的语法糖,可以在列表中条件性地添加元素。如果条件为true,[]里的元素会被展开到外层列表;如果为false,什么都不添加。这里如果有描述,添加SizedBox和Text两个Widget;如果没有描述,什么都不添加。描述文字用14像素字号,颜色用grey[700],比标签的灰色深一点,让描述更醒目。
照片项组件
每张照片的展示:
Widget _buildPhotoItem(BuildContext context, PhotoModel photo) {
return InkWell(
onTap: () => Navigator.push(
context,
MaterialPageRoute(builder: (_) => PhotoDetailScreen(photo: photo)),
),
child: Stack(
fit: StackFit.expand,
children: [
Container(
color: _getColorForPhoto(photo.url),
child: Center(
child: Icon(Icons.photo, color: Colors.white.withOpacity(0.5), size: 32.sp),
),
),
点击照片跳转到照片详情页。
用
Stack实现层叠布局,底层是照片,上层是收藏标记。这里用颜色块代替真实图片,加载更快。
_buildPhotoItem构建单张照片的Widget。InkWell提供点击效果,onTap跳转到PhotoDetailScreen查看照片详情。Stack实现层叠布局,fit设为expand让子组件填满整个空间。底层是Container,用颜色块模拟照片。_getColorForPhoto根据照片URL生成颜色,保证同一张照片的颜色固定。Center让图标居中显示,Icons.photo是照片图标,颜色用白色50%透明度,尺寸32像素,作为占位符。实际项目中应该用Image.network或Image.file加载真实图片。
if (photo.isFavorite)
Positioned(
top: 4.w,
right: 4.w,
child: Icon(Icons.favorite, color: Colors.red, size: 20.sp),
),
],
),
);
}
如果照片被收藏了,右上角显示红色爱心。
用
Positioned定位到右上角。这样用户一眼就能看出哪些是收藏的照片。
如果photo.isFavorite为true,在Stack上层添加收藏标记。Positioned定位到右上角,top和right都设为4像素。Icons.favorite是实心爱心图标,颜色用红色,尺寸20像素。这个标记是只读的,不能点击取消收藏,因为详情页的定位是展示,取消收藏的操作应该在照片详情页进行。这种设计让功能职责更清晰,避免用户误操作。
颜色生成方法
根据照片URL生成颜色:
Color _getColorForPhoto(String url) {
final colors = [
Colors.pink[200]!,
Colors.purple[200]!,
Colors.blue[200]!,
Colors.teal[200]!,
Colors.green[200]!,
Colors.orange[200]!,
];
return colors[url.hashCode % colors.length];
}
准备了6种浅色,用哈希值取模选择。
每张照片颜色不同,但同一张照片每次打开颜色固定。
浅色系看起来比较柔和。
_getColorForPhoto方法生成照片的占位颜色。colors数组包含6种颜色,都是200色阶,比较淡,作为背景不会太抢眼。url.hashCode获取字符串的哈希值,是个整数。对colors.length取模,结果在0到5之间,用作数组索引。这样同一个URL总是得到相同的颜色,不同URL的颜色分布比较均匀。哈希算法保证了颜色的随机性和一致性,用户每次打开页面看到的颜色都一样,不会闪烁变化。实际项目中用真实图片后,这个方法就不需要了,但在开发阶段用颜色块能快速验证布局效果。
删除确认对话框
删除相册前需要确认:
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弹出确认框。提示用户删除相册会同时删除里面的照片。
取消按钮关闭对话框。
_showDeleteDialog显示删除确认对话框。showDialog是Flutter的对话框API,builder返回AlertDialog。title显示"删除相册",content显示详细说明,特别强调"相册中的所有照片也会被删除",让用户明白这是个危险操作。actions数组包含两个按钮,第一个是取消按钮,点击后调用Navigator.pop关闭对话框,不执行删除操作。
TextButton(
onPressed: () {
context.read<AlbumProvider>().deleteAlbum(album.id);
Navigator.pop(context);
Navigator.pop(context);
},
child: const Text('删除', style: TextStyle(color: Colors.red)),
),
],
),
);
}
确认删除后调用Provider的
deleteAlbum方法。然后关闭对话框,再返回上一页。
删除按钮用红色,提醒用户这是危险操作。
第二个按钮是删除按钮,点击后执行删除操作。context.read()获取Provider实例,调用deleteAlbum方法删除相册。这个方法会从数据库中删除相册记录,同时删除相册中的所有照片。删除完成后,第一个Navigator.pop关闭对话框,第二个Navigator.pop返回上一页(相册列表页)。删除按钮的文字用红色,这是Material Design的惯例,红色表示危险操作,提醒用户谨慎点击。
添加照片功能
在页面底部添加悬浮按钮:
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(album.name),
actions: [
IconButton(
icon: const Icon(Icons.edit),
onPressed: () => Navigator.push(
context,
MaterialPageRoute(builder: (_) => EditAlbumScreen(album: album)),
),
),
PopupMenuButton<String>(
onSelected: (value) {
if (value == 'delete') {
_showDeleteDialog(context);
} else if (value == 'share') {
_shareAlbum(context);
}
},
itemBuilder: (context) => [
const PopupMenuItem(value: 'share', child: Text('分享相册')),
const PopupMenuItem(value: 'delete', child: Text('删除相册')),
],
),
],
),
body: Consumer<AlbumProvider>(
builder: (context, provider, _) {
final photos = provider.getPhotosByAlbum(album.id);
return CustomScrollView(
slivers: [
SliverToBoxAdapter(
child: _buildAlbumInfo(),
),
SliverPadding(
padding: EdgeInsets.all(16.w),
sliver: photos.isEmpty
? SliverToBoxAdapter(
child: Center(
child: Column(
children: [
SizedBox(height: 50.h),
Icon(Icons.photo_library, size: 64.sp, color: Colors.grey[300]),
SizedBox(height: 16.h),
Text('暂无照片', style: TextStyle(color: Colors.grey, fontSize: 16.sp)),
SizedBox(height: 8.h),
Text(
'点击右下角+号添加照片',
style: TextStyle(color: Colors.grey[400], fontSize: 14.sp),
),
],
),
),
)
: SliverGrid(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
mainAxisSpacing: 4.w,
crossAxisSpacing: 4.w,
),
delegate: SliverChildBuilderDelegate(
(context, index) => _buildPhotoItem(context, photos[index]),
childCount: photos.length,
),
),
),
],
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: () => Navigator.push(
context,
MaterialPageRoute(
builder: (_) => AddPhotoScreen(albumId: album.id),
),
),
child: const Icon(Icons.add),
),
);
}
void _shareAlbum(BuildContext context) {
// 实现分享功能
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('分享功能开发中...')),
);
}
}
FloatingActionButton是悬浮操作按钮,点击后跳转到AddPhotoScreen添加照片。albumId参数传入当前相册的ID,这样新添加的照片会自动归入这个相册。空状态提示也更新了,加了"点击右下角+号添加照片"的引导文字,帮助用户快速上手。PopupMenuButton也增加了"分享相册"选项,虽然功能还没实现,但UI框架已经搭好了。_shareAlbum方法目前只是显示一个SnackBar提示,实际项目中可以调用系统分享API,让用户把相册分享给其他人。
照片长按选择功能
支持长按照片进入多选模式:
class _AlbumDetailScreenState extends State<AlbumDetailScreen> {
bool _isSelectionMode = false;
final Set<String> _selectedPhotoIds = {};
Widget _buildPhotoItem(BuildContext context, PhotoModel photo) {
final isSelected = _selectedPhotoIds.contains(photo.id);
return InkWell(
onTap: () {
if (_isSelectionMode) {
setState(() {
if (isSelected) {
_selectedPhotoIds.remove(photo.id);
if (_selectedPhotoIds.isEmpty) {
_isSelectionMode = false;
}
} else {
_selectedPhotoIds.add(photo.id);
}
});
} else {
Navigator.push(
context,
MaterialPageRoute(builder: (_) => PhotoDetailScreen(photo: photo)),
);
}
},
onLongPress: () {
setState(() {
_isSelectionMode = true;
_selectedPhotoIds.add(photo.id);
});
},
child: Stack(
fit: StackFit.expand,
children: [
Container(
color: _getColorForPhoto(photo.url),
child: Center(
child: Icon(Icons.photo, color: Colors.white.withOpacity(0.5), size: 32.sp),
),
),
if (photo.isFavorite)
Positioned(
top: 4.w,
right: 4.w,
child: Icon(Icons.favorite, color: Colors.red, size: 20.sp),
),
if (_isSelectionMode)
Positioned.fill(
child: Container(
color: isSelected ? Colors.blue.withOpacity(0.3) : Colors.transparent,
child: isSelected
? const Align(
alignment: Alignment.topRight,
child: Padding(
padding: EdgeInsets.all(8.0),
child: Icon(Icons.check_circle, color: Colors.blue, size: 24),
),
)
: null,
),
),
],
),
);
}
长按照片进入选择模式,再次点击可以选中或取消选中照片。选中的照片会显示蓝色半透明遮罩和勾选图标。_isSelectionMode标记当前是否处于选择模式,_selectedPhotoIds存储已选中的照片ID。
在选择模式下,点击照片不再跳转详情页,而是切换选中状态。如果取消所有选中,自动退出选择模式。这种交互方式在相册类App中很常见,用户体验好。
批量操作工具栏
选择模式下显示底部工具栏:
Widget build(BuildContext context) {
return Scaffold(
appBar: _isSelectionMode
? AppBar(
leading: IconButton(
icon: const Icon(Icons.close),
onPressed: () {
setState(() {
_isSelectionMode = false;
_selectedPhotoIds.clear();
});
},
),
title: Text('已选择${_selectedPhotoIds.length}张'),
actions: [
TextButton(
onPressed: () {
setState(() {
final provider = context.read<AlbumProvider>();
final photos = provider.getPhotosByAlbum(widget.album.id);
_selectedPhotoIds.addAll(photos.map((p) => p.id));
});
},
child: const Text('全选', style: TextStyle(color: Colors.white)),
),
],
)
: AppBar(
title: Text(widget.album.name),
actions: [
IconButton(
icon: const Icon(Icons.edit),
onPressed: () => Navigator.push(
context,
MaterialPageRoute(builder: (_) => EditAlbumScreen(album: widget.album)),
),
),
PopupMenuButton<String>(
onSelected: (value) {
if (value == 'delete') {
_showDeleteDialog(context);
} else if (value == 'share') {
_shareAlbum(context);
}
},
itemBuilder: (context) => [
const PopupMenuItem(value: 'share', child: Text('分享相册')),
const PopupMenuItem(value: 'delete', child: Text('删除相册')),
],
),
],
),
body: Consumer<AlbumProvider>(
builder: (context, provider, _) {
final photos = provider.getPhotosByAlbum(widget.album.id);
return CustomScrollView(
slivers: [
if (!_isSelectionMode)
SliverToBoxAdapter(
child: _buildAlbumInfo(),
),
SliverPadding(
padding: EdgeInsets.all(16.w),
sliver: photos.isEmpty
? SliverToBoxAdapter(
child: Center(
child: Column(
children: [
SizedBox(height: 50.h),
Icon(Icons.photo_library, size: 64.sp, color: Colors.grey[300]),
SizedBox(height: 16.h),
Text('暂无照片', style: TextStyle(color: Colors.grey, fontSize: 16.sp)),
SizedBox(height: 8.h),
Text(
'点击右下角+号添加照片',
style: TextStyle(color: Colors.grey[400], fontSize: 14.sp),
),
],
),
),
)
: SliverGrid(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
mainAxisSpacing: 4.w,
crossAxisSpacing: 4.w,
),
delegate: SliverChildBuilderDelegate(
(context, index) => _buildPhotoItem(context, photos[index]),
childCount: photos.length,
),
),
),
],
);
},
),
bottomNavigationBar: _isSelectionMode
? BottomAppBar(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
IconButton(
icon: const Icon(Icons.favorite),
onPressed: _selectedPhotoIds.isEmpty ? null : _batchAddToFavorites,
tooltip: '添加到收藏',
),
IconButton(
icon: const Icon(Icons.drive_file_move),
onPressed: _selectedPhotoIds.isEmpty ? null : _batchMovePhotos,
tooltip: '移动到其他相册',
),
IconButton(
icon: const Icon(Icons.share),
onPressed: _selectedPhotoIds.isEmpty ? null : _batchSharePhotos,
tooltip: '分享',
),
IconButton(
icon: const Icon(Icons.delete),
onPressed: _selectedPhotoIds.isEmpty ? null : _batchDeletePhotos,
tooltip: '删除',
),
],
),
)
: null,
floatingActionButton: _isSelectionMode
? null
: FloatingActionButton(
onPressed: () => Navigator.push(
context,
MaterialPageRoute(
builder: (_) => AddPhotoScreen(albumId: widget.album.id),
),
),
child: const Icon(Icons.add),
),
);
}
选择模式下,AppBar显示已选数量和全选按钮,左边的关闭按钮退出选择模式。底部显示工具栏,提供收藏、移动、分享、删除四个批量操作。
如果没有选中任何照片,工具栏按钮会禁用(onPressed设为null)。选择模式下隐藏悬浮按钮,避免界面混乱。这种设计让批量操作变得简单高效。
批量操作实现
实现各种批量操作方法:
void _batchAddToFavorites() {
final provider = context.read<AlbumProvider>();
for (final photoId in _selectedPhotoIds) {
provider.togglePhotoFavorite(photoId);
}
setState(() {
_isSelectionMode = false;
_selectedPhotoIds.clear();
});
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('已添加到收藏')),
);
}
void _batchMovePhotos() {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('移动到'),
content: SizedBox(
width: double.maxFinite,
child: Consumer<AlbumProvider>(
builder: (context, provider, _) {
final albums = provider.albums.where((a) => a.id != widget.album.id).toList();
return ListView.builder(
shrinkWrap: true,
itemCount: albums.length,
itemBuilder: (context, index) {
final album = albums[index];
return ListTile(
title: Text(album.name),
subtitle: Text(album.category),
onTap: () {
provider.movePhotos(_selectedPhotoIds.toList(), album.id);
Navigator.pop(context);
setState(() {
_isSelectionMode = false;
_selectedPhotoIds.clear();
});
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('已移动到${album.name}')),
);
},
);
},
);
},
),
),
),
);
}
void _batchSharePhotos() {
// 实现分享功能
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('分享${_selectedPhotoIds.length}张照片')),
);
setState(() {
_isSelectionMode = false;
_selectedPhotoIds.clear();
});
}
void _batchDeletePhotos() {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('删除照片'),
content: Text('确定要删除选中的${_selectedPhotoIds.length}张照片吗?'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('取消'),
),
TextButton(
onPressed: () {
final provider = context.read<AlbumProvider>();
for (final photoId in _selectedPhotoIds) {
provider.deletePhoto(photoId);
}
Navigator.pop(context);
setState(() {
_isSelectionMode = false;
_selectedPhotoIds.clear();
});
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('已删除')),
);
},
child: const Text('删除', style: TextStyle(color: Colors.red)),
),
],
),
);
}
批量收藏遍历选中的照片ID,调用togglePhotoFavorite切换收藏状态。批量移动弹出对话框,列出其他相册,用户选择目标相册后调用movePhotos方法。
批量分享目前只是显示提示,实际项目中可以调用系统分享API。批量删除弹出确认对话框,显示要删除的数量,确认后遍历删除。所有操作完成后都退出选择模式并清空选中列表。
照片排序功能
支持按不同方式排序照片:
enum PhotoSortType {
dateDesc,
dateAsc,
nameAsc,
nameDesc,
}
class _AlbumDetailScreenState extends State<AlbumDetailScreen> {
PhotoSortType _sortType = PhotoSortType.dateDesc;
List<PhotoModel> _sortPhotos(List<PhotoModel> photos) {
final sorted = List<PhotoModel>.from(photos);
switch (_sortType) {
case PhotoSortType.dateDesc:
sorted.sort((a, b) => b.createdAt.compareTo(a.createdAt));
break;
case PhotoSortType.dateAsc:
sorted.sort((a, b) => a.createdAt.compareTo(b.createdAt));
break;
case PhotoSortType.nameAsc:
sorted.sort((a, b) => a.name.compareTo(b.name));
break;
case PhotoSortType.nameDesc:
sorted.sort((a, b) => b.name.compareTo(a.name));
break;
}
return sorted;
}
void _showSortDialog() {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('排序方式'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
RadioListTile<PhotoSortType>(
title: const Text('最新优先'),
value: PhotoSortType.dateDesc,
groupValue: _sortType,
onChanged: (value) {
setState(() => _sortType = value!);
Navigator.pop(context);
},
),
RadioListTile<PhotoSortType>(
title: const Text('最早优先'),
value: PhotoSortType.dateAsc,
groupValue: _sortType,
onChanged: (value) {
setState(() => _sortType = value!);
Navigator.pop(context);
},
),
RadioListTile<PhotoSortType>(
title: const Text('名称升序'),
value: PhotoSortType.nameAsc,
groupValue: _sortType,
onChanged: (value) {
setState(() => _sortType = value!);
Navigator.pop(context);
},
),
RadioListTile<PhotoSortType>(
title: const Text('名称降序'),
value: PhotoSortType.nameDesc,
groupValue: _sortType,
onChanged: (value) {
setState(() => _sortType = value!);
Navigator.pop(context);
},
),
],
),
),
);
}
}
定义PhotoSortType枚举,包含四种排序方式:日期降序、日期升序、名称升序、名称降序。_sortPhotos方法根据当前排序类型对照片列表排序。
_showSortDialog弹出排序选择对话框,用RadioListTile显示单选项。选择后立即应用排序并关闭对话框。在AppBar的actions中添加排序按钮,调用这个方法。
小结
相册详情页主要是展示照片网格和相册信息。
用三列网格展示照片,收藏的照片右上角有爱心标记。
支持编辑和删除相册,删除前有确认对话框,提醒用户这是危险操作。
悬浮按钮提供添加照片的快捷入口,空状态有友好的引导文字。
PopupMenuButton收纳了不常用的操作,保持界面简洁。长按照片进入选择模式,支持批量收藏、移动、分享、删除等操作。
选择模式下AppBar显示已选数量和全选按钮,底部显示操作工具栏。支持按日期或名称排序照片,满足不同查看需求。整个页面的交互流畅,功能完整,是相册App的核心页面之一。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐



所有评论(0)