管理大量文本块

有些人尝试在 Corona 中创建异常大的文本块,但发现不起作用。这是因为 Corona 的图像核心基于 OpenGL。这意味着屏幕上几乎所有东西都是图片… 甚至是文本。

更具体地说,在 “原生” 应用程序中,文本只是屏幕上的一个对象,但在 OpenGL 中,所有内容都必须渲染为图像纹理。因此,为了让文本出现在屏幕上,Corona 会采用您传递给 display.newText() 的字符串值,并命令的操作系统的字体引擎使用您提供的字体指标生成图片。然后,返回一个 Corona 显示对象(纹理)并在屏幕上渲染。

为什么这可能是大块文本的问题呢?基本上,如果您渲染一段极长的文字(或段落),您可能会超出设备的最大纹理大小限制。在 OpenGL 中,此最大纹理大小定义为呈现图片所能容纳的最大像素限制 —水平或垂直 —如上所述,这包括大段文本,因为 Corona 会将它们渲染成图像纹理。对于一些较旧或价格较低的设备,此限制小到任一方向只有 1024 像素,这意味着如果文本块超出该值,OpenGL 则无法正确渲染它,通常会在屏幕上渲染一个纯白色框!

幸运的是,大多数现代设备支持至少 2048 像素的纹理大小,为了您的方便,Corona 通过以下 system.getInfo() 调用提供此信息

system.getInfo( "maxTextureSize" )

即使考虑到合理的 2048 像素纹理大小限制,在通过多行 display.newText() 调用创建大段文本时,超过此限制也并不少见。如上所述,这可能会导致在您预期文本出现的位置呈现纯白色块。那么您如何解决这个问题呢?很简单,避免创建极大的文本块,而要创建一系列经过对齐的更小块(纹理),并将它们放置在屏幕上。

关于行尾编码

在计算机历史的早期,对于定义计算机所使用的字符集有两项相互竞争的标准:ASCIIEBCDIC。最后 ASCII 击败 EBCDIC,现在几乎所有内容(包括 macOS、Windows、Android 和 iOS)都是基于 ASCII.

ASCII 在一个字节的数据中包含 26 个大写字母、26 个小写字母、数字以及各种符号字符。除了您已知的可见字符外,ASCII 还包括一系列控制字符。事实上,前 31 个字符都是控制字符!这些控制字符可用于控制屏幕上的文本定位,它们的传统可以追溯到古老的手动打字机。其中包括

对于我们这些还记得手动打字机的人来说,回车会使打字头回到页面左侧。它本身不会将纸张推进到下一行,因此如果你在回车后开始打字,你会覆盖前一行。因此,需要第二个称为换行的动作来将纸张推进一行。

计算机尝试模仿此系统,但不同的操作系统对它的处理方法略有不同。Microsoft DOS(以及现在的 Windows)选择了2 个字符 行尾序列,模仿打字机。也就是说,每行以CTRL-M, CTRL-J序列结尾。然而 Unix 身为一款“极简”操作系统,仅使用换行符 (CTRL-J) 表示行尾。

当今,Android、macOS 和 iOS 均基于 Unix,因此更通用的标准是使用单个CTRL-J换行符来标记行尾。然而,Windows 仍在使用CTRL-M + CTRL-J组合。为了增加混乱,人们引用这些字符串的方式也有所不同。例如

为了澄清,当你看到插入符号 (^) 时,它表示控制 (CTRL)。当你看到反斜杠 (\) 时,它表示转义字符,因此在本例中,\r返回\n换行符.

实现多行文本

在 Corona 中有两种渲染多行文本对象的方法。你使用哪种方法主要取决于你的应用程序设计,且了解每种方法的优点和缺点非常重要。

换行包装

一种将文本换行到下一行的方法是在你传递给 display.newText() 调用的字符串中定义换行符。由于我们移动设备的操作系统基于 Unix,因此更常用 \n转义版本。事实上,你可能见过如下多行Corona 代码中的字符串示例

local myString = "First line of text.\nSecond line of text.\nThird line of text."

以这种方式指定多行将在 Corona 中被视为单独的视觉文本行。

或者,你可以使用 Lua 的多行文本引号来实现相同的功能

local myString = [[First line of text.
Second line of text.
Third line of text.]]

请注意,在这种情况下,你无需像第一个版本中那样指定换行符 (\n) 标记。

现在,如果你将此 myString 变量传递给 display.newText(),你将获得如下所示的文本输出。在你指定 \n(或在[[ ]] 格式中启动一个新的代码行)的每一点,文本都将换行到下一行。通过这种方式,你将在你指定它们的准确位置获得换行 — 例如,要在两个段落之间使用换行,你可以在第一个段落的末尾指定 \n\n

宽度包装

所有支持文本的现代设计/布局应用程序都允许你创建文本框,无论其内容是什么 — 换行当一行达到边框时。当您要在固定宽度空间中显示可变文本内容时,此方法非常有用,可以使文本输出自然地换行到新行。

display.newText() 的 width 参数非常方便地支持该概念。通过指定像素宽度,可以指示文本在该点换行,例如

local myString = "The quick brown fox jumped over the lazy dog."

local myText = display.newText( myString, 30, 30, 200, 0, native.systemFont, 24 )

其中,display.newText() 命令中 200 的值指示 Corona 创建一个宽为 200 像素的文本框,从而获得此处所示的输出。紧随其后的 0 参数表明文本框为 灵活高度,这意味着呈现的文本对象的宽度将根据提供的文本字符串自动调整。尽管可以为该框设置静态高度,但通常不建议这样做,因为它可能会导致文本内容在框的较低边界点被视觉截断。

分隔长文

如果您正在创建诸如电子书之类的应用,或为用户提供一些详细说明,通常不会需要太多文本来创建超出 2048 像素的呈现文本图像。无论使用已定义换行符 还是 自动宽度换行,都可能轻松地出现这种情况。

幸运的是,您可以循环遍历一个较长的文本串,如上所述,将其分解为较小的块,可由 OpenGL 适当地呈现,确保它们不会超过最大纹理大小限制。执行此操作时,一种选择是使用自然语言概念 段落 来拆分长文本块。让我们进一步探究一下...

初始设置

对于本教程,我们将使用由 www.lipsum.com

生成的文本

local myText = [[Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque semper mollis erat a interdum. Praesent tristique diam in nulla varius, nec aliquet mauris posuere. Suspendisse pretium risus lacus, commodo lacinia sapien dictum et. Sed non varius felis. Curabitur elementum tortor non libero pulvinar, at convallis lectus varius. Interdum et malesuada fames ac ante ipsum primis in faucibus. Curabitur sit amet nunc congue, molestie erat vel, facilisis turpis. Morbi vitae diam ligula. Suspendisse purus turpis, commodo in aliquam id, lobortis a sapien. Sed at libero porta, aliquam odio nec, porta dui. In a congue velit. Aliquam ac quam feugiat, ultricies metus nec, porta neque. Phasellus posuere mollis magna, ac vestibulum ligula congue id. Pellentesque imperdiet aliquam lacus, ac pellentesque dui eleifend nec. Suspendisse auctor vehicula facilisis. Pellentesque id massa tincidunt neque luctus varius.

Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Maecenas sit amet dapibus nulla. Suspendisse ut risus nulla. Maecenas varius elit non faucibus fermentum. Fusce rhoncus, nisl et varius tristique, enim felis egestas purus, et feugiat lorem urna a augue. Maecenas non pulvinar tortor. Aenean condimentum nibh id eros fringilla viverra. Fusce condimentum urna ut volutpat porttitor. Nunc tincidunt congue ligula.

Duis placerat felis varius, convallis massa sed, volutpat magna. Sed vitae viverra neque. Integer ac sollicitudin libero, at ornare purus. Aliquam egestas hendrerit tellus. Aliquam eu elit vitae lorem lacinia tempus. Proin vel dictum mi. Maecenas porttitor, justo a dictum volutpat, nisl libero dictum ligula, vitae posuere urna elit a quam. Nam arcu metus, semper suscipit pellentesque ac, tempor ut arcu. Vestibulum eu nibh erat. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Sed semper sollicitudin lorem, vel commodo libero commodo eget. Proin lacinia euismod elit vitae porttitor. Proin ipsum neque, dictum at dictum eu, egestas malesuada turpis. Nulla eros lectus, adipiscing eget velit sed, malesuada aliquam ipsum. Curabitur et egestas massa. Vestibulum luctus est est, tincidunt viverra nisi vulputate id.

Integer lobortis tellus eu ligula viverra egestas. Quisque commodo, massa vel pretium imperdiet, nisl enim euismod justo, sed ultricies lacus mi ut nisi. Maecenas molestie vitae magna non interdum. In gravida ornare orci in vulputate. Praesent suscipit lobortis dui ut interdum. Proin pulvinar metus ligula, a malesuada nunc interdum at. Aenean et scelerisque enim. Integer eget congue sapien. Etiam suscipit mauris neque, id semper quam volutpat vel. Proin venenatis dictum felis quis ultricies. Suspendisse feugiat mi congue ante gravida, id accumsan leo mollis. Lorem ipsum dolor sit amet, consectetur adipiscing elit. In hac habitasse platea dictumst. Nulla facilisi.

Nam arcu mauris, convallis sit amet dictum consequat, imperdiet at mi. Vestibulum velit erat, accumsan sit amet vehicula vitae, tempor id nisi. Quisque eu tellus vulputate nisi vestibulum tincidunt at vitae tellus. Quisque sed pretium nisl. Vivamus a aliquet purus. Integer pulvinar neque in dapibus pharetra. Quisque convallis urna vulputate ligula mattis dictum. Vivamus pharetra molestie nunc, ac rhoncus dolor euismod at. Cras fringilla sollicitudin sapien vel sagittis. Donec dignissim scelerisque mi nec pulvinar. Mauris at metus gravida, lacinia dolor quis, vehicula lacus. Donec a pellentesque tellus. Praesent sit amet lorem nisl. Pellentesque interdum felis quis vehicula vestibulum. Donec ut dolor tortor.
]]

现在,让我们添加一些代码来容纳如此大量的文本

local widget = require( "widget" )

local scrollView = widget.newScrollView(
{
    top = 0,
    left = 0,
    width = display.contentWidth,
    height = display.contentHeight,
    horizontalScrollDisabled = true
})

local paragraphs = {}
local paragraph
local tmpString = myText

local yStart = 10
local mainPadding = 20

让我们仔细检查此代码

  1. 由于这五段较长的文本显然不能放在屏幕上,因此它们将被插入到 widget.newScrollView() 中。因此,我们必须首先 require() Widget 库(第 12 行),并创建一个新的滚动视图,该视图占据整个内容区域(第 14-21 行)。我们的文本将从上到下输出,因此我们通过将 horizontalScrollDisabled 参数设置为 true 来仅将滚动限制为垂直方向。

  2. 在此之后,paragraphs 表将保存每个段落的不同 display.newText() 对象(分隔的文本块)。paragraphtmpString 变量将分别保存当前段落文本和已删除当前段落后的字符串副本。

  3. 变量 yStart 将用于将每个段落一个接一个地放置在后面,其值为 10(像素),以便在每个新段落之前提供一些间距。当然,您可以根据您的视觉演示调整此值。至于 mainPadding,我们将使用它在每个呈现的文本块周围添加一些水平填充。

重复循环

为了执行主要工作,我们将使用repeat-until循环。由于我们希望至少执行此过程一次——而且有可能是针对多个段落——因此 repeat 循环比 while 循环更有意义。本质上,此循环将一直运行,直到 tmpString 等于 niltmpString 的长度为 0

repeat
    paragraph, tmpString = string.match( tmpString, "([^\n]*)\n(.*)" )
    paragraphs[#paragraphs+1] = display.newText( { text=paragraph, width=scrollView.width-(mainPadding*2), fontSize=14 } )
    paragraphs[#paragraphs].anchorX = 0
    paragraphs[#paragraphs].anchorY = 0
    paragraphs[#paragraphs].x = mainPadding
    paragraphs[#paragraphs].y = yStart
    paragraphs[#paragraphs]:setFillColor( 0 )
    scrollView:insert( paragraphs[#paragraphs] )
    yStart = yStart + paragraphs[#paragraphs].height
until tmpString == nil or string.len( tmpString ) == 0

让我们探讨一下此循环里面发生的情况

  1. 首先,我们使用 string.match() 在字符串中搜索 换行 字符(\n)。此晦涩难懂的搜索字符串基本上告诉 Lua 将字符串分为两部分:直到遇到第一个 换行 (\n) 为止,任意数量不是 换行 的字符 ([^\n*])。这存储在 paragraph 中,然后其余的字符串存储在 tmpString 中。

  2. 使用变量 paragraph 容纳一整段文本后,我们通过 display.newText() 创建显示对象,将 paragraph 的值作为 text 参数传入。使用 #paragraphs+1 作为循环索引,它将在 paragraphs 表格末尾创建新的表格条目。在后续行中,我们可以使用 #paragraphs 作为索引来引用新条目。

注意

注意文本对象的 width 参数设置为scrollView.width-(mainPadding*2 ——它促进了自动换行符, 并且为块提供一些水平填充。

  1. 然后在第 33-36 行中,我们定位文本。为保持简洁,我们将文本块的锚点更改为左上角显示对象的角,设置 x 坐标作为 mainPadding 来提供一些左边空白,并将 y 坐标设置为 yStart。按照此操作,在第 37-38 行中,我们将文本颜色更改为黑色,最后将段落插入滚动视图。

  2. 在循环内的最后一行上,我们将 yStart 增加之前创建的段落的高度。这样,你便可以将下一块文本立即定位在前一块文本之下,形成一种错觉,即这只是带有一小段空白的文本长块。

完成收尾

最后,让我们增加滚动视图的总可滚动高度,以便在最后一段下方提供一些垂直填充

scrollView:setScrollHeight( scrollView:getView().height + (mainPadding*2) )

就是这样!有了这段完整代码,你可以构建包含无限段落且在视觉上通过可调节像素量分隔并具备可针对不同字体和字号进行自动换行的滚动视图。

结论

希望本教程已经向你展示了如何通过一点额外的努力来克服纹理大小的限制,进而构建 Corona 中的文本长块。