创建歌单是音乐播放器中一个基础但重要的功能。用户可以创建自己的歌单来整理和收藏喜欢的音乐。本篇文章将详细介绍如何实现一个简洁实用的创建歌单页面,包括封面上传、名称输入、隐私设置等功能。
页面基础结构
创建歌单页面使用StatefulWidget,因为需要管理输入框内容和开关状态。
dart
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class CreatePlaylistPage extends StatefulWidget {
const CreatePlaylistPage({super.key});
@override
State<CreatePlaylistPage> createState() => _CreatePlaylistPageState();
}
页面继承自StatefulWidget,使用GetX进行路由管理。创建歌单的交互相对简单,但需要响应用户的输入和开关操作。
状态变量定义
页面需要管理输入控制器和隐私开关状态。
dart
class _CreatePlaylistPageState extends State<CreatePlaylistPage> {
final _nameController = TextEditingController();
bool _isPrivate = false;
@override
void dispose() {
_nameController.dispose();
super.dispose();
}
_nameController用于控制歌单名称输入框,_isPrivate标识歌单是否设为私密。在dispose方法中释放控制器资源,这是Flutter开发中的标准做法。
AppBar设计
AppBar包含标题和完成按钮。
dart
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('创建歌单'),
actions: [
TextButton(
onPressed: () => _createPlaylist(),
child: const Text(
'完成',
style: TextStyle(color: Color(0xFFE91E63)),
),
),
],
),
完成按钮使用主题色,放在AppBar右侧。点击后调用_createPlaylist方法提交创建请求。这种设计符合用户的操作习惯。
页面主体布局
页面主体使用Padding包裹Column,垂直排列各个组件。
dart
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildCoverPicker(),
const SizedBox(height: 24),
_buildNameInput(),
const SizedBox(height: 16),
_buildPrivacySwitch(),
],
),
),
);
}
页面包含三个主要部分:封面选择、名称输入和隐私设置。使用SizedBox控制各部分之间的间距。
封面选择组件
封面选择区域居中显示,点击可以选择图片。
dart
Widget _buildCoverPicker() {
return Center(
child: GestureDetector(
onTap: () => _pickCoverImage(),
child: Container(
width: 120,
height: 120,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
color: const Color(0xFF1E1E1E),
),
child: const Column(
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包裹整个容器,扩大点击区域。
选择封面图片
点击封面区域后调用图片选择方法。
dart
void _pickCoverImage() async {
// 实际项目中使用image_picker插件
Get.bottomSheet(
Container(
decoration: const BoxDecoration(
color: Color(0xFF1E1E1E),
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
leading: const Icon(Icons.camera_alt),
title: const Text('拍照'),
onTap: () {
Get.back();
Get.snackbar('提示', '相机功能开发中');
},
),
ListTile(
leading: const Icon(Icons.photo_library),
title: const Text('从相册选择'),
onTap: () {
Get.back();
Get.snackbar('提示', '相册功能开发中');
},
),
const SizedBox(height: 16),
],
),
),
);
}
使用底部菜单提供拍照和从相册选择两个选项。实际项目中需要使用image_picker插件来实现真正的图片选择功能。
名称输入组件
歌单名称输入区域包含标签和输入框。
dart
Widget _buildNameInput() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'歌单名称',
style: TextStyle(fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
TextField(
controller: _nameController,
decoration: InputDecoration(
hintText: '请输入歌单名称',
filled: true,
fillColor: const Color(0xFF1E1E1E),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide.none,
),
),
),
],
);
}
输入框使用深色填充背景,无边框设计。hintText提示用户输入内容。圆角与封面容器保持一致,视觉上更加协调。
隐私设置开关
使用SwitchListTile实现隐私设置。
dart
Widget _buildPrivacySwitch() {
return SwitchListTile(
title: const Text('设为私密'),
subtitle: const Text('私密歌单仅自己可见'),
value: _isPrivate,
onChanged: (v) => setState(() => _isPrivate = v),
activeColor: const Color(0xFFE91E63),
contentPadding: EdgeInsets.zero,
);
}
SwitchListTile集成了标题、副标题和开关,非常适合这种设置项。activeColor设置为主题色,contentPadding设为零与其他组件对齐。
创建歌单方法
点击完成按钮后执行创建逻辑。
dart
void _createPlaylist() {
final name = _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,
);
}
首先验证歌单名称是否为空,为空时显示错误提示。创建成功后返回上一页并传递创建结果,同时显示成功提示。
输入验证增强
可以添加更多的输入验证规则。
dart
bool _validateInput() {
final name = _nameController.text.trim();
if (name.isEmpty) {
_showError('请输入歌单名称');
return false;
}
if (name.length > 40) {
_showError('歌单名称不能超过40个字符');
return false;
}
// 检查是否包含特殊字符
final regex = RegExp(r'[<>"\\/|?*]');
if (regex.hasMatch(name)) {
_showError('歌单名称不能包含特殊字符');
return false;
}
return true;
}
void _showError(String message) {
Get.snackbar(
'提示',
message,
snackPosition: SnackPosition.BOTTOM,
backgroundColor: Colors.red.withOpacity(0.8),
colorText: Colors.white,
);
}
验证规则包括非空检查、长度限制和特殊字符检查。将错误提示抽取为独立方法,避免代码重复。
歌单描述输入
可以添加歌单描述输入框。
dart
Widget _buildDescriptionInput() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'歌单简介',
style: TextStyle(fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
TextField(
controller: _descController,
maxLines: 4,
maxLength: 200,
decoration: InputDecoration(
hintText: '介绍一下这个歌单吧(选填)',
filled: true,
fillColor: const Color(0xFF1E1E1E),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide.none,
),
),
),
],
);
}
描述输入框设置maxLines为4,允许多行输入。maxLength限制最大字符数,输入框会自动显示字符计数。
标签选择功能
可以为歌单添加标签。
dart
final List<String> _availableTags = ['流行', '摇滚', '民谣', '电子', '古典', '爵士', 'R&B', '说唱'];
final Set<String> _selectedTags = {};
Widget _buildTagSelector() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'歌单标签',
style: TextStyle(fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
Wrap(
spacing: 8,
runSpacing: 8,
children: _availableTags.map((tag) {
final isSelected = _selectedTags.contains(tag);
return GestureDetector(
onTap: () {
setState(() {
if (isSelected) {
_selectedTags.remove(tag);
} else if (_selectedTags.length < 3) {
_selectedTags.add(tag);
} else {
Get.snackbar('提示', '最多选择3个标签');
}
});
},
child: Chip(
label: Text(tag),
backgroundColor: isSelected ? const Color(0xFFE91E63) : const Color(0xFF1E1E1E),
labelStyle: TextStyle(
color: isSelected ? Colors.white : Colors.grey,
),
),
);
}).toList(),
),
],
);
}
使用Wrap组件实现流式布局,标签会自动换行。选中的标签使用主题色背景,限制最多选择3个标签。
封面预览
选择封面后显示预览。
dart
String? _coverPath;
Widget _buildCoverPreview() {
return Center(
child: GestureDetector(
onTap: () => _pickCoverImage(),
child: Container(
width: 120,
height: 120,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
color: const Color(0xFF1E1E1E),
image: _coverPath != null
? DecorationImage(
image: FileImage(File(_coverPath!)),
fit: BoxFit.cover,
)
: null,
),
child: _coverPath == null
? const Column(
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: const EdgeInsets.all(4),
decoration: const BoxDecoration(
color: Colors.black54,
shape: BoxShape.circle,
),
child: const Icon(Icons.edit, size: 16, color: Colors.white),
),
),
],
),
),
),
);
}
如果已选择封面,使用DecorationImage显示图片,右上角显示编辑图标。未选择时显示添加提示。
加载状态处理
创建歌单时显示加载状态。
dart
bool _isLoading = false;
void _createPlaylist() async {
if (!_validateInput()) return;
setState(() => _isLoading = true);
try {
// 模拟网络请求
await Future.delayed(const Duration(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确保状态恢复。
完成按钮状态
根据加载状态和输入内容控制按钮状态。
dart
Widget _buildSubmitButton() {
return TextButton(
onPressed: _isLoading || _nameController.text.trim().isEmpty
? null
: () => _createPlaylist(),
child: _isLoading
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(Color(0xFFE91E63)),
),
)
: const Text(
'完成',
style: TextStyle(color: Color(0xFFE91E63)),
),
);
}
加载中显示CircularProgressIndicator,名称为空时按钮禁用。这种设计提供了清晰的状态反馈。
总结
创建歌单页面虽然功能相对简单,但涉及到表单输入、状态管理、输入验证等多个Flutter开发中的常见场景。通过合理的组件拆分和状态管理,让代码结构清晰、易于维护。在实际项目中,还需要对接后端接口实现真正的歌单创建功能,以及使用image_picker等插件实现图片选择。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net