Flutter---相册

发布时间:2026/8/12 16:10:00

Flutter---相册
效果图代码实例local_album_page UI主页面import dart:math; import package:flutter/material.dart; import package:my_flutter/photo_detail_page.dart; import ../local_photos.dart; ///本地相册页面 class LocalAlbumPage extends StatefulWidget { const LocalAlbumPage({super.key}); override StateStatefulWidget createState() _LocalAlbumPageState(); } class _LocalAlbumPageState extends StateLocalAlbumPage { late ListLocalPhotos photoList;// 照片数据列表 bool isDeleteState false; //全局是否处于删除状态 // 文本资源 String localAlbum 本地相册; override void initState() { super.initState(); // 生成模拟照片数据 photoList generateMockPhotos(); } // 生成模拟照片数据 ListLocalPhotos generateMockPhotos() { final ListLocalPhotos photos []; final Random random Random(); // 模拟照片路径使用网络图片或本地占位图 final ListString mockImageUrls [ https://picsum.photos/seed/1/400/400, https://picsum.photos/seed/2/400/400, https://picsum.photos/seed/3/400/400, https://picsum.photos/seed/4/400/400, https://picsum.photos/seed/5/400/400, https://picsum.photos/seed/6/400/400, https://picsum.photos/seed/7/400/400, https://picsum.photos/seed/8/400/400, https://picsum.photos/seed/9/400/400, https://picsum.photos/seed/10/400/400, https://picsum.photos/seed/11/400/400, https://picsum.photos/seed/12/400/400, https://picsum.photos/seed/13/400/400, https://picsum.photos/seed/14/400/400, https://picsum.photos/seed/15/400/400, ]; // 生成过去30天的随机日期 final DateTime now DateTime.now(); for (int i 0; i 30; i) { // 随机选择过去30天内的某一天 final int daysAgo random.nextInt(30); final DateTime photoDate now.subtract(Duration(days: daysAgo)); // 格式化日期为 2024-01-15 final String dateStr ${photoDate.year}-${photoDate.month.toString().padLeft(2, 0)}-${photoDate.day.toString().padLeft(2, 0)}; // 随机选择图片可以重复 final String imageUrl mockImageUrls[random.nextInt(mockImageUrls.length)]; photos.add(LocalPhotos( date: dateStr, photoPath: imageUrl, isSelected:false, )); } // 按日期排序最新在前面 photos.sort((a, b) b.date.compareTo(a.date)); return photos; } // 删除选中的照片 void deleteSelectedPhotos() { setState(() { photoList.removeWhere((photo) photo.isSelected); isDeleteState false; }); } //////////////////////////////////////////////////////////////////////////////////////// override Widget build(BuildContext context) { return Scaffold( backgroundColor: Color(0xFFF5FCFF), appBar: AppBar( backgroundColor: Color(0xFFF5FCFF), leading: IconButton( onPressed: () { Navigator.pop(context); }, icon: Icon(Icons.arrow_back_ios,color: Colors.black,), ), title: Text( localAlbum, style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold), ), centerTitle: true, actions: [ isDeleteState? Text(全选) :IconButton( onPressed: () { //进入全选状态 setState(() { isDeleteState true; }); }, icon: Icon(Icons.select_all), ), ], ), body: Stack( children: [ Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox(height: 20), // 照片网格 Expanded( child: _buildPhotoGrid(), ), ], ), //删除悬浮按钮 isDeleteState? Positioned( bottom: 30, left: 0, right: 0, child: Center( child:Container( height: 52, width: 127, padding: EdgeInsets.symmetric(vertical: 5), decoration: BoxDecoration( color: Colors.black.withOpacity(0.3), borderRadius: BorderRadius.circular(26), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, crossAxisAlignment: CrossAxisAlignment.center, children: [ //分享 GestureDetector( onTap: (){ }, child: Column( children: [ Icon(Icons.share,color: Colors.white,), SizedBox(height: 4,), Text(分享,style: TextStyle(color: Colors.white,fontSize: 8),), ], ), ), //删除 GestureDetector( onTap: (){ deleteSelectedPhotos(); }, child: Column( children: [ Icon(Icons.delete,color: Colors.white,), SizedBox(height: 4,), Text(删除,style: TextStyle(color: Colors.white,fontSize: 8),), ], ), ), ], ), ), ) ):SizedBox.shrink() ], ) ); } //构建照片网格 Widget _buildPhotoGrid() { // 按日期分组 final MapString, ListLocalPhotos groupedPhotos {}; for (var photo in photoList) { if (!groupedPhotos.containsKey(photo.date)) { groupedPhotos[photo.date] []; } groupedPhotos[photo.date]!.add(photo); } // 获取排序后的日期列表最新在前面 final ListString sortedDates groupedPhotos.keys.toList()..sort((a, b) b.compareTo(a)); return ListView.builder( padding: EdgeInsets.symmetric(horizontal: 16), itemCount: sortedDates.length, itemBuilder: (context, index) { final String date sortedDates[index]; final ListLocalPhotos photosOfDay groupedPhotos[date]!; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 日期标题 Padding( padding: EdgeInsets.symmetric(vertical: 12), child: Text( _formatDateDisplay(date), style: TextStyle( fontSize: 20, fontWeight: FontWeight.w600, color: Colors.black, ), ), ), // 该日期的照片网格 GridView.builder( shrinkWrap: true, // 让 GridView 在 ListView 中自适应 physics: NeverScrollableScrollPhysics(), // 禁止内部滚动 gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 3, // 每行3列 crossAxisSpacing: 4, mainAxisSpacing: 4, childAspectRatio: 1, // 正方形 ), itemCount: photosOfDay.length, itemBuilder: (context, photoIndex) { return _buildPhotoItem(photosOfDay[photoIndex]); }, ), SizedBox(height: 8), ], ); }, ); } //构建单张照片 Widget _buildPhotoItem(LocalPhotos photo) { return GestureDetector( onTap: (){ //单击进入详细页面 if (!isDeleteState) { Navigator.push( context, MaterialPageRoute( builder: (context) PhotoDetailPage(photo: photo), ), ); } }, child: ClipRRect( borderRadius: BorderRadius.circular(26), child: Stack( fit: StackFit.expand, children: [ // 底层图片 Image.network( photo.photoPath, fit: BoxFit.cover, loadingBuilder: (context, child, loadingProgress) { if (loadingProgress null) { return child; } return Container( color: Colors.grey[200], child: Center( child: CircularProgressIndicator( value: loadingProgress.expectedTotalBytes ! null ? loadingProgress.cumulativeBytesLoaded / loadingProgress.expectedTotalBytes! : null, strokeWidth: 2, ), ), ); }, errorBuilder: (context, error, stackTrace) { return Container( color: Colors.grey[300], child: Icon(Icons.broken_image, color: Colors.grey[600]), ); }, ), //圆环图标始终显示 isDeleteState?Positioned( right: 8, top: 8, child:GestureDetector( onTap: (){ setState(() { photo.isSelected !photo.isSelected; }); }, child: Container( height: 20, width: 20, child: photo.isSelected? Icon(Icons.check_circle,color: Colors.white,):Icon(Icons.circle_outlined,color: Colors.white) ), ) ):SizedBox.shrink() ], ), ), ); } // 格式化日期显示 String _formatDateDisplay(String dateStr) { final parts dateStr.split(-); if (parts.length ! 3) return dateStr; final year parts[0]; final month parts[1]; final day parts[2]; // 判断是否是今天、昨天等 final DateTime now DateTime.now(); final DateTime today DateTime(now.year, now.month, now.day); final DateTime photoDate DateTime( int.parse(year), int.parse(month), int.parse(day) ); final int difference today.difference(photoDate).inDays; if (difference 0) { return 今天; } else if (difference 1) { return 昨天; } else if (difference 7) { return ${difference}天前; } else { // 返回 2024年1月15日 格式 return ${year}年${int.parse(month)}月${int.parse(day)}日; } } }photo_detail_page 照片详情页面import package:flutter/material.dart; import local_photos.dart; /// 照片展示页面 - 显示照片原始尺寸 class PhotoDetailPage extends StatefulWidget { const PhotoDetailPage({ super.key, required this.photo, }); final LocalPhotos photo; override StateStatefulWidget createState() _PhotoDetailPageState(); } class _PhotoDetailPageState extends StatePhotoDetailPage { override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.white, appBar: AppBar( backgroundColor: Colors.white, elevation: 0, leading: IconButton( onPressed: () { Navigator.pop(context); }, icon: Icon(Icons.arrow_back_ios, color: Colors.black), ), actions: [ Text( _formatDate(widget.photo.date), style: TextStyle(color: Colors.black, fontSize: 20), ), ], ), body: Stack( children: [ //背景图 Center( child: Image.network( widget.photo.photoPath, fit: BoxFit.fitWidth, loadingBuilder: (context, child, loadingProgress) { if (loadingProgress null) return child; return Container( width: 300, height: 300, color: Colors.grey[900], child: Center( child: CircularProgressIndicator( value: loadingProgress.expectedTotalBytes ! null ? loadingProgress.cumulativeBytesLoaded / loadingProgress.expectedTotalBytes! : null, color: Colors.white, ), ), ); }, errorBuilder: (context, error, stackTrace) { return Container( width: 300, height: 300, color: Colors.grey[900], child: Center( child: Icon(Icons.broken_image, color: Colors.grey[600], size: 64), ), ); }, ), ), //底部按钮 Positioned( bottom: 20, right: 0, left: 0, child: Center( child: Container( height: 52, width: 270, padding: EdgeInsets.symmetric(vertical: 5), decoration: BoxDecoration( color: Color(0xFF000000).withOpacity(0.3), borderRadius: BorderRadius.circular(26), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ //智能分析 GestureDetector( child: Column( children: [ Icon(Icons.analytics,color: Colors.white,), SizedBox(height: 4,), Text(智能分析,style: TextStyle(color: Colors.white,fontSize: 8),) ], ) ), //下载 GestureDetector( child: Column( children: [ Icon(Icons.download,color: Colors.white,), SizedBox(height: 4,), Text(下载,style: TextStyle(color: Colors.white,fontSize: 8),) ], ) ), //分析 GestureDetector( child: Column( children: [ Icon(Icons.share,color: Colors.white,), SizedBox(height: 4,), Text(分享,style: TextStyle(color: Colors.white,fontSize: 8),) ], ) ), //删除 GestureDetector( child: Column( children: [ Icon(Icons.delete,color: Colors.white,), SizedBox(height: 4,), Text(删除,style: TextStyle(color: Colors.white,fontSize: 8),) ], ) ), ], ), ), ), ) ], ) ); } // 格式化日期为 2024年1月15日 格式 String _formatDate(String dateStr) { final parts dateStr.split(-); if (parts.length ! 3) return dateStr; final year parts[0]; final month int.parse(parts[1]); // 转成数字去掉前导0 final day int.parse(parts[2]); return ${year}年${month}月${day}日; } }照片类///本地照片类 class LocalPhotos { String date;//日期 String photoPath; //照片路径 bool isSelected; //是否被选中 LocalPhotos({ required this.date, required this.photoPath, this.isSelected false, //默认未选中 }); //复制方法 LocalPhotos copyWith({ String? date, String? photoPath, bool? isSelected, }){ return LocalPhotos( date: date ?? this.date, photoPath: photoPath ?? this.photoPath, isSelected: isSelected ?? this.isSelected, ); } }

相关新闻

收藏!清华姚班大模型实习日薪5500元,揭秘AI行业高薪真相!

收藏!清华姚班大模型实习日薪5500元,揭秘AI行业高薪真相!

2026/8/12 16:10:00

本文揭示了AI大模型领域的人才争夺战,以清华姚班学子在DeepSeek、腾讯、阿里等公司的高薪实习为例,展现了AI行业的两极分化。顶尖AI人才因其稀缺性和技术价值,获得远超普通技术岗的薪资。文章强调AI产业的竞争本质是顶尖智力的竞争&#xff0…

FDE模式:AI落地关键!小白程序员必备,收藏这份进阶指南

FDE模式:AI落地关键!小白程序员必备,收藏这份进阶指南

2026/8/12 16:10:00

FDE(现场工程交付)并非AI行业新概念,而是继承了咨询、实施与现场工程的工作方式,并因AI模型不确定性和企业数据流程约束,升级为以业务结果、生产系统和平台复用为核心的新交付模式。本文深入剖析FDE的核心价值&#xf…

从零开始学大模型Agent开发:小白也能掌握的实战路线图

从零开始学大模型Agent开发:小白也能掌握的实战路线图

2026/8/12 16:10:00

本文以“售后工单Agent”为例,系统阐述了开发大模型Agent的八层技术栈及依赖关系,强调从稳定调用模型API入手,逐步掌握工具、上下文管理、状态控制、评测、安全与部署等环节。推荐四阶段学习路线:结构化信息提取、知识问答、业务操…

12 - 英伟达的“窄门”:一家5.5万亿美元的公司,如何成为AI时代“叫醒人类”的闹钟

12 - 英伟达的“窄门”:一家5.5万亿美元的公司,如何成为AI时代“叫醒人类”的闹钟

2026/8/12 18:20:06

2026年5月13日,英伟达盘中市值突破5.5万亿美元,成为史上首家跨过这一里程碑的企业。这个数字意味着什么?按IMF 2026年4月的预测,德国GDP约5.45万亿美元,日本约4.38万亿美元——英伟达一家公司的市值,已经超…

2026年学术论文降AI率工具评测与实战指南

2026年学术论文降AI率工具评测与实战指南

2026/8/12 18:20:06

1. 项目背景与核心痛点 2026年的学术圈正在经历一场前所未有的技术革命与信任危机。随着AI生成内容的边界不断模糊,全球TOP100高校中有87所已明确将"AI生成内容占比"纳入论文查重指标。Nature最新统计显示,2025年全球被撤稿的论文中&#xff0…

NS-Scope上位机软件:解锁泰克TDS1000/2000示波器数据采集与自动化潜能

NS-Scope上位机软件:解锁泰克TDS1000/2000示波器数据采集与自动化潜能

2026/8/12 18:20:06

1. 项目概述:当硬件示波器遇上软件“外挂” 手头有一台泰克Tektronix的TDS1000/2000系列示波器,比如经典的TDS1012、TDS2002或TDS2004,是很多电子工程师、学生和爱好者的老朋友。这些设备皮实耐用,操作直观,但有一个痛…

从ReAct到Plan-and-Solve:构建具备规划能力的AI智能体

从ReAct到Plan-and-Solve:构建具备规划能力的AI智能体

2026/8/12 18:20:06

1. 项目概述:从“想到就做”到“三思而后行”的智能体进化在AI智能体开发这个圈子里,最近两年大家讨论最多的,可能就是如何让大语言模型(LLM)驱动的智能体,从一个只会“直线思考”的简单执行者,…

XShell批量发送命令:多服务器运维效率提升实战指南

XShell批量发送命令:多服务器运维效率提升实战指南

2026/8/12 18:20:06

1. 为什么你需要批量发送命令?如果你是一名运维工程师、系统管理员,或者需要同时管理多台服务器、网络设备的开发者,那么下面这个场景你一定不陌生:公司有几十台甚至上百台线上服务器,突然需要紧急更新一个系统配置&am…

Web安全实战:用户输入处理中的转义、验证与清理机制详解

Web安全实战:用户输入处理中的转义、验证与清理机制详解

2026/8/12 18:10:05

最近在开发一个需要处理用户上传内容的项目时,遇到了一个棘手的问题:如何安全、高效地处理用户提交的文本,特别是当文本中包含一些特殊字符或潜在风险内容时。这让我深入研究了字符串处理中的转义、验证和清理机制。本文将围绕这个主题&#…

比较好的亚太EMBA,问了6位校友师资差别真的挺大

比较好的亚太EMBA,问了6位校友师资差别真的挺大

2026/8/12 7:11:29

比较好的亚太EMBA核心差异先看什么?对于希望兼顾工作与系统管理能力提升的亚太区高管而言,筛选匹配度高的EMBA项目时,师资配置是决定学习体验与实际收获的核心要素之一。我们结合3-4个公开信息透明、办学历史较长的亚太区主流EMBA项目特点&am…

备考3个月对比6份资料 海外游学的亚洲EMBA面试注意点

备考3个月对比6份资料 海外游学的亚洲EMBA面试注意点

2026/8/11 8:44:43

备考海外游学的亚洲EMBA面试,核心要围绕项目国际化设计逻辑、个人跨文化管理经验匹配度两个维度准备,避免把游学模块等同于普通旅游参访的认知偏差。不少备考者花3个月对比6份资料,却容易忽略面试官对“国际视野落地能力”的考察——比如香港…

比较好的国内EMBA,问了二十位校友聊透人脉价值

比较好的国内EMBA,问了二十位校友聊透人脉价值

2026/8/11 15:57:54

比较好的国内EMBA核心差异体现在哪些方面?比较好的国内EMBA的核心长期价值,很大程度上依托于校友网络的连接质量与资源生态的活跃度,这也是不少高管在择校时优先考量的因素。我们结合3-4个市场关注度较高的项目公开信息,从课程、师…

告别模组冲突!5步掌握《神界:原罪2》模组管理的终极秘诀

告别模组冲突!5步掌握《神界:原罪2》模组管理的终极秘诀

2026/8/12 9:39:37

告别模组冲突!5步掌握《神界:原罪2》模组管理的终极秘诀 【免费下载链接】DivinityModManager A mod manager for Divinity: Original Sin - Definitive Edition. 项目地址: https://gitcode.com/gh_mirrors/di/DivinityModManager 你是否曾经为《…

如何用Charge Limiter延长MacBook电池寿命:终极保护指南

如何用Charge Limiter延长MacBook电池寿命:终极保护指南

2026/8/12 9:39:37

如何用Charge Limiter延长MacBook电池寿命:终极保护指南 【免费下载链接】charge-limiter macOS app to set battery charge limit for Intel MacBooks 项目地址: https://gitcode.com/gh_mirrors/ch/charge-limiter 还在为MacBook电池健康度下降而烦恼吗&am…

推三返一模式5.0版本系统开发

推三返一模式5.0版本系统开发

2026/8/12 9:39:37

推三返一模式5.0版本系统开发要点编辑:araolin(私域邦网络土土哥)模式核心逻辑 推三返一是一种促销或分销机制,用户推荐三人完成特定行为(如购买、注册),推荐人可获得返利或奖励。5.0版本通常在…

摆脱论文困扰!盘点2026年全网爆红的的AI论文写作工具

摆脱论文困扰!盘点2026年全网爆红的的AI论文写作工具

2026/8/8 5:07:31

一天写完毕业论文在2026年已不再是天方夜谭。2026年最炸裂、实测能大幅提速的AI论文写作工具,覆盖选题构思、文献整理、内容生成、格式排版等核心场景,真正帮你高效搞定论文难题。 一、全流程王者:一站式搞定论文全链路(一天定稿首…

导师推荐!2026最新AI论文工具测评与实用推荐

导师推荐!2026最新AI论文工具测评与实用推荐

2026/8/9 13:42:46

2026年真正好用的AI论文工具,核心看生成的论文质量、低AI味、格式正确、学术适配四大指标。综合实测,千笔AI、ThouPen、豆包、DeepSeek、Grammarly 是当前最值得推荐的梯队,覆盖从免费到付费、从中文到英文、从文科到理工的全场景需求。 一、…

告别游戏崩溃:XCOM 2模组管理器的智能革命

告别游戏崩溃:XCOM 2模组管理器的智能革命

2026/8/8 2:30:15

告别游戏崩溃:XCOM 2模组管理器的智能革命 【免费下载链接】xcom2-launcher The Alternative Mod Launcher (AML) is a replacement for the default game launchers from XCOM 2 and XCOM Chimera Squad. 项目地址: https://gitcode.com/gh_mirrors/xc/xcom2-lau…