2013年12月3日
教程:自定义文本输入
一个常见的被请求的功能是“基于小部件”的文本输入字段,今天的教程为您提供了构建自己的文本输入字段的基础。 这不是一个完整的实现,它只克服了与 native.newTextField() 相关的一些问题,但它应该提供一个良好的起点。
让我们从一些基本的设置代码开始
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
local myTextField = widget.newTextField( { top = 10, left = 20, width = 200, height = 30, cornerRadius = 8, strokeWidth = 3, backgroundColor = { 1, 1, 1 }, strokeColor = { 0, 0, 0 }, font = "Helvetica", fontSize = 24, listener = textFieldHandler } ) |
当然,像这样的小部件最终可能会有很多选项,但让我们从考虑一些基本选项开始
- top — 距屏幕顶部的距离
- left — 距屏幕左侧的距离
- x — 小部件的水平位置(中心)
- y — 小部件的垂直位置(中心)
- width — 字段的宽度
- height — 字段的高度
- font — 要使用的字体
- fontSize — 字体的大小
- backgroundColor — 文本后面的颜色
- strokeColor — 字段周围笔画的颜色
- strokeWidth — 笔画的大小
- cornerRadius — 如果您想要圆角
- text — 初始文本
- inputType — 要显示的键盘类型
- listener — 输入处理程序
显然,这可以扩展到包括诸如“返回”按钮的值,具有在您第一次开始编辑字段时消失的占位符文本,甚至外观功能。 但是,由于此函数将使用原生文本字段,因此我们需要编写 — 核心 — 一个处理字段编辑的函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
local function textFieldHandler( event ) -- “event.text” 仅在编辑阶段存在,以显示正在编辑的内容; -- 它不是字段的 “.text” 属性(即 “event.target.text”)。 if ( event.phase == "began" ) then -- 用户开始编辑 textField print( "开始编辑", event.target.text ) elseif ( event.phase == "ended" or event.phase == "submitted" ) then -- 使用 defaulField 的文本做一些事情 print( "最终文本:", event.target.text ) native.setKeyboardFocus( nil ) elseif ( event.phase == "editing" ) then print( event.newCharacters ) print( event.oldText ) print( event.startPosition ) print( event.text ) end end |
如果我们希望将其作为小部件库的一部分,我们只需将其添加到模块并包含小部件库
1 2 3 4 |
local widget = require( "widget" ) function widget.newTextField( options ) ... end |
这将导致将一个新函数 newTextField()
添加到已包含在您的项目中的小部件库的实例中。 然后,在您需要 widget 库的任何场景或模块中,该函数都将可用。
现在让我们定义默认选项值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
function widget.newTextField( options ) local customOptions = options or {} local opt = {} opt.left = customOptions.left or 0 opt.top = customOptions.top or 0 opt.x = customOptions.x or 0 opt.y = customOptions.y or 0 opt.width = customOptions.width or (display.contentWidth * 0.75) opt.height = customOptions.height or 20 opt.id = customOptions.id opt.listener = customOptions.listener or nil opt.text = customOptions.text or "" opt.inputType = customOptions.inputType or "default" opt.font = customOptions.font or native.systemFont opt.fontSize = customOptions.fontSize or opt.height * 0.67 -- Vector options opt.strokeWidth = customOptions.strokeWidth or 2 opt.cornerRadius = customOptions.cornerRadius or opt.height * 0.33 or 10 opt.strokeColor = customOptions.strokeColor or { 0, 0, 0 } opt.backgroundColor = customOptions.backgroundColor or { 1, 1, 1 } ... |
上面的代码可能看起来有点令人困惑,但我们只是简单地接受一个名为 options
的函数参数。第一行创建了一个名为 customOptions
的表,以确保它是一个 Lua 表。如果您没有传递 options
参数,则会创建一个空表。之后,我们只是将每个单独的选项设置为传递的值或默认值。对于这个小部件,诸如圆角半径和字体大小之类的东西应该默认为适应字段大小的值。
创建视觉元素
在本节中,我们将创建文本字段小部件的视觉部分,包括与之配对的原生 UI 元素
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
local field = display.newGroup() local background = display.newRoundedRect( 0, 0, opt.width, opt.height, opt.cornerRadius ) background:setFillColor( unpack(opt.backgroundColor) ) background.strokeWidth = opt.strokeWidth background.stroke = opt.strokeColor field:insert( background ) if ( opt.x ) then field.x = opt.x elseif ( opt.left ) then field.x = opt.left + opt.width * 0.5 end if ( opt.y ) then field.y = opt.y elseif ( opt.top ) then field.y = opt.top + opt.height * 0.5 end -- 原生 UI 元素 local tHeight = opt.height - opt.strokeWidth * 2 if "Android" == system.getInfo("platformName") then -- -- 较旧的 Android 设备有额外的“边框”,需要进行补偿。 -- tHeight = tHeight + 10 end field.textField = native.newTextField( 0, 0, opt.width - opt.cornerRadius, tHeight ) field.textField.x = field.x field.textField.y = field.y field.textField.hasBackground = false field.textField.inputType = opt.inputType field.textField.text = opt.text print( opt.listener, type(opt.listener) ) if ( opt.listener and type(opt.listener) == "function" ) then field.textField:addEventListener( "userInput", opt.listener ) end local deviceScale = ( display.pixelWidth / display.contentWidth ) * 0.5 field.textField.font = native.newFont( opt.font ) field.textField.size = opt.fontSize * deviceScale |
在您检查此代码时,请考虑以下几点:
- 如果将角变为圆形,请不要让实际的文本字段延伸到角中。
- 请记住隐藏原生文本字段的背景,以便显示您的自定义视觉效果。
- native.newTextField() 需要将字体字符串名称转换为native.newFont()。但是,尺寸有点棘手,因为原生文本字段不会自动缩放。因此,我们必须计算设备的实际缩放因子,然后将所需的字体大小乘以该缩放因子。
移除元素
我们需要确保在移除小部件时,也同时移除原生文本字段。以前,我们需要使用我们自己的函数覆盖 removeSelf() 函数,该函数最终会调用原始的 removeSelf()。但是,新的 finalize 事件使此过程更加容易。这使我们能够设置一个函数,该函数在*显示对象从舞台上移除之前*执行,以防我们需要处理相关的清理任务——在本例中,在移除我们的自定义文本字段组之前移除原生文本字段。
1 2 3 4 5 6 7 |
function field:finalize( event ) event.target.textField:removeSelf() end field:addEventListener( "finalize" ) return field |
使用自定义文本字段
使用我们新的“小部件”文本字段很简单。要将其放置在屏幕上,我们的代码可能如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
local myTextField = widget.newTextField( { width = 250, height = 30, text = "Hello World!", fontSize = 18, font = "HelveticaNeue-Light", listener = textFieldHandler } ) myTextField.x = display.contentCenterX myTextField.y = 100 |
检索文本字段的值
1 |
local myText = myTextField.textField.text |
设置文本字段的值
1 |
myTextField.textField.text = "Edited Value" |
总结
本教程应该可以帮助您开始实现“样式化”文本输入字段,并希望将其扩展到更复杂的使用场景。
迪恩·霍奇
发布于 15:34, 12 月 3 日罗布,很棒的教程,但是……
我兴奋了一会儿,以为这可以解决 textField 的最大问题,即字体大小在各种设备(尤其是 Android)上的缩放问题。
每次我做一个商业应用程序时,这个问题总会困扰我,以及许多其他人。
希望我们能尽快解决字体大小缩放问题,因为它已经困扰我和许多其他开发人员很长时间了。
请尝试找到此问题的解决方案。
克里斯
发布于 16:01, 12 月 3 日+1 在设备上的字体缩放
罗布·米勒克
发布于 16:03, 12 月 3 日你试过这个吗?我在我的 iPhone 5、iPad 4 和 Google Nexus 7(具有 320×480 的基本内容区域)上运行了代码,字体大小在设备之间保持一致。那里有代码来维持缩放。
罗布
凯雷姆
发布于 11:54, 12 月 5 日刚刚在我的 Android 2.2 三星 Galaxy S 上尝试了代码。字体大小问题仍然存在。不确定这是否是由于过旧的操作系统版本造成的。
罗布·米勒克
发布于 17:32, 12 月 5 日您能否在论坛帖子中回复此内容并提供屏幕截图?
星际碎片
发布于 15:03, 12 月 4 日我记得必须明确地 removeSelf() 小部件...这些小部件是否已经考虑到 finalize 进行了重写?也就是说,我是否可以摆脱该处理代码?
罗布·米勒克
发布于 15:20, 12 月 4 日所有显示对象都需要显式删除并设置为 nil,但 Storyboard 除外,当场景被销毁时,场景视图中的显示对象会被为您删除。
罗布
星际碎片
发布于 16:57, 12 月 4 日对不起,我表达得不够准确;这可能听起来有点琐碎的问题。我的意思是,对于小部件,removeSelf() 是否仍然是必要的,而不是说 group:remove() 或移除父组,考虑到逻辑已移到终结器中。至少,我假设那些不会调用(可能被覆盖的)removeSelf() 方法...
当然,也许我会测试一下,看看我的假设是否一直都是错误的。🙂
凯雷姆
发布于 00:14, 12 月 5 日感谢您提供的精彩教程。这是我希望能够发展成为正式的 Corona Labs widget.newTextField() 的一个很好的起点。
问题 - 我们如何扩展功能,以便如果将一个新参数(即 eraseInitialText)设置为 true,那么当用户第一次点击此输入字段时,最初提供的文本将被删除?我可以在监听器函数的开始字段中执行此操作,但我想将此代码推送到小部件扩展代码中,以便可以重用它。这可能吗?谢谢
罗布·米勒克
发布于 17:30, 12 月 5 日我在此论坛主题中回复了:http://forums.coronalabs.com/topic/41661-widget-for-text-input/
请将回复定向到那里。
谢谢
罗布
肖恩
发布于 06:54, 12 月 5 日嘿,罗布,这些文本字段现在会在小部件 ScrollView 中滚动吗?这对我来说一直是个大问题;让文本字段与屏幕的其余部分一起滚动。关于何时在实际小部件代码库中实现此功能,是否有时间表?
罗布·米勒克
发布于 16:31, 12 月 6 日我没有尝试过,但它应该可以。同步代码将原生文本字段保持在对象所在的任何位置。根据您滚动 scrollView 的速度,系统移动 native.newTextField 时可能会有一些延迟。
罗布
卡维卡
发布于 10:03, 12 月 8 日有人在使用 X-PRESSIVE.COM 的“Text Candy”吗?
卡维卡
发布于 12:35, 12 月 8 日有人在使用 X-PRESSIVE.COM 的“Widget Candy”吗?
bobcgausa
发布于 06:49, 1 月 15 日我使用 Corona 来教学生,他们在使用 Mac 或 Windows 上的模拟器,并且不一定拥有安卓设备。
“由于此模块仍然使用原生文本字段,因此它将在 Windows Corona 模拟器上绘制小部件背景,但文本字段将不起作用。”
真的,拥有一个跨平台单行文本框有多难?
这对课堂使用来说是一个巨大的限制。
罗布·米勒克
发布于 1月15日 17:51我们已经多次解释过这个问题。Corona SDK 基于 OpenGL。在 OS-X、iOS 和 Android 上,操作系统允许原生对象和 OpenGL 画布占据同一窗口。在 Windows 上,它们不允许原生对象和 OpenGL 对象在同一窗口中共存。微软允许基于 DirectX 的应用程序这样做,但我们是基于 OpenGL 的应用程序。
sangu
发布于 9月17日 01:07我的代码中 widget.newTextField({}) 显示错误,同时引用了 widget。
它在 pro-corona 中支持吗?或者我在代码中做错了什么?
local widget = require("widget")
local myTextField = widget.newTextField(
{
top = 10,
left = 20,
width = 200,
height = 30,
cornerRadius = 8,
strokeWidth = 3,
backgroundColor = { 1, 1, 1 },
strokeColor = { 0, 0, 0 },
font = "Helvetica",
fontSize = 24
--listener = textFieldHandler
}
罗布·米勒克
发布于 9月17日 17:28请在论坛中提出这个问题。
谢谢
罗布
Julius Bangert
发布于 9月8日 06:21是否有人开发出提供类型提前输入/自动完成功能的模块或插件?
因此,当用户输入前几个字母时,UI 中文本输入附近会显示从 lua 表中提取的建议词汇?
这将会非常棒。