news 2026/5/6 21:12:58

Flutter for OpenHarmony音乐播放器App实战11:创建歌单实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Flutter for OpenHarmony音乐播放器App实战11:创建歌单实现

创建歌单是音乐播放器中一个基础但重要的功能。用户可以创建自己的歌单来整理和收藏喜欢的音乐。本篇文章将详细介绍如何实现一个简洁实用的创建歌单页面,包括封面上传、名称输入、隐私设置等功能。

页面基础结构

创建歌单页面使用StatefulWidget,因为需要管理输入框内容和开关状态。

import'package:flutter/material.dart';import'package:get/get.dart';classCreatePlaylistPageextendsStatefulWidget{constCreatePlaylistPage({super.key});@overrideState<CreatePlaylistPage>createState()=>_CreatePlaylistPageState();}

页面继承自StatefulWidget,使用GetX进行路由管理。创建歌单的交互相对简单,但需要响应用户的输入和开关操作。

状态变量定义

页面需要管理输入控制器和隐私开关状态。

class_CreatePlaylistPageStateextendsState<CreatePlaylistPage>{final_nameController=TextEditingController();bool _isPrivate=false;@overridevoiddispose(){_nameController.dispose();super.dispose();}

_nameController用于控制歌单名称输入框,_isPrivate标识歌单是否设为私密。在dispose方法中释放控制器资源,这是Flutter开发中的标准做法。

AppBar设计

AppBar包含标题和完成按钮。

@overrideWidgetbuild(BuildContextcontext){returnScaffold(appBar:AppBar(title:constText('创建歌单'),actions:[TextButton(onPressed:()=>_createPlaylist(),child:constText('完成',style:TextStyle(color:Color(0xFFE91E63)),),),],),

完成按钮使用主题色,放在AppBar右侧。点击后调用_createPlaylist方法提交创建请求。这种设计符合用户的操作习惯。

页面主体布局

页面主体使用Padding包裹Column,垂直排列各个组件。

body:Padding(padding:constEdgeInsets.all(16),child:Column(crossAxisAlignment:CrossAxisAlignment.start,children:[_buildCoverPicker(),constSizedBox(height:24),_buildNameInput(),constSizedBox(height:16),_buildPrivacySwitch(),],),),);}

页面包含三个主要部分:封面选择、名称输入和隐私设置。使用SizedBox控制各部分之间的间距。

封面选择组件

封面选择区域居中显示,点击可以选择图片。

Widget_buildCoverPicker(){returnCenter(child:GestureDetector(onTap:()=>_pickCoverImage(),child:Container(width:120,height:120,decoration:BoxDecoration(borderRadius:BorderRadius.circular(12),color:constColor(0xFF1E1E1E),),child:constColumn(mainAxisAlignment:MainAxisAlignment.center,children:[Icon(Icons.add_photo_alternate,size:40,color:Colors.grey),SizedBox(height:8),Text('添加封面',style:TextStyle(color:Colors.grey,fontSize:12)),],),),),);}

封面容器使用深色背景,圆角设置为12。内部垂直排列图标和文字,提示用户点击添加封面。GestureDetector包裹整个容器,扩大点击区域。

选择封面图片

点击封面区域后调用图片选择方法。

void_pickCoverImage()async{// 实际项目中使用image_picker插件Get.bottomSheet(Container(decoration:constBoxDecoration(color:Color(0xFF1E1E1E),borderRadius:BorderRadius.vertical(top:Radius.circular(16)),),child:Column(mainAxisSize:MainAxisSize.min,children:[ListTile(leading:constIcon(Icons.camera_alt),title:constText('拍照'),onTap:(){Get.back();Get.snackbar('提示','相机功能开发中');},),ListTile(leading:constIcon(Icons.photo_library),title:constText('从相册选择'),onTap:(){Get.back();Get.snackbar('提示','相册功能开发中');},),constSizedBox(height:16),],),),);}

使用底部菜单提供拍照和从相册选择两个选项。实际项目中需要使用image_picker插件来实现真正的图片选择功能。

名称输入组件

歌单名称输入区域包含标签和输入框。

Widget_buildNameInput(){returnColumn(crossAxisAlignment:CrossAxisAlignment.start,children:[constText('歌单名称',style:TextStyle(fontWeight:FontWeight.bold),),constSizedBox(height:8),TextField(controller:_nameController,decoration:InputDecoration(hintText:'请输入歌单名称',filled:true,fillColor:constColor(0xFF1E1E1E),border:OutlineInputBorder(borderRadius:BorderRadius.circular(12),borderSide:BorderSide.none,),),),],);}

输入框使用深色填充背景,无边框设计。hintText提示用户输入内容。圆角与封面容器保持一致,视觉上更加协调。

隐私设置开关

使用SwitchListTile实现隐私设置。

Widget_buildPrivacySwitch(){returnSwitchListTile(title:constText('设为私密'),subtitle:constText('私密歌单仅自己可见'),value:_isPrivate,onChanged:(v)=>setState(()=>_isPrivate=v),activeColor:constColor(0xFFE91E63),contentPadding:EdgeInsets.zero,);}

SwitchListTile集成了标题、副标题和开关,非常适合这种设置项。activeColor设置为主题色,contentPadding设为零与其他组件对齐。

创建歌单方法

点击完成按钮后执行创建逻辑。

void_createPlaylist(){finalname=_nameController.text.trim();if(name.isEmpty){Get.snackbar('提示','请输入歌单名称',snackPosition:SnackPosition.BOTTOM,backgroundColor:Colors.red.withOpacity(0.8),colorText:Colors.white,);return;}// 模拟创建歌单Get.back(result:{'name':name,'isPrivate':_isPrivate,});Get.snackbar('成功','歌单创建成功',snackPosition:SnackPosition.BOTTOM,backgroundColor:Colors.green.withOpacity(0.8),colorText:Colors.white,);}

首先验证歌单名称是否为空,为空时显示错误提示。创建成功后返回上一页并传递创建结果,同时显示成功提示。

输入验证增强

可以添加更多的输入验证规则。

bool_validateInput(){finalname=_nameController.text.trim();if(name.isEmpty){_showError('请输入歌单名称');returnfalse;}if(name.length>40){_showError('歌单名称不能超过40个字符');returnfalse;}// 检查是否包含特殊字符finalregex=RegExp(r'[<>"\\/|?*]');if(regex.hasMatch(name)){_showError('歌单名称不能包含特殊字符');returnfalse;}returntrue;}void_showError(Stringmessage){Get.snackbar('提示',message,snackPosition:SnackPosition.BOTTOM,backgroundColor:Colors.red.withOpacity(0.8),colorText:Colors.white,);}

验证规则包括非空检查、长度限制和特殊字符检查。将错误提示抽取为独立方法,避免代码重复。

歌单描述输入

可以添加歌单描述输入框。

Widget_buildDescriptionInput(){returnColumn(crossAxisAlignment:CrossAxisAlignment.start,children:[constText('歌单简介',style:TextStyle(fontWeight:FontWeight.bold),),constSizedBox(height:8),TextField(controller:_descController,maxLines:4,maxLength:200,decoration:InputDecoration(hintText:'介绍一下这个歌单吧(选填)',filled:true,fillColor:constColor(0xFF1E1E1E),border:OutlineInputBorder(borderRadius:BorderRadius.circular(12),borderSide:BorderSide.none,),),),],);}

描述输入框设置maxLines为4,允许多行输入。maxLength限制最大字符数,输入框会自动显示字符计数。

标签选择功能

可以为歌单添加标签。

finalList<String>_availableTags=['流行','摇滚','民谣','电子','古典','爵士','R&B','说唱'];finalSet<String>_selectedTags={};Widget_buildTagSelector(){returnColumn(crossAxisAlignment:CrossAxisAlignment.start,children:[constText('歌单标签',style:TextStyle(fontWeight:FontWeight.bold),),constSizedBox(height:8),Wrap(spacing:8,runSpacing:8,children:_availableTags.map((tag){finalisSelected=_selectedTags.contains(tag);returnGestureDetector(onTap:(){setState((){if(isSelected){_selectedTags.remove(tag);}elseif(_selectedTags.length<3){_selectedTags.add(tag);}else{Get.snackbar('提示','最多选择3个标签');}});},child:Chip(label:Text(tag),backgroundColor:isSelected?constColor(0xFFE91E63):constColor(0xFF1E1E1E),labelStyle:TextStyle(color:isSelected?Colors.white:Colors.grey,),),);}).toList(),),],);}

使用Wrap组件实现流式布局,标签会自动换行。选中的标签使用主题色背景,限制最多选择3个标签。

封面预览

选择封面后显示预览。

String?_coverPath;Widget_buildCoverPreview(){returnCenter(child:GestureDetector(onTap:()=>_pickCoverImage(),child:Container(width:120,height:120,decoration:BoxDecoration(borderRadius:BorderRadius.circular(12),color:constColor(0xFF1E1E1E),image:_coverPath!=null?DecorationImage(image:FileImage(File(_coverPath!)),fit:BoxFit.cover,):null,),child:_coverPath==null?constColumn(mainAxisAlignment:MainAxisAlignment.center,children:[Icon(Icons.add_photo_alternate,size:40,color:Colors.grey),SizedBox(height:8),Text('添加封面',style:TextStyle(color:Colors.grey,fontSize:12)),],):Stack(children:[Positioned(right:4,top:4,child:Container(padding:constEdgeInsets.all(4),decoration:constBoxDecoration(color:Colors.black54,shape:BoxShape.circle,),child:constIcon(Icons.edit,size:16,color:Colors.white),),),],),),),);}

如果已选择封面,使用DecorationImage显示图片,右上角显示编辑图标。未选择时显示添加提示。

加载状态处理

创建歌单时显示加载状态。

bool _isLoading=false;void_createPlaylist()async{if(!_validateInput())return;setState(()=>_isLoading=true);try{// 模拟网络请求awaitFuture.delayed(constDuration(seconds:1));Get.back(result:{'name':_nameController.text.trim(),'isPrivate':_isPrivate,'tags':_selectedTags.toList(),});Get.snackbar('成功','歌单创建成功');}catch(e){Get.snackbar('错误','创建失败,请重试');}finally{if(mounted){setState(()=>_isLoading=false);}}}

创建过程中设置_isLoading为true,完成后恢复。使用try-catch处理可能的错误,finally确保状态恢复。

完成按钮状态

根据加载状态和输入内容控制按钮状态。

Widget_buildSubmitButton(){returnTextButton(onPressed:_isLoading||_nameController.text.trim().isEmpty?null:()=>_createPlaylist(),child:_isLoading?constSizedBox(width:20,height:20,child:CircularProgressIndicator(strokeWidth:2,valueColor:AlwaysStoppedAnimation<Color>(Color(0xFFE91E63)),),):constText('完成',style:TextStyle(color:Color(0xFFE91E63)),),);}

加载中显示CircularProgressIndicator,名称为空时按钮禁用。这种设计提供了清晰的状态反馈。

总结

创建歌单页面虽然功能相对简单,但涉及到表单输入、状态管理、输入验证等多个Flutter开发中的常见场景。通过合理的组件拆分和状态管理,让代码结构清晰、易于维护。在实际项目中,还需要对接后端接口实现真正的歌单创建功能,以及使用image_picker等插件实现图片选择。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/26 18:16:16

基于Springboot公司资产管理系统【附源码+文档】

&#x1f495;&#x1f495;作者&#xff1a; 米罗学长 &#x1f495;&#x1f495;个人简介&#xff1a;混迹java圈十余年&#xff0c;精通Java、小程序、数据库等。 &#x1f495;&#x1f495;各类成品Java毕设 。javaweb&#xff0c;ssm&#xff0c;springboot等项目&#…

作者头像 李华
网站建设 2026/4/25 20:04:08

从金鱼记忆到博学大脑:构建AI Agent的专业检索系统全攻略

文章探讨了如何解决AI Agent的"金鱼记忆"问题&#xff0c;通过构建短期工作记忆和长期语义记忆两种核心能力。详细介绍了Agent记忆的两种实现方式、三级检索架构&#xff0c;以及性能优化方案&#xff08;Elasticsearch&#xff09;、语义召回方案&#xff08;Embedd…

作者头像 李华
网站建设 2026/5/1 21:47:12

一文掌握LoRA变体:分类理论到代码实现,解锁大模型高效微调新方法

本文首次对LoRA变体进行统一研究&#xff0c;提出基于秩、优化动力学、初始化策略和MoE集成的结构化分类体系&#xff0c;构建统一理论框架&#xff0c;推出LoRAFactory开源代码库实现50多种变体。大规模评估发现&#xff0c;LoRA对学习率高度敏感&#xff0c;且在适当配置下可…

作者头像 李华
网站建设 2026/5/1 6:44:42

一文读懂银行、上金所、基金公司最新政策全影响

央行工作会议强调延续适度宽松&#xff0c;黄金税收新政推高行业准入门槛&#xff0c;新基金发行市场科技主题当道——2026年的投资市场&#xff0c;在政策引导下正经历一场静水深流的深刻重塑。 中国人民银行宣布&#xff0c;2026年将继续实施好适度宽松的货币政策&#xff0c…

作者头像 李华
网站建设 2026/5/4 11:51:44

深度学习篇---RCF

我用最通俗的方式介绍RCF——它是HED的“威力加强版”&#xff0c;把细节做到了极致。一句话核心思想RCF “用显微镜放大每一层”的HED如果说HED是让AI学会了“概括性画画”&#xff0c;那么RCF就是让AI学会了“超写实素描”——连每一根发丝都要画得清清楚楚。一、一个完美比…

作者头像 李华