Flutter for OpenHarmony 游戏中心App实战:游戏搜索功能实现
本文介绍了游戏中心应用中搜索功能的实现方法,主要包括: 搜索界面设计理念:强调快速响应、准确结果和易用交互,将搜索框置于AppBar顶部,支持实时搜索、历史记录和智能建议。 技术实现细节: 使用StatefulWidget管理搜索状态 通过TextEditingController控制输入内容 采用自动聚焦和简洁UI提升用户体验 实现实时搜索监听和关键词筛选功能 状态管理: 空状态显示友好提示 无

搜索功能是现代应用中不可或缺的一部分,它让用户能够快速找到自己想要的内容,而不需要在大量信息中慢慢浏览。在游戏中心应用中,搜索功能尤为重要,因为随着游戏数量的增加,用户需要一个高效的方式来定位特定的游戏。本文将详细介绍游戏搜索页面的实现,包括搜索界面设计、实时搜索、搜索历史、搜索建议等功能。
搜索功能的设计理念
一个好的搜索功能应该快速、准确、易用。快速意味着搜索响应要及时,用户输入关键词后能立即看到结果。准确意味着搜索算法要智能,能够理解用户的意图,返回相关度高的结果。易用意味着交互要简单,用户不需要学习就能使用。
在我们的游戏中心应用中,搜索功能的设计遵循以下原则:搜索框放在页面顶部,用户进入页面后立即可以输入;支持实时搜索,用户每输入一个字符就更新搜索结果;提供搜索历史,让用户可以快速重复之前的搜索;显示搜索建议,帮助用户发现可能感兴趣的游戏。
这种设计让搜索功能既强大又易用。用户可以快速找到想玩的游戏,也可以通过搜索发现新的游戏。搜索不仅是一个工具,也是一个游戏发现的渠道。
页面结构的定义
GameSearchPage是一个有状态组件,因为它需要维护搜索关键词、搜索结果等可变的状态。让我们从基本的页面结构开始。
class GameSearchPage extends StatefulWidget {
const GameSearchPage({super.key});
State<GameSearchPage> createState() => _GameSearchPageState();
}
StatefulWidget是Flutter中用于创建有状态组件的基类。与StatelessWidget不同,StatefulWidget可以在运行时改变其内部状态,并触发UI的重新构建。搜索页面需要响应用户的输入,实时更新搜索结果,所以必须使用StatefulWidget。
createState方法返回一个State实例,这个实例会在组件的整个生命周期中保持存在。State类中保存了组件的可变状态,以及处理状态变化的逻辑。这种设计将不可变的Widget和可变的State分离,让代码结构更加清晰。
文本输入控制器的使用
搜索功能的核心是文本输入,我们需要一个TextEditingController来管理输入框的内容。
class _GameSearchPageState extends State<GameSearchPage> {
final TextEditingController _searchController = TextEditingController();
TextEditingController是Flutter提供的文本输入控制器,它可以读取和修改TextField的内容,也可以监听文本的变化。使用final修饰表示这个控制器在创建后不会被替换,但它管理的文本内容是可以变化的。
创建TextEditingController时不需要传入初始值,默认是空字符串。如果需要设置初始值,可以在构造函数中传入text参数。控制器创建后,我们可以通过_searchController.text获取当前的文本内容,也可以通过_searchController.text = '新内容’来修改文本。
TextEditingController还提供了addListener方法,可以监听文本的变化。每当用户输入或删除字符时,监听器就会被调用。这个特性非常适合实现实时搜索功能,我们可以在监听器中执行搜索逻辑,实时更新搜索结果。
页面框架的构建
搜索页面的框架与其他页面略有不同,因为搜索框需要放在AppBar中,让用户进入页面后立即可以输入。
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: TextField(
controller: _searchController,
decoration: const InputDecoration(
hintText: '搜索游戏...',
border: InputBorder.none,
),
autofocus: true,
),
backgroundColor: const Color(0xFF16213e),
),
Scaffold的appBar属性接收一个AppBar组件。与普通页面不同,这里的AppBar的title不是Text,而是TextField。这种设计让搜索框占据了AppBar的主要区域,用户可以直接在标题栏输入搜索关键词。
TextField是Flutter中的文本输入组件,controller参数绑定了我们之前创建的_searchController。这样TextField的内容就会与控制器同步,我们可以通过控制器来读取和修改输入的文本。
decoration参数设置了TextField的装饰样式。hintText是占位符文本,当输入框为空时显示"搜索游戏…",提示用户可以输入什么。border设置为InputBorder.none,去掉了输入框的边框,让它与AppBar融为一体,看起来更加简洁。
autofocus设置为true,这是一个很重要的细节。当页面打开时,TextField会自动获得焦点,键盘会自动弹出,用户可以立即开始输入。这种设计减少了用户的操作步骤,提升了搜索的效率。
AppBar的backgroundColor保持与应用主题一致的深蓝色。AppBar左侧会自动显示返回按钮,用户可以点击返回上一个页面。这些都是Scaffold提供的默认行为,不需要我们手动实现。
占位内容的实现
当用户还没有输入搜索关键词时,我们显示一个友好的占位内容,提示用户可以进行搜索。
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.search, size: 80.sp, color: Colors.white30),
SizedBox(height: 20.h),
Text('输入关键词搜索游戏', style: TextStyle(fontSize: 16.sp, color: Colors.white60)),
],
),
),
);
}
}
body部分使用Center组件将内容居中显示。这种居中布局适合展示空状态或占位内容,让页面看起来平衡美观,不会显得空荡荡的。
Column组件垂直排列图标和文字。mainAxisAlignment设置为center,让内容在垂直方向上也居中。这样整个占位内容就会出现在屏幕的正中央,无论屏幕大小如何都能保持良好的视觉效果。
Icon使用了Material Icons中的search图标,size设置为80.sp,这是一个很大的尺寸,让图标成为视觉焦点。color设置为Colors.white30,这是一个非常浅的白色,表明这是一个占位状态,不是主要内容。
SizedBox添加了20.h的垂直间距,将图标和文字分开。适当的间距可以让界面更加舒适,不会显得拥挤。使用flutter_screenutil的适配单位,确保间距在不同设备上保持一致的视觉效果。
Text显示提示文字"输入关键词搜索游戏",字体大小为16.sp,颜色使用Colors.white60,比图标稍深一些,但仍然是次要文本的颜色。这样的设计让用户明白当前的状态,知道下一步应该做什么。
实时搜索的实现
实时搜索是现代搜索功能的标配,用户每输入一个字符,搜索结果就会立即更新。这种即时反馈让搜索体验更加流畅。
要实现实时搜索,我们需要监听TextField的文本变化。在initState方法中添加监听器:
void initState() {
super.initState();
_searchController.addListener(_onSearchChanged);
}
void _onSearchChanged() {
final keyword = _searchController.text;
if (keyword.isEmpty) {
setState(() {
_searchResults = [];
});
return;
}
_performSearch(keyword);
}
initState是State类的生命周期方法,在组件创建时调用一次。我们在这里给_searchController添加监听器,监听器函数是_onSearchChanged。每当用户输入或删除字符时,这个函数就会被调用。
_onSearchChanged方法首先获取当前的搜索关键词。如果关键词为空,说明用户清空了输入框,这时候应该清空搜索结果,显示初始状态。调用setState更新_searchResults为空列表,触发UI重建。
如果关键词不为空,调用_performSearch方法执行实际的搜索逻辑。这个方法会根据关键词筛选游戏列表,更新搜索结果。
void _performSearch(String keyword) {
final results = allGames.where((game) {
return game.name.toLowerCase().contains(keyword.toLowerCase());
}).toList();
setState(() {
_searchResults = results;
});
}
_performSearch方法使用where方法筛选游戏列表。筛选条件是游戏名称包含搜索关键词。为了实现不区分大小写的搜索,我们将游戏名称和关键词都转换为小写后再比较。
contains方法检查字符串是否包含子串,这是一个简单但有效的搜索算法。对于更复杂的需求,可以使用模糊匹配、拼音搜索等算法。toList方法将筛选结果转换为列表。
最后调用setState更新_searchResults,触发UI重建,显示新的搜索结果。整个搜索过程非常快,用户几乎感觉不到延迟。
搜索结果的展示
当有搜索结果时,我们需要用一个列表来展示这些游戏。ListView是展示列表的最佳选择。
body: _searchController.text.isEmpty
? _buildEmptyState()
: _searchResults.isEmpty
? _buildNoResultState()
: ListView.builder(
padding: EdgeInsets.all(16.w),
itemCount: _searchResults.length,
itemBuilder: (context, index) {
final game = _searchResults[index];
return _buildGameItem(game);
},
),
这段代码使用嵌套的三元运算符来决定显示什么内容。如果搜索框为空,显示空状态(占位内容)。如果搜索框不为空但没有搜索结果,显示无结果状态。如果有搜索结果,显示结果列表。
ListView.builder是构建列表的高效方式,它只会渲染可见的项目。padding添加了16.w的内边距,让列表内容不会紧贴屏幕边缘。itemCount设置为搜索结果的数量,itemBuilder为每个结果创建一个Widget。
_buildGameItem方法负责构建单个游戏项的UI:
Widget _buildGameItem(GameModel game) {
return Container(
margin: EdgeInsets.only(bottom: 12.h),
child: ListTile(
leading: Container(
width: 50.w,
height: 50.w,
decoration: BoxDecoration(
color: Colors.purpleAccent.withOpacity(0.3),
borderRadius: BorderRadius.circular(12.r),
),
child: Center(child: Text(game.icon, style: TextStyle(fontSize: 24.sp))),
),
title: Text(game.name, style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.bold)),
subtitle: Text(game.category, style: TextStyle(fontSize: 12.sp, color: Colors.white60)),
trailing: const Icon(Icons.arrow_forward_ios, size: 16),
tileColor: const Color(0xFF16213e),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12.r)),
onTap: () => _onGameTap(game),
),
);
}
每个游戏项使用ListTile组件,这是Flutter中展示列表项的标准组件。leading显示游戏图标,title显示游戏名称,subtitle显示游戏分类,trailing显示一个箭头图标表示可以点击。
tileColor设置了ListTile的背景色,shape添加了圆角,让列表项看起来更加精致。onTap回调处理点击事件,点击游戏项时可以跳转到游戏详情页或直接启动游戏。
margin添加了底部间距,让列表项之间有一定的间隔。这种卡片式的列表设计比紧密排列的列表更加美观,也更容易点击。
搜索历史的实现
搜索历史可以让用户快速重复之前的搜索,这是一个非常实用的功能。我们需要保存用户的搜索关键词,并在搜索框为空时显示历史记录。
List<String> _searchHistory = [];
void _addToHistory(String keyword) {
if (keyword.isEmpty) return;
_searchHistory.remove(keyword);
_searchHistory.insert(0, keyword);
if (_searchHistory.length > 10) {
_searchHistory = _searchHistory.sublist(0, 10);
}
_saveHistory();
}
_searchHistory列表保存搜索历史。_addToHistory方法将新的搜索关键词添加到历史记录中。首先检查关键词是否为空,空关键词不应该被保存。
然后调用remove方法移除列表中已存在的相同关键词,避免重复。insert方法将新关键词插入到列表开头,这样最新的搜索总是显示在最前面。
如果历史记录超过10条,只保留最新的10条。这样可以避免历史记录无限增长,占用过多存储空间。sublist方法返回列表的一个子集,从索引0到索引10(不包括10)。
最后调用_saveHistory方法将历史记录保存到本地存储。这样即使用户关闭应用,下次打开时仍然可以看到之前的搜索历史。
显示搜索历史的UI:
Widget _buildSearchHistory() {
if (_searchHistory.isEmpty) {
return _buildEmptyState();
}
return ListView.builder(
padding: EdgeInsets.all(16.w),
itemCount: _searchHistory.length + 1,
itemBuilder: (context, index) {
if (index == 0) {
return Padding(
padding: EdgeInsets.only(bottom: 12.h),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('搜索历史', style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.bold)),
TextButton(
onPressed: _clearHistory,
child: const Text('清空'),
),
],
),
);
}
final keyword = _searchHistory[index - 1];
return ListTile(
leading: const Icon(Icons.history),
title: Text(keyword),
trailing: IconButton(
icon: const Icon(Icons.close, size: 16),
onPressed: () => _removeFromHistory(keyword),
),
onTap: () {
_searchController.text = keyword;
_performSearch(keyword);
},
);
},
);
}
这段代码构建了搜索历史的UI。第一项是标题行,显示"搜索历史"文字和"清空"按钮。后面的项是历史记录,每条记录显示为一个ListTile。
leading显示历史图标,title显示搜索关键词,trailing显示一个删除按钮,可以单独删除某条历史记录。点击历史记录时,将关键词填入搜索框并执行搜索,让用户可以快速重复之前的搜索。
搜索建议的实现
搜索建议可以帮助用户发现可能感兴趣的游戏,也可以纠正用户的输入错误。当用户输入关键词时,我们可以显示一些相关的建议。
List<String> _getSuggestions(String keyword) {
if (keyword.isEmpty) return [];
final suggestions = <String>[];
// 添加匹配的游戏名称
for (final game in allGames) {
if (game.name.toLowerCase().contains(keyword.toLowerCase())) {
suggestions.add(game.name);
}
}
// 添加匹配的分类
for (final category in gameCategories) {
if (category.toLowerCase().contains(keyword.toLowerCase())) {
suggestions.add(category);
}
}
return suggestions.take(5).toList();
}
_getSuggestions方法根据关键词生成搜索建议。首先检查关键词是否为空,空关键词不需要建议。
然后遍历所有游戏,找出名称包含关键词的游戏,将游戏名称添加到建议列表。同样的方式处理游戏分类,如果分类名称包含关键词,也添加到建议列表。
最后使用take方法只保留前5条建议,避免建议列表过长。toList方法将结果转换为列表。
显示搜索建议的UI可以使用一个下拉列表,或者在搜索框下方显示一个浮动的建议面板。用户点击建议时,将建议填入搜索框并执行搜索。
搜索性能的优化
当游戏数量很多时,实时搜索可能会影响性能。每次用户输入一个字符,都要遍历整个游戏列表进行匹配,这可能会导致卡顿。
一个优化方法是使用防抖(debounce)技术。不是每次输入都立即搜索,而是等待用户停止输入一段时间(比如300毫秒)后再执行搜索。这样可以减少搜索次数,提高性能。
Timer? _debounceTimer;
void _onSearchChanged() {
_debounceTimer?.cancel();
_debounceTimer = Timer(const Duration(milliseconds: 300), () {
final keyword = _searchController.text;
if (keyword.isEmpty) {
setState(() {
_searchResults = [];
});
} else {
_performSearch(keyword);
}
});
}
这段代码实现了防抖功能。每次文本变化时,先取消之前的定时器,然后创建一个新的定时器。只有当用户停止输入300毫秒后,定时器才会触发,执行实际的搜索逻辑。
另一个优化方法是使用索引。预先为游戏名称建立索引,搜索时直接查询索引而不是遍历列表。对于大量数据,这种方法可以显著提高搜索速度。
还可以使用异步搜索,将搜索逻辑放在单独的isolate中执行,避免阻塞UI线程。这种方法适合非常复杂的搜索算法,对于简单的字符串匹配可能有些过度设计。
搜索结果的高亮
为了让用户更容易看到匹配的部分,我们可以在搜索结果中高亮显示关键词。这需要对文本进行特殊处理,将匹配的部分用不同的颜色显示。
Widget _buildHighlightedText(String text, String keyword) {
if (keyword.isEmpty) {
return Text(text);
}
final lowerText = text.toLowerCase();
final lowerKeyword = keyword.toLowerCase();
final index = lowerText.indexOf(lowerKeyword);
if (index == -1) {
return Text(text);
}
return RichText(
text: TextSpan(
style: TextStyle(fontSize: 16.sp, color: Colors.white),
children: [
TextSpan(text: text.substring(0, index)),
TextSpan(
text: text.substring(index, index + keyword.length),
style: const TextStyle(color: Colors.amber, fontWeight: FontWeight.bold),
),
TextSpan(text: text.substring(index + keyword.length)),
],
),
);
}
这个方法将文本分成三部分:关键词之前的部分、关键词本身、关键词之后的部分。关键词部分使用琥珀色和粗体显示,其他部分使用普通样式。
RichText组件可以在一个文本中使用多种样式,非常适合实现高亮效果。TextSpan定义了文本的一个片段及其样式,多个TextSpan组合在一起形成完整的文本。
这种高亮效果让搜索结果更加直观,用户可以一眼看出哪些游戏匹配了搜索关键词,以及匹配的是哪个部分。
搜索过滤器的实现
除了关键词搜索,我们还可以提供过滤器,让用户可以按照分类、评分、热度等条件筛选游戏。
class SearchFilter {
String? category;
double? minRating;
int? minPlayCount;
SearchFilter({this.category, this.minRating, this.minPlayCount});
}
SearchFilter _filter = SearchFilter();
List<GameModel> _applyFilter(List<GameModel> games) {
var filtered = games;
if (_filter.category != null) {
filtered = filtered.where((game) => game.category == _filter.category).toList();
}
if (_filter.minRating != null) {
filtered = filtered.where((game) => game.rating >= _filter.minRating!).toList();
}
if (_filter.minPlayCount != null) {
filtered = filtered.where((game) => game.playCount >= _filter.minPlayCount!).toList();
}
return filtered;
}
SearchFilter类定义了过滤条件,包括分类、最低评分、最低游玩次数。所有字段都是可空的,表示这些过滤条件是可选的。
_applyFilter方法应用过滤条件。它接收一个游戏列表,根据设置的过滤条件逐步筛选,最后返回符合所有条件的游戏。这种链式筛选的方式代码清晰,也便于添加新的过滤条件。
在UI上,可以在AppBar中添加一个过滤按钮,点击时弹出一个对话框让用户设置过滤条件。也可以在搜索框下方显示一排过滤标签,用户点击标签切换过滤选项。
总结
本文详细介绍了游戏搜索功能的实现。我们从搜索的设计理念开始,确定了快速、准确、易用的设计目标。然后实现了GameSearchPage页面,包括搜索框、占位内容、实时搜索、搜索结果展示等核心功能。
我们还讨论了搜索历史、搜索建议、性能优化、结果高亮、搜索过滤器等扩展功能。这些功能可以根据实际需求逐步实现,让搜索功能更加强大和易用。
搜索功能是提升用户体验的重要手段,一个好的搜索功能可以让用户快速找到想要的内容,减少浏览时间,提高应用的使用效率。通过本文的学习,你掌握了搜索功能的实现方法,这些知识可以应用到各种类型的应用中。
在下一篇文章中,我们将实现精选游戏功能,展示编辑推荐的优质游戏。精选功能会涉及到数据排序、推荐算法、特殊标记等内容,敬请期待。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐



所有评论(0)