在这里插入图片描述

搜索功能是现代应用中不可或缺的一部分,它让用户能够快速找到自己想要的内容,而不需要在大量信息中慢慢浏览。在游戏中心应用中,搜索功能尤为重要,因为随着游戏数量的增加,用户需要一个高效的方式来定位特定的游戏。本文将详细介绍游戏搜索页面的实现,包括搜索界面设计、实时搜索、搜索历史、搜索建议等功能。

搜索功能的设计理念

一个好的搜索功能应该快速、准确、易用。快速意味着搜索响应要及时,用户输入关键词后能立即看到结果。准确意味着搜索算法要智能,能够理解用户的意图,返回相关度高的结果。易用意味着交互要简单,用户不需要学习就能使用。

在我们的游戏中心应用中,搜索功能的设计遵循以下原则:搜索框放在页面顶部,用户进入页面后立即可以输入;支持实时搜索,用户每输入一个字符就更新搜索结果;提供搜索历史,让用户可以快速重复之前的搜索;显示搜索建议,帮助用户发现可能感兴趣的游戏。

这种设计让搜索功能既强大又易用。用户可以快速找到想玩的游戏,也可以通过搜索发现新的游戏。搜索不仅是一个工具,也是一个游戏发现的渠道。

页面结构的定义

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

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐