news 2026/9/3 2:46:09

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/9/3 0:00:48

nodejs毕设选题推荐:基于nodejs的自习室座位预约系统的设计与实现【附源码、mysql、文档、调试+代码讲解+全bao等】

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

作者头像 李华
网站建设 2026/9/3 1:48:59

基于Springboot医院门诊挂号系统【附源码+文档】

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

作者头像 李华
网站建设 2026/9/2 23:58:16

基于Presto的多数据源统一多维分析方案

基于Presto的多数据源统一多维分析方案:让数据“讲同一种语言” 1. 引入:当分析师变成“数据搬运工” 凌晨1点,某电商公司的数据分析师小夏还在电脑前揉着眼睛——他要做一份“2023年Q4用户购买行为与库存周转分析”,但眼前的困境比Excel的公式错误更让人崩溃: 用户点击…

作者头像 李华
网站建设 2026/9/2 23:11:11

收藏备用|大模型6大核心职业方向拆解,小白程序员也能快速入局

当下&#xff0c;大模型技术正以不可阻挡之势渗透各行各业&#xff0c;从日常智能交互到产业深度升级&#xff0c;其应用身影无处不在&#xff0c;更给程序员群体开辟了一条全新的职业上升赛道&#xff0c;成为转型加薪的核心突破口。本文专为想要转型大模型领域的小白程序员、…

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

【课程设计/毕业设计】基于.Net智慧阅读书城系统的设计与实现基于net的网上书城系统线上阳光好书系统【附源码、数据库、万字文档】

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

作者头像 李华