核心内容摘要
泪洒红颜,咬碎铁心:露娜的沉默与呐喊
搜索功能是音乐播放器中使用频率最高的功能之一。
用户可以通过搜索快速找到想听的歌曲、歌手或专辑。
本篇文章将详细介绍如何实现一个功能完善的搜索页面包括搜索建议、热门搜索、搜索历史以及多类型搜索结果展示。
页面基础结构搜索页面使用StatefulWidget因为需要管理搜索框内容、搜索状态等多个状态变量。
importpackage:flutter/material.dart;importpackage:get/get.dart;classSearchPageextendsStatefulWidget{constSearchPage({super.key});overrideStateSearchPagecreateState()_SearchPageState();}页面继承自StatefulWidget使用GetX进行路由管理。
搜索页面的交互比较复杂需要响应用户的输入和点击操作。
状态变量定义搜索页面需要管理多个状态包括输入控制器、Tab控制器和搜索状态标志。
class_SearchPageStateextendsStateSearchPagewithSingleTickerProviderStateMixin{finalTextEditingController_controllerTextEditingController();lateTabController_tabController;bool _showResultfalse;_controller用于控制搜索输入框的内容_tabController用于控制搜索结果的Tab切换_showResult标志决定显示搜索建议还是搜索结果。
混入SingleTickerProviderStateMixin是使用TabController的必要条件。
生命周期管理在initState中初始化TabController在dispose中释放所有控制器资源。
overridevoidinitState(){super.initState();_tabControllerTabController(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.white
,border:InputBorder.none,),TextField设置了autofocus为true进入页面时自动弹出键盘。
输入文字使用白色提示文字使用半透明白色与深色主题协调。
border设置为none让输入框与AppBar融为一体。
搜索提交处理用户按下键盘确认键或点击搜索按钮时触发搜索。
onSubmitted:(v)setState(()_showResultv.isNotEmpty),),actions:[TextButton(onPressed:()setState(()_showResult_controller.text.isNotEmpty),child:constText(搜索,style:TextStyle(color:Color(0xFFE91E
)),),],),onSubmitted回调在用户按下键盘确认键时触发actions区域放置搜索按钮。
两种方式都会检查输入内容是否为空不为空时切换到搜索结果视图。
搜索按钮使用主题色突出显示。
页面内容切换根据_showResult状态决定显示搜索建议还是搜索结果。
body:_showResult?_buildSearchResult():_buildSearchSuggestion(),);}这种条件渲染的方式简洁明了。
当用户还没有进行搜索时显示热门搜索和搜索历史搜索后显示搜索结果列表。
搜索建议页面搜索建议页面包含热门搜索和搜索历史两个部分。
Widget_buildSearchSuggestion(){returnSingleChildScrollView(padding:constEdgeInsets.all(
,child:Column(crossAxisAlignment:CrossAxisAlignment.start,children:[constText(热门搜索,style:TextStyle(fontSize:16,fontWeight:FontWeight.bold),),constSizedBox(height:
,使用SingleChildScrollView包裹整个内容当内容超出屏幕时可以滚动。
Column采用左对齐标题使用粗体突出显示。
热门搜索标签热门搜索使用Wrap组件实现流式布局的标签展示。
Wrap(spacing:8,runSpacing:8,children:List.generate(12,(i)GestureDetector(onTap:(){_controller.text热门${i1};setState(()_showResulttrue);},child:Chip(label:Text(热门${i1}),backgroundColor:constColor(0xFF1E1E1E),),),),),Wrap组件会自动换行spacing和runSpacing分别设置水平和垂直间距。
点击标签时会将标签内容填入搜索框并触发搜索。
Chip组件使用深色背景与整体主题一致。
搜索历史区域搜索历史使用列表形式展示方便用户快速选择之前搜索过的内容。
constSizedBox(height:
,constText(搜索历史,style:TextStyle(fontSize:16,fontWeight:FontWeight.bold),),constSizedBox(height:
,ListView.builder(shrinkWrap:true,physics:constNeverScrollableScrollPhysics(),itemCount:5,itemBuilder:(context,i)ListTile(leading:constIcon(Icons.history,color:Colors.grey),title:Text(历史搜索${i1}),trailing:constIcon(Icons.north_west,color:Colors.grey,size:
,),),],),);}ListView.builder设置shrinkWrap为true让列表高度自适应内容。
physics设置为NeverScrollableScrollPhysics禁用列表自身的滚动由外层SingleChildScrollView统一处理。
每个历史记录前面有时钟图标尾部的箭头图标表示点击可以填入搜索框。
搜索结果页面结构搜索结果页面使用TabBar和TabBarView实现多类型结果切换。
Widget_buildSearchResult(){returnColumn(children:[TabBar(controller:_tabController,isScrollable:true,labelColor:constColor(0xFFE91E
,unselectedLabelColor:Colors.grey,indicatorColor:constColor(0xFFE91E
,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(${i1},style:constTextStyle(color:Colors.grey),),title:Text(搜索结果${i1}),subtitle:constText(歌手名),trailing:constIcon(Icons.play_circle_outline,color:Color(0xFFE91E
,),),);}每首歌曲前面显示序号中间显示歌曲名和歌手名尾部是播放按钮。
播放按钮使用主题色点击可以直接播放歌曲。
歌手搜索结果歌手列表使用圆形头像展示。
Widget_buildArtistList(){returnListView.builder(itemCount:10,itemBuilder:(context,i)ListTile(leading:CircleAvatar(backgroundColor:Colors.primaries[i%Colors.primaries.length].withOpacity(
0.
,child:constIcon(Icons.person,color:Colors.white
,),title:Text(歌手${i1}),subtitle:Text(${(i
*100}首歌曲),),);}CircleAvatar用于显示歌手头像背景色根据索引变化。
副标题显示歌手的歌曲数量帮助用户了解歌手的作品规模。
专辑搜索结果专辑列表使用方形封面展示。
Widget_buildAlbumList(){returnListView.builder(itemCount:10,itemBuilder:(context,i)ListTile(leading:Container(width:50,height:50,decoration:BoxDecoration(borderRadius:BorderRadius.circular(
,color:Colors.primaries[i%Colors.primaries.length].withOpacity(
0.
,),child:constIcon(Icons.album,color:Colors.white
,),title:Text(专辑${i1}),subtitle:constText(歌手),),);}专辑封面使用圆角矩形与歌手的圆形头像形成区分。
Container设置固定宽高保证封面比例一致。
歌单搜索结果歌单列表的样式与专辑类似但图标不同。
Widget_buildPlaylistList(){returnListView.builder(itemCount:10,itemBuilder:(context,i)ListTile(leading:Container(width:50,height:50,decoration:BoxDecoration(borderRadius:BorderRadius.circular(
,color:Colors.primaries[i%Colors.primaries.length].withOpacity(
0.
,),child:constIcon(Icons.queue_music,color:Colors.white
,),title:Text(歌单${i1}),subtitle:Text(${(i
*50}首),),);}歌单使用queue_music图标副标题显示歌单包含的歌曲数量。
这种统一的列表样式让用户容易理解和操作。
MV搜索结果MV使用网格布局展示更适合视频类内容。
Widget_buildMVGrid(){returnGridView.builder(padding:constEdgeInsets.all(
,gridDelegate:constSliverGridDelegateWithFixedCrossAxisCount(crossAxisCount:2,childAspectRatio:
5,crossAxisSpacing:12,mainAxisSpacing:12,),itemCount:10,itemBuilder:(context,i)Container(decoration:BoxDecoration(borderRadius:BorderRadius.circular(
,color:Colors.primaries[i%Colors.primaries.length].withOpacity(
0.
,),child:constCenter(child:Icon(Icons.play_circle_filled,size:40,color:Colors.white
,),),);}GridView使用SliverGridDelegateWithFixedCrossAxisCount设置每行2个childAspectRatio设置为
5让MV封面呈现横向矩形。
每个MV卡片中间显示播放图标点击可以播放MV。
搜索防抖处理为了避免频繁请求接口可以添加搜索防抖功能。
Timer?_debounceTimer;void_onSearchChanged(Stringvalue){_debounceTimer?.cancel();_debounceTimerTimer(constDuration(milliseconds:
,(){if(value.isNotEmpty){_performSearch(value);}});}void_performSearch(Stringkeyword){setState(()_showResulttrue);// 调用搜索接口}使用Timer实现防抖用户停止输入500毫秒后才执行搜索。
每次输入变化时先取消之前的定时器避免重复请求。
清空搜索框搜索框右侧可以添加清空按钮。
Widget_buildClearButton(){if(_controller.text.isEmpty)returnconstSizedBox.shrink();returnIconButton(icon:constIcon(Icons.clear,color:Colors.grey),onPressed:(){_controller.clear();setState(()_showResultfalse);},);}只有当搜索框有内容时才显示清空按钮。
点击后清空输入内容并返回搜索建议页面。
SizedBox.shrink()返回一个零尺寸的Widget不占用任何空间。
搜索历史管理搜索历史可以使用SharedPreferences进行本地存储。
Futurevoid_saveSearchHistory(Stringkeyword)async{finalprefsawaitSharedPreferences.getInstance();ListStringhistoryprefs.getStringList(search_history)??[];history.remove(keyword);history.insert(0,keyword);if(history.length
{historyhistory.sublist(0,
;}awaitprefs.setStringList(search_history,history);}新的搜索词插入到列表开头如果已存在则先删除再插入保证最新搜索的在最前面。
限制历史记录最多20条避免占用过多存储空间。
总结搜索功能的实现涉及到多个Flutter组件的综合运用TextField实现搜索输入、Wrap实现流式标签布局、TabBar和TabBarView实现多类型结果切换、ListView和GridView实现不同形式的列表展示。
通过合理的状态管理和UI设计为用户提供了流畅的搜索体验。
在实际项目中还需要对接后端搜索接口实现真正的搜索功能。
欢迎加入开源鸿蒙跨平台社区https://openharmonycrossplatform.csdn.net