Flutter for OpenHarmony音乐播放器App实战:搜索实现
本文介绍了音乐播放器搜索页面的实现方法,包含搜索建议和搜索结果两大核心功能。搜索建议部分采用Wrap组件展示热门搜索标签,ListView展示搜索历史;搜索结果使用TabBar切换不同类型。页面状态管理通过TextEditingController和TabController实现,并根据用户输入动态切换视图。整体设计遵循音乐类App的常见交互模式,提供流畅的搜索体验。

搜索功能是音乐播放器中使用频率最高的功能之一。用户可以通过搜索快速找到想听的歌曲、歌手或专辑。本篇文章将详细介绍如何实现一个功能完善的搜索页面,包括搜索建议、热门搜索、搜索历史以及多类型搜索结果展示。
页面基础结构
搜索页面使用StatefulWidget,因为需要管理搜索框内容、搜索状态等多个状态变量。
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class SearchPage extends StatefulWidget {
const SearchPage({super.key});
State<SearchPage> createState() => _SearchPageState();
}
页面继承自StatefulWidget,使用GetX进行路由管理。搜索页面的交互比较复杂,需要响应用户的输入和点击操作。
状态变量定义
搜索页面需要管理多个状态,包括输入控制器、Tab控制器和搜索状态标志。
class _SearchPageState extends State<SearchPage> with SingleTickerProviderStateMixin {
final TextEditingController _controller = TextEditingController();
late TabController _tabController;
bool _showResult = false;
_controller用于控制搜索输入框的内容,_tabController用于控制搜索结果的Tab切换,_showResult标志决定显示搜索建议还是搜索结果。混入SingleTickerProviderStateMixin是使用TabController的必要条件。
生命周期管理
在initState中初始化TabController,在dispose中释放所有控制器资源。
void initState() {
super.initState();
_tabController = TabController(length: 5, vsync: this);
}
void dispose() {
_controller.dispose();
_tabController.dispose();
super.dispose();
}
TabController的length设置为5,对应单曲、歌手、专辑、歌单、MV五个搜索类型。dispose方法中同时释放输入控制器和Tab控制器,避免内存泄漏。
AppBar搜索框设计
搜索框直接放在AppBar的title位置,这是音乐类App常见的设计模式。
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: TextField(
controller: _controller,
autofocus: true,
style: const TextStyle(color: Colors.white),
decoration: const InputDecoration(
hintText: '搜索歌曲、歌手、专辑',
hintStyle: TextStyle(color: Colors.white54),
border: InputBorder.none,
),
TextField设置了autofocus为true,进入页面时自动弹出键盘。输入文字使用白色,提示文字使用半透明白色,与深色主题协调。border设置为none,让输入框与AppBar融为一体。
搜索提交处理
用户按下键盘确认键或点击搜索按钮时触发搜索。
onSubmitted: (v) => setState(() => _showResult = v.isNotEmpty),
),
actions: [
TextButton(
onPressed: () => setState(() => _showResult = _controller.text.isNotEmpty),
child: const Text('搜索', style: TextStyle(color: Color(0xFFE91E63))),
),
],
),
onSubmitted回调在用户按下键盘确认键时触发,actions区域放置搜索按钮。两种方式都会检查输入内容是否为空,不为空时切换到搜索结果视图。搜索按钮使用主题色,突出显示。
页面内容切换
根据_showResult状态决定显示搜索建议还是搜索结果。
body: _showResult ? _buildSearchResult() : _buildSearchSuggestion(),
);
}
这种条件渲染的方式简洁明了。当用户还没有进行搜索时显示热门搜索和搜索历史,搜索后显示搜索结果列表。
搜索建议页面
搜索建议页面包含热门搜索和搜索历史两个部分。
Widget _buildSearchSuggestion() {
return SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'热门搜索',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
const SizedBox(height: 12),
使用SingleChildScrollView包裹整个内容,当内容超出屏幕时可以滚动。Column采用左对齐,标题使用粗体突出显示。
热门搜索标签
热门搜索使用Wrap组件实现流式布局的标签展示。
Wrap(
spacing: 8,
runSpacing: 8,
children: List.generate(
12,
(i) => GestureDetector(
onTap: () {
_controller.text = '热门 ${i + 1}';
setState(() => _showResult = true);
},
child: Chip(
label: Text('热门 ${i + 1}'),
backgroundColor: const Color(0xFF1E1E1E),
),
),
),
),
Wrap组件会自动换行,spacing和runSpacing分别设置水平和垂直间距。点击标签时会将标签内容填入搜索框并触发搜索。Chip组件使用深色背景,与整体主题一致。
搜索历史区域
搜索历史使用列表形式展示,方便用户快速选择之前搜索过的内容。
const SizedBox(height: 24),
const Text(
'搜索历史',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
const SizedBox(height: 12),
ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: 5,
itemBuilder: (context, i) => ListTile(
leading: const Icon(Icons.history, color: Colors.grey),
title: Text('历史搜索 ${i + 1}'),
trailing: const Icon(Icons.north_west, color: Colors.grey, size: 16),
),
),
],
),
);
}
ListView.builder设置shrinkWrap为true,让列表高度自适应内容。physics设置为NeverScrollableScrollPhysics,禁用列表自身的滚动,由外层SingleChildScrollView统一处理。每个历史记录前面有时钟图标,尾部的箭头图标表示点击可以填入搜索框。
搜索结果页面结构
搜索结果页面使用TabBar和TabBarView实现多类型结果切换。
Widget _buildSearchResult() {
return Column(
children: [
TabBar(
controller: _tabController,
isScrollable: true,
labelColor: const Color(0xFFE91E63),
unselectedLabelColor: Colors.grey,
indicatorColor: const Color(0xFFE91E63),
tabs: const [
Tab(text: '单曲'),
Tab(text: '歌手'),
Tab(text: '专辑'),
Tab(text: '歌单'),
Tab(text: 'MV'),
],
),
TabBar设置isScrollable为true,当Tab数量较多时可以横向滚动。选中的Tab使用主题色,未选中使用灰色。五个Tab分别对应不同类型的搜索结果。
TabBarView内容区域
TabBarView包含五个不同类型的搜索结果列表。
Expanded(
child: TabBarView(
controller: _tabController,
children: [
_buildSongList(),
_buildArtistList(),
_buildAlbumList(),
_buildPlaylistList(),
_buildMVGrid(),
],
),
),
],
);
}
使用Expanded让TabBarView占据剩余空间。每个Tab对应一个构建方法,分别构建不同类型的结果列表。
单曲搜索结果
单曲列表展示搜索到的歌曲。
Widget _buildSongList() {
return ListView.builder(
itemCount: 20,
itemBuilder: (context, i) => ListTile(
leading: Text(
'${i + 1}',
style: const TextStyle(color: Colors.grey),
),
title: Text('搜索结果 ${i + 1}'),
subtitle: const Text('歌手名'),
trailing: const Icon(
Icons.play_circle_outline,
color: Color(0xFFE91E63),
),
),
);
}
每首歌曲前面显示序号,中间显示歌曲名和歌手名,尾部是播放按钮。播放按钮使用主题色,点击可以直接播放歌曲。
歌手搜索结果
歌手列表使用圆形头像展示。
Widget _buildArtistList() {
return ListView.builder(
itemCount: 10,
itemBuilder: (context, i) => ListTile(
leading: CircleAvatar(
backgroundColor: Colors.primaries[i % Colors.primaries.length].withOpacity(0.3),
child: const Icon(Icons.person, color: Colors.white70),
),
title: Text('歌手 ${i + 1}'),
subtitle: Text('${(i + 1) * 100} 首歌曲'),
),
);
}
CircleAvatar用于显示歌手头像,背景色根据索引变化。副标题显示歌手的歌曲数量,帮助用户了解歌手的作品规模。
专辑搜索结果
专辑列表使用方形封面展示。
Widget _buildAlbumList() {
return ListView.builder(
itemCount: 10,
itemBuilder: (context, i) => ListTile(
leading: Container(
width: 50,
height: 50,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
color: Colors.primaries[i % Colors.primaries.length].withOpacity(0.3),
),
child: const Icon(Icons.album, color: Colors.white70),
),
title: Text('专辑 ${i + 1}'),
subtitle: const Text('歌手'),
),
);
}
专辑封面使用圆角矩形,与歌手的圆形头像形成区分。Container设置固定宽高,保证封面比例一致。
歌单搜索结果
歌单列表的样式与专辑类似,但图标不同。
Widget _buildPlaylistList() {
return ListView.builder(
itemCount: 10,
itemBuilder: (context, i) => ListTile(
leading: Container(
width: 50,
height: 50,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
color: Colors.primaries[i % Colors.primaries.length].withOpacity(0.3),
),
child: const Icon(Icons.queue_music, color: Colors.white70),
),
title: Text('歌单 ${i + 1}'),
subtitle: Text('${(i + 1) * 50} 首'),
),
);
}
歌单使用queue_music图标,副标题显示歌单包含的歌曲数量。这种统一的列表样式让用户容易理解和操作。
MV搜索结果
MV使用网格布局展示,更适合视频类内容。
Widget _buildMVGrid() {
return GridView.builder(
padding: const EdgeInsets.all(16),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
childAspectRatio: 1.5,
crossAxisSpacing: 12,
mainAxisSpacing: 12,
),
itemCount: 10,
itemBuilder: (context, i) => Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
color: Colors.primaries[i % Colors.primaries.length].withOpacity(0.3),
),
child: const Center(
child: Icon(Icons.play_circle_filled, size: 40, color: Colors.white70),
),
),
);
}
GridView使用SliverGridDelegateWithFixedCrossAxisCount设置每行2个,childAspectRatio设置为1.5,让MV封面呈现横向矩形。每个MV卡片中间显示播放图标,点击可以播放MV。
搜索防抖处理
为了避免频繁请求接口,可以添加搜索防抖功能。
Timer? _debounceTimer;
void _onSearchChanged(String value) {
_debounceTimer?.cancel();
_debounceTimer = Timer(const Duration(milliseconds: 500), () {
if (value.isNotEmpty) {
_performSearch(value);
}
});
}
void _performSearch(String keyword) {
setState(() => _showResult = true);
// 调用搜索接口
}
使用Timer实现防抖,用户停止输入500毫秒后才执行搜索。每次输入变化时先取消之前的定时器,避免重复请求。
清空搜索框
搜索框右侧可以添加清空按钮。
Widget _buildClearButton() {
if (_controller.text.isEmpty) return const SizedBox.shrink();
return IconButton(
icon: const Icon(Icons.clear, color: Colors.grey),
onPressed: () {
_controller.clear();
setState(() => _showResult = false);
},
);
}
只有当搜索框有内容时才显示清空按钮。点击后清空输入内容并返回搜索建议页面。SizedBox.shrink()返回一个零尺寸的Widget,不占用任何空间。
搜索历史管理
搜索历史可以使用SharedPreferences进行本地存储。
Future<void> _saveSearchHistory(String keyword) async {
final prefs = await SharedPreferences.getInstance();
List<String> history = prefs.getStringList('search_history') ?? [];
history.remove(keyword);
history.insert(0, keyword);
if (history.length > 20) {
history = history.sublist(0, 20);
}
await prefs.setStringList('search_history', history);
}
新的搜索词插入到列表开头,如果已存在则先删除再插入,保证最新搜索的在最前面。限制历史记录最多20条,避免占用过多存储空间。
总结
搜索功能的实现涉及到多个Flutter组件的综合运用:TextField实现搜索输入、Wrap实现流式标签布局、TabBar和TabBarView实现多类型结果切换、ListView和GridView实现不同形式的列表展示。通过合理的状态管理和UI设计,为用户提供了流畅的搜索体验。在实际项目中,还需要对接后端搜索接口,实现真正的搜索功能。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐



所有评论(0)