news 2026/2/16 1:10:55

Flutter for OpenHarmony音乐播放器App实战08:搜索实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Flutter for OpenHarmony音乐播放器App实战08:搜索实现

搜索功能是音乐播放器中使用频率最高的功能之一。用户可以通过搜索快速找到想听的歌曲、歌手或专辑。本篇文章将详细介绍如何实现一个功能完善的搜索页面,包括搜索建议、热门搜索、搜索历史以及多类型搜索结果展示。

页面基础结构

搜索页面使用StatefulWidget,因为需要管理搜索框内容、搜索状态等多个状态变量。

import'package:flutter/material.dart';import'package:get/get.dart';classSearchPageextendsStatefulWidget{constSearchPage({super.key});@overrideState<SearchPage>createState()=>_SearchPageState();}

页面继承自StatefulWidget,使用GetX进行路由管理。搜索页面的交互比较复杂,需要响应用户的输入和点击操作。

状态变量定义

搜索页面需要管理多个状态,包括输入控制器、Tab控制器和搜索状态标志。

class_SearchPageStateextendsState<SearchPage>withSingleTickerProviderStateMixin{finalTextEditingController_controller=TextEditingController();lateTabController_tabController;bool _showResult=false;

_controller用于控制搜索输入框的内容,_tabController用于控制搜索结果的Tab切换,_showResult标志决定显示搜索建议还是搜索结果。混入SingleTickerProviderStateMixin是使用TabController的必要条件。

生命周期管理

在initState中初始化TabController,在dispose中释放所有控制器资源。

@overridevoidinitState(){super.initState();_tabController=TabController(length:5,vsync:this);}@overridevoiddispose(){_controller.dispose();_tabController.dispose();super.dispose();}

TabController的length设置为5,对应单曲、歌手、专辑、歌单、MV五个搜索类型。dispose方法中同时释放输入控制器和Tab控制器,避免内存泄漏。

AppBar搜索框设计

搜索框直接放在AppBar的title位置,这是音乐类App常见的设计模式。

@overrideWidgetbuild(BuildContextcontext){returnScaffold(appBar:AppBar(title:TextField(controller:_controller,autofocus:true,style:constTextStyle(color:Colors.white),decoration:constInputDecoration(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:constText('搜索',style:TextStyle(color:Color(0xFFE91E63))),),],),

onSubmitted回调在用户按下键盘确认键时触发,actions区域放置搜索按钮。两种方式都会检查输入内容是否为空,不为空时切换到搜索结果视图。搜索按钮使用主题色,突出显示。

页面内容切换

根据_showResult状态决定显示搜索建议还是搜索结果。

body:_showResult?_buildSearchResult():_buildSearchSuggestion(),);}

这种条件渲染的方式简洁明了。当用户还没有进行搜索时显示热门搜索和搜索历史,搜索后显示搜索结果列表。

搜索建议页面

搜索建议页面包含热门搜索和搜索历史两个部分。

Widget_buildSearchSuggestion(){returnSingleChildScrollView(padding:constEdgeInsets.all(16),child:Column(crossAxisAlignment:CrossAxisAlignment.start,children:[constText('热门搜索',style:TextStyle(fontSize:16,fontWeight:FontWeight.bold),),constSizedBox(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:constColor(0xFF1E1E1E),),),),),

Wrap组件会自动换行,spacing和runSpacing分别设置水平和垂直间距。点击标签时会将标签内容填入搜索框并触发搜索。Chip组件使用深色背景,与整体主题一致。

搜索历史区域

搜索历史使用列表形式展示,方便用户快速选择之前搜索过的内容。

constSizedBox(height:24),constText('搜索历史',style:TextStyle(fontSize:16,fontWeight:FontWeight.bold),),constSizedBox(height:12),ListView.builder(shrinkWrap:true,physics:constNeverScrollableScrollPhysics(),itemCount:5,itemBuilder:(context,i)=>ListTile(leading:constIcon(Icons.history,color:Colors.grey),title:Text('历史搜索${i+1}'),trailing:constIcon(Icons.north_west,color:Colors.grey,size:16),),),],),);}

ListView.builder设置shrinkWrap为true,让列表高度自适应内容。physics设置为NeverScrollableScrollPhysics,禁用列表自身的滚动,由外层SingleChildScrollView统一处理。每个历史记录前面有时钟图标,尾部的箭头图标表示点击可以填入搜索框。

搜索结果页面结构

搜索结果页面使用TabBar和TabBarView实现多类型结果切换。

Widget_buildSearchResult(){returnColumn(children:[TabBar(controller:_tabController,isScrollable:true,labelColor:constColor(0xFFE91E63),unselectedLabelColor:Colors.grey,indicatorColor:constColor(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(){returnListView.builder(itemCount:20,itemBuilder:(context,i)=>ListTile(leading:Text('${i+1}',style:constTextStyle(color:Colors.grey),),title:Text('搜索结果${i+1}'),subtitle:constText('歌手名'),trailing:constIcon(Icons.play_circle_outline,color:Color(0xFFE91E63),),),);}

每首歌曲前面显示序号,中间显示歌曲名和歌手名,尾部是播放按钮。播放按钮使用主题色,点击可以直接播放歌曲。

歌手搜索结果

歌手列表使用圆形头像展示。

Widget_buildArtistList(){returnListView.builder(itemCount:10,itemBuilder:(context,i)=>ListTile(leading:CircleAvatar(backgroundColor:Colors.primaries[i%Colors.primaries.length].withOpacity(0.3),child:constIcon(Icons.person,color:Colors.white70),),title:Text('歌手${i+1}'),subtitle:Text('${(i+1)*100}首歌曲'),),);}

CircleAvatar用于显示歌手头像,背景色根据索引变化。副标题显示歌手的歌曲数量,帮助用户了解歌手的作品规模。

专辑搜索结果

专辑列表使用方形封面展示。

Widget_buildAlbumList(){returnListView.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:constIcon(Icons.album,color:Colors.white70),),title:Text('专辑${i+1}'),subtitle:constText('歌手'),),);}

专辑封面使用圆角矩形,与歌手的圆形头像形成区分。Container设置固定宽高,保证封面比例一致。

歌单搜索结果

歌单列表的样式与专辑类似,但图标不同。

Widget_buildPlaylistList(){returnListView.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:constIcon(Icons.queue_music,color:Colors.white70),),title:Text('歌单${i+1}'),subtitle:Text('${(i+1)*50}首'),),);}

歌单使用queue_music图标,副标题显示歌单包含的歌曲数量。这种统一的列表样式让用户容易理解和操作。

MV搜索结果

MV使用网格布局展示,更适合视频类内容。

Widget_buildMVGrid(){returnGridView.builder(padding:constEdgeInsets.all(16),gridDelegate:constSliverGridDelegateWithFixedCrossAxisCount(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:constCenter(child:Icon(Icons.play_circle_filled,size:40,color:Colors.white70),),),);}

GridView使用SliverGridDelegateWithFixedCrossAxisCount设置每行2个,childAspectRatio设置为1.5,让MV封面呈现横向矩形。每个MV卡片中间显示播放图标,点击可以播放MV。

搜索防抖处理

为了避免频繁请求接口,可以添加搜索防抖功能。

Timer?_debounceTimer;void_onSearchChanged(Stringvalue){_debounceTimer?.cancel();_debounceTimer=Timer(constDuration(milliseconds:500),(){if(value.isNotEmpty){_performSearch(value);}});}void_performSearch(Stringkeyword){setState(()=>_showResult=true);// 调用搜索接口}

使用Timer实现防抖,用户停止输入500毫秒后才执行搜索。每次输入变化时先取消之前的定时器,避免重复请求。

清空搜索框

搜索框右侧可以添加清空按钮。

Widget_buildClearButton(){if(_controller.text.isEmpty)returnconstSizedBox.shrink();returnIconButton(icon:constIcon(Icons.clear,color:Colors.grey),onPressed:(){_controller.clear();setState(()=>_showResult=false);},);}

只有当搜索框有内容时才显示清空按钮。点击后清空输入内容并返回搜索建议页面。SizedBox.shrink()返回一个零尺寸的Widget,不占用任何空间。

搜索历史管理

搜索历史可以使用SharedPreferences进行本地存储。

Future<void>_saveSearchHistory(Stringkeyword)async{finalprefs=awaitSharedPreferences.getInstance();List<String>history=prefs.getStringList('search_history')??[];history.remove(keyword);history.insert(0,keyword);if(history.length>20){history=history.sublist(0,20);}awaitprefs.setStringList('search_history',history);}

新的搜索词插入到列表开头,如果已存在则先删除再插入,保证最新搜索的在最前面。限制历史记录最多20条,避免占用过多存储空间。

总结

搜索功能的实现涉及到多个Flutter组件的综合运用:TextField实现搜索输入、Wrap实现流式标签布局、TabBar和TabBarView实现多类型结果切换、ListView和GridView实现不同形式的列表展示。通过合理的状态管理和UI设计,为用户提供了流畅的搜索体验。在实际项目中,还需要对接后端搜索接口,实现真正的搜索功能。

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

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

基于单片机的振动检测仪的设计

文章目录摘要一、系统功能二、总体设计三、效果图源码获取摘要 振动监测系统在工程领域具有广泛的应用价值。本文设计了一种基于单片机的振动监测系统&#xff0c;旨在实时监测机械设备的运行状况&#xff0c;并在设备出现故障时发出预警&#xff0c;以确保设备安全运行。 首先…

作者头像 李华
网站建设 2026/2/16 3:26:19

GB28181视频平台EasyGBS视频质量诊断操作指南:从部署到实操

作为一名长期深耕视频监控领域的开发者&#xff0c;你是否也曾遇到过这些棘手问题&#xff1a;监控画面突然卡顿、视频流中断却找不到根源、设备离线告警延迟导致故障扩大……在传统监控系统中&#xff0c;这些问题往往需要耗费大量时间排查&#xff0c;从设备硬件到网络链路&a…

作者头像 李华
网站建设 2026/2/14 23:19:59

mevan pom.xml常见问题

本文介绍如何使用Maven进行项目打包&#xff0c;并通过配置maven-assembly-plugin和maven-shade-plugin添加依赖及主类。针对打包过程中出现的签名错误问题&#xff0c;提供了具体的解决方案&#xff0c;包括排除不必要的文件和手动添加主类。 打包依赖&#xff0c;同时添加主类…

作者头像 李华