2015 年 5 月 12 日
教程:响应式实时搜索
今天的客座教程由 Corona 大使、俄勒冈州波特兰市的应用程序开发人员 Ed Maurina 友情提供。Ed 是每周 Corona Geek 聚会的常客,也是 Corona 社区的活跃成员。他为 REEL FX Studios 开发了游戏,并维护他的 Corona SSK 游戏开发库。在 RoamingGamer.com 查看他的作品和博客。
在本教程中,我们将讨论一种简单的技术,您可以使用该技术来实现实时数据搜索,并在您的应用程序中产生响应式反馈和更新。
挑战
如果您曾经尝试实现实时搜索,您就会意识到,对于大型数据集和/或动态变化的搜索条件,很难保持应用程序的响应性。
例如,您的应用程序可能具有以下要求
- 该应用程序具有庞大的数据集。
- 该数据集需要可搜索。
- 当用户开始输入搜索条件时,需要立即执行搜索。
- 当搜索条件更改时,搜索应自动实时调整。
- 匹配的条目在找到时会返回,更新应用程序界面。
- 应用程序应保持响应。
最后一个要求至关重要。如果您的应用程序在执行搜索时出现挂起或临时故障,您最好不要分发它。
那么,如何做到这一点?
示例应用程序
为了演示上述一般问题的解决方案,让我指定一个确切的应用程序,然后提供解决该问题的代码。
该应用程序将具有以下功能
- 大型数据集 — 一个包含 100,000 多个单词的简单单词列表。
- FPS 计数器 — 将始终显示一个简单的 FPS 计数器,以提供响应性的具体证明。
- 搜索字段 — 一个文本输入字段(在设备和两个模拟器中都有效)。
- 进度计数器 — 用于显示总单词数、已找到的单词数和当前搜索索引的仪表。
- 结果列表 — 一个基本的(不可滚动列表),用于显示找到的单词。
应用程序模块
示例代码 具有多个模块,这些模块位于同名的 Lua 文件中
common.lua
— 计算和发现有用的变量和标志(left
、right
、centerX
、onSimulator
等)。wordList.lua
— 生成数据集。meter.lua
— 创建一个帧速率仪表。searchField.lua
— 为我们的搜索创建一个“文本输入字段”,该字段在设备和两个模拟器上都有效(还会创建计数和索引计数器)。example.lua
— 本文开头提出的问题的解决方案。
初始化搜索设置
在启动 "enterFrame"
监听器之前,我们需要初始化模块
- 创建和定位初始结果显示组。
- 将标志和变量初始化为起始值。
- 设置每帧允许执行的比较次数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
function example.init( maxTime ) -- 1. foundGroup = display.newGroup() foundGroup.y = com.top + 60 -- 2. searching = false -- 当前未搜索 lastTerm = "" -- 尚无搜索词。 curIndex = 1 -- 位于单词列表中的第一个单词。 foundCount = 0 -- 尚未找到任何单词。 -- 3. searchTime = maxTime or (1000/display.fps/2) end |
请注意,在步骤 3 中,当我们初始化搜索代码时,如果我们没有指定特定时间,代码会自动检测 FPS(如 config.lua
中设置的),然后计算等于一帧一半的时间。
“enterFrame” 监听器
初始化模块后,我们可以定义 "enterFrame"
监听器并开始运行它。该定义有五个部分。
第 1 部分 — 获取当前搜索词,并查看它是否已更改
1 2 3 4 5 6 |
local searchTerm = searchField.getSearchTerm() if ( lastTerm ~= searchTerm ) then example.resetResults() lastTerm = searchTerm searching = ( string.len( searchTerm ) > 0 ) end |
如果搜索结果已更改,我们会重置搜索结果(类似于模块初始化),记下新的搜索词,并设置标志,指示我们正在“搜索”。如果它们没有更改,我们只需忽略这段代码并继续。
第 2 部分 — 如果未“搜索”则中止
1 |
if ( not searching ) then return end |
如果 searching
标志设置为 false,我们会提前中止并等待下一帧重新开始。
第 3 部分 — 搜索直到时间耗尽,或到达单词列表末尾
虽然以上每个模块都可能有用且有趣,但我们将只关注 example.lua
。
解决方案
在构建之后,您可能会失望地发现这基本上是一个自我调节的 "enterFrame"
监听器。简而言之,每当搜索条件更改时,监听器就会启动新的搜索,并在一个紧密的循环中搜索,直到经过设定的时间。然后它停止搜索并退出。在下一帧,整个序列再次开始。
监听器具有以下逻辑结构
现在让我们看一下实际代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
local getTimer = system.getTimer -- 为了提高速度而本地化 local strLower = string.lower -- 为了提高速度而本地化 local startTime = getTimer() local elapsedTime = 0 while ( elapsedTime < searchTime and curIndex <= #wordList ) do local curWord = wordList[curIndex] if ( string.match( strLower(curWord), strLower( searchTerm ) ) ~= nil ) then example.drawResult( curWord ) end elapsedTime = getTimer() - startTime curIndex = curIndex + 1 end |
这段代码
- 本地化了一些有用的函数以提高执行速度。
- 记录了
startTime
。 - 将
elapsedTime
设置为零。 - 进入搜索循环,只有当到达列表末尾或超时时才会退出。
- 一旦找到匹配项,代码会显示它,然后继续。
这是解决方案的核心,您应该理解,通过每次搜索并(可能)显示结果时测量“经过的时间”,我们可以确保
- 搜索可以尽快给出结果,而不会阻塞当前帧的完成。
- 停止和恢复搜索的代码是动态的,并考虑了搜索和显示结果的成本。
第 4 部分 — 更新搜索索引标签
1 |
searchField.setSearchIndex( curIndex ) |
(请注意,这部分纯粹是为了示例中的反馈)
第 5 部分 — 检查是否已到达列表末尾,如果是则退出
1 |
searching = curIndex < #wordList |
作为监听器的最后一步,我们检查是否已到达单词列表的末尾。 如果是,我们将 searching
设置为 false。 在任何情况下,我们都会退出该函数(它将在下一帧的开头再次执行)。
结论
正如我上面提到的,这篇博文附带示例代码,请在您自己的应用程序中进行实验。 希望本教程向您展示了一种在您的项目中实现实时、响应式搜索的有趣方法。
Lerg
发布于 15:05, 5 月 12 日好东西。它也可以与 SQLite 一起使用。您只需使用 SQL 和 LIMIT 关键字将表循环替换为小的增量搜索即可。
Erich Grüttner D.
发布于 07:35, 5 月 13 日像往常一样,Ed,干得好!
谢谢!!!
Andrzej // Futuretro 工作室
发布于 11:52, 5 月 13 日很棒的教程,绝对会参考它。谢谢!
Mario
发布于 17:17, 5 月 13 日是的!我实现了 Erich 提到的 sqlite 版本。 效果很好。 有很多方法可以解决一个常见问题。 感谢这篇文章!!
-Mario
Andreas
发布于 05:26, 9 月 5 日您好
示例代码的链接似乎已损坏或已失效。
有人可以帮我找到示例吗?
谢谢 Andreas
Andreas
发布于 05:50, 9 月 5 日好的..看起来只有 zip 文件不见了。
在这里找到了文件
https://github.com/roaminggamer/RG_FreeStuff/tree/master/AskEd/2015/05/responsiveSearches