2013年12月31日
教程:创建导航栏
使用 Corona,创建我们自己的顶部导航栏非常容易——只需绘制背景,添加一些标题文本,并根据需要放置按钮即可。但是,如果我们想遵守“DRY”编程理论(Don't Repeat Yourself,不要重复自己),在应用程序的每个场景中都包含一整套导航栏代码会比应该做的更多。
关于导航栏
在 iOS 术语中,顶部导航栏被称为 UI 导航栏,它具有相当一致的行为
- 一个背景
- 一个可选的左侧按钮
- 一个可选的右侧按钮
- 一个可选的标题
本教程演示如何创建一个标准的 iOS 风格“UI 导航栏”小部件,其中我们提供了两个按钮的定义、处理按钮交互的函数以及一些绘制栏的基本信息。
设置
就像我们在自定义文本输入教程中所做的那样,让我们首先扩展核心widget库并定义默认参数
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 |
local widget = require( "widget" ) function widget.newNavigationBar( options ) local customOptions = options or {} local opt = {} opt.left = customOptions.left or nil opt.top = customOptions.top or nil opt.width = customOptions.width or display.contentWidth opt.height = customOptions.height or 50 if ( customOptions.includeStatusBar == nil ) then opt.includeStatusBar = true -- 假设商业应用程序使用状态栏 else opt.includeStatusBar = customOptions.includeStatusBar end -- 确定调整状态栏存在所需要的空间 local statusBarPad = 0 if ( opt.includeStatusBar ) then statusBarPad = display.topStatusBarContentHeight end opt.x = customOptions.x or display.contentCenterX opt.y = customOptions.y or (opt.height + statusBarPad) * 0.5 opt.id = customOptions.id opt.isTransluscent = customOptions.isTransluscent or true opt.background = customOptions.background opt.backgroundColor = customOptions.backgroundColor opt.title = customOptions.title or "" opt.titleColor = customOptions.titleColor or { 0, 0, 0 } opt.font = customOptions.font or native.systemFontBold opt.fontSize = customOptions.fontSize or 18 opt.leftButton = customOptions.leftButton or nil opt.rightButton = customOptions.rightButton or nil -- 如果传递了“left”和“top”参数,则计算 X 和 Y if ( opt.left ) then opt.x = opt.left + opt.width * 0.5 end if ( opt.top ) then opt.y = opt.top + (opt.height + statusBarPad) * 0.5 end |
在创建顶部导航栏时,有几件事需要考虑,我们在代码中已经这样做了。例如,应用是否有设备特定的状态栏? 如果有,导航栏应放置在其下方,但状态栏占用的区域仍然是我们负责的空间。因为我们无法通过编程方式检测状态栏是否正在显示,所以我们必须传入一个标志,表明我们是否决定显示或隐藏状态栏。然后,该函数将计算状态栏的高度并相应地调整导航栏的位置。
接下来,让我们构建导航栏本身
1 2 3 4 5 6 7 8 9 |
local barContainer = display.newGroup() local background = display.newRect( barContainer, opt.x, opt.y, opt.width, opt.height + statusBarPad ) if ( opt.background ) then background.fill = { type="image", filename=opt.background } elseif ( opt.backgroundColor ) then background.fill = opt.backgroundColor else background.fill = { 1, 1, 1 } end |
在此代码块中,我们首先创建一个容器组来保存所有视觉对象。然后,创建一个矩形作为“背景”,并将其添加到容器组中。接下来,传入的参数确定如何填充矩形 - 使用指定的图像、纯色或(如果未提供任何内容)纯白色填充。
现在,让我们将页面的“标题”添加到导航栏(请注意,某些 iOS 应用程序不使用标题)
1 2 3 4 5 |
if ( opt.title ) then local title = display.newText( opt.title, background.x, background.y + statusBarPad * 0.5, opt.font, opt.fontSize ) title:setFillColor( unpack(opt.titleColor) ) barContainer:insert( title ) end |
现在,让我们设置按钮。在此示例中,将有两个按钮:“左”按钮和“右”按钮。为了简单起见,我们将使用现有的widget.newButton()小部件 - 本质上,我们将参数传递给按钮构造函数
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 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
local leftButton if ( opt.leftButton ) then if ( opt.leftButton.defaultFile ) then -- 构造一个图像按钮 leftButton = widget.newButton({ id = opt.leftButton.id, width = opt.leftButton.width, height = opt.leftButton.height, baseDir = opt.leftButton.baseDir, defaultFile = opt.leftButton.defaultFile, overFile = opt.leftButton.overFile onEvent = opt.leftButton.onEvent }) else -- 否则,构建一个文本按钮 leftButton = widget.newButton({ id = opt.leftButton.id, label = opt.leftButton.label, onEvent = opt.leftButton.onEvent, font = opt.leftButton.font or opt.font, fontSize = opt.fontSize, labelColor = opt.leftButton.labelColor or { default={ 1, 1, 1 }, over={ 0, 0, 0, 0.5 } }, labelAlign = "left", }) end leftButton.x = 15 + leftButton.width * 0.5 leftButton.y = title.y barContainer:insert( leftButton ) -- 将按钮插入到容器组中 end local rightButton if ( opt.rightButton ) then if ( opt.rightButton.defaultFile ) then -- 构建一个图像按钮 rightButton = widget.newButton({ id = opt.rightButton.id, width = opt.rightButton.width, height = opt.rightButton.height, baseDir = opt.rightButton.baseDir, defaultFile = opt.rightButton.defaultFile, overFile = opt.rightButton.overFile, onEvent = opt.rightButton.onEvent }) else -- 否则,构建一个文本按钮 rightButton = widget.newButton({ id = opt.rightButton.id, label = opt.rightButton.label or "Default", onEvent = opt.rightButton.onEvent, font = opt.leftButton.font or opt.font, fontSize = opt.fontSize, labelColor = opt.rightButton.labelColor or { default={ 1, 1, 1 }, over={ 0, 0, 0, 0.5 } }, labelAlign = "right", }) end rightButton.x = display.contentWidth - ( 15 + rightButton.width * 0.5 ) rightButton.y = title.y barContainer:insert( rightButton ) -- 将按钮插入到容器组中 end return barContainer end |
如果通过 defaultFile
属性提供了图像,代码会查找与基于图像的小部件按钮相关的参数(请参阅文档)。如果首选简单的文本按钮,我们可以指定 label
、font
、fontSize
和 labelColor
数组。一旦两个按钮都设置好,我们只需 return
将容器作为对该对象的引用返回给调用者。
使用导航栏小部件
要使用这个新的小部件,我们只需将上面的代码包含在 main.lua
中。它将被添加到小部件库中,因此在其他你 require("widget")
的模块中,它将可供你使用。然后,当我们需要显示新的导航栏时,我们遵循三个基本步骤
1. 编写按钮监听器
除非有与点击动作相关的回调监听器,否则导航栏上的按钮不会很有用。因此,让我们在要使用导航栏的模块中包含一些基本的监听器函数
1 2 3 4 5 6 7 8 9 10 11 12 13 |
local function handleLeftButton( event ) if ( event.phase == "ended" ) then -- 执行操作 end return true end local function handleRightButton( event ) if ( event.phase == "ended" ) then -- 执行操作 end return true end |
2. 声明导航栏按钮
现在,让我们配置特定于此导航栏的按钮。在此示例中,左侧按钮将是一个双图像按钮,而右侧按钮将是一个简单的文本按钮。请注意,我们还包括了对我们刚刚编写的回调监听器的引用,以便我们的按钮响应触摸。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
local leftButton = { onEvent = handleLeftButton, width = 60, height = 34, defaultFile = "images/backbutton.png", overFile = "images/backbutton_over.png" } local rightButton = { onEvent = handleRightButton, label = "添加", labelColor = { default = {1, 1, 1}, over = { 0.5, 0.5, 0.5} }, font = "HelveticaNeue-Light", isBackButton = false } |
3. 声明导航栏
最后,我们声明实际的导航栏如下
1 2 3 4 5 6 7 8 9 10 |
local navBar = widget.newNavigationBar({ title = "超级棒的应用", backgroundColor = { 0.96, 0.62, 0.34 }, --background = "images/topBarBgTest.png", titleColor = {1, 1, 1}, font = "HelveticaNeue", leftButton = leftButton, rightButton = rightButton, includeStatusBar = true }) |
从哪里开始?
您可以基于这个概念进行扩展。首先,这对 iOS 非常友好,但它不会构建 Android 样式的导航栏。Android 样式的顶部栏更像是 iOS 的 工具栏,右侧有一系列按钮,左侧有一个“汉堡”图标和图形标题。其次,可以扩展此示例以支持比双图像或基本文本按钮更多的按钮样式。最后,此示例不支持导航尖角(小于号和大于号),但是可以很容易地进行配置。
Kerem
发布于 13:20, 12 月 31 日好主意和教程!非常感谢 Rob。新年快乐!!!
Mark
发布于 20:07, 1 月 1 日只是好奇。为什么不将其添加到核心小部件库中,而不是对其进行扩展?
Rob Miracle
发布于 09:54, 1 月 2 日我们的工程人员正忙于他们正在开发的功能和错误。这个特殊的功能不是一个高需求的功能,并且大多数人已经以某种形式或方式在做这件事。我们在教程帖子中撰写博客的部分内容有时是为了激发您并向您展示如何做事。希望在一天结束时,读者会说“哦,原来是这样做的”,并有一些新的东西可以玩。
Rob
Dan R.
发布于 09:26, 1 月 3 日Rob,
感谢您提供精彩的教程帖子!作为一名业余爱好者,这些教程对我的进步非常有帮助...
两个简短的问题
1. 当我在模拟器中运行 NavBar 演示时,我收到一条消息,提示该项目使用 Pro(或更高版本)订阅者的优质图形功能。多次查看您的代码后,我不确定这些是什么或要删除什么以避免这种情况。有什么想法吗?我正在使用 OSX 10.9 上的入门版 2013.2100
2. 在设备旋转后以正确的宽度重新创建该导航栏时,将演示调整为 Storyboard 场景的最佳方法是什么?这是我正在尝试的,并且似乎有效,但也许我错过了一个更明显的解决方案
– 我已将博客演示中的代码放入 create scene 函数中。
– 我已将 navBar 添加到 createScene 的组中。
– 在 createScene 中,我添加了一个方向函数 (onOrientationChange),该函数在当前场景上调用 storyboard.purgeScene,然后调用当前场景上的 storyboard.gotoScene 以强制重新创建导航栏元素。
– 在 createScene 函数的末尾,我包含了一个用于方向的运行时事件监听器:Runtime:addEventListener( “orientation”, onOrientationChange )
– 在 exitScene 和 destroyScene 函数中,我都包含了:Runtime:removeEventListener( “orientation”, onOrientationChange )。我不确定是否需要同时在这两个地方。
那么,有没有更好的方法来管理它?我所写的内容似乎可以工作,但是让一个场景清除自身然后跳转到自身是否可以,或者会产生意想不到的后果吗?
感谢您的想法...
{如果此主题需要移至论坛讨论,请随时将其重新定位...}
Rob Miracle
发布于 12:05, 1 月 3 日整个栏的创建都基于 Graphics 2.0 方法。可以很容易地重写它,使其不创建矩形并用图案填充它,而是使用 display.newImageRect() 代替 display.newRect()。iOS 6 渐变也使用 G2.0 填充。这很可能就是正在发生的事情。
您可能只需要在 destroyScene 中删除侦听器。我不喜欢清除屏幕上的场景,但这可能是该场景的有效用法。
tarun
发布于 05:13, 1 月 6 日您好 Rob,
我遇到了这个异常
堆栈回溯
[C]: 在函数 'setFillColor' 中
…corona/examples/project/widget_newNavBar.lua:59:在函数 'newNavigationBar' 中
…top/android/corona/examples/project/main.lua:40:在主块中
您可以确认示例代码是否没有错误。
tarun
发布于 05:14, 1 月 6 日文件:…corona/examples/project/widget_newNavBar.lua
行:59
尝试调用方法 'setFillColor'(nil 值)
Rob Miracle
发布于 14:49, 1 月 6 日您使用的是哪个版本的 Corona SDK?
您的订阅级别是什么?
如果第 59 行是标题的颜色,你可能正在使用 Corona SDK 的 pre-Graphics 2.0 版本。你可以安全地将其更改为 :setTextColor(),但设置 navBar 的背景非常依赖于 Graphics 2.0。
Dan R,
发布于 01月07日 08:41Taurn – 如果你不在意 iOS6 的渐变填充,只想使用 iOS7 风格的栏,这个简化的版本似乎可以在 Starter 版本中使用。它不如 Rob 的示例强大。
在函数 widget.newNavigationBar( options ) 中进行以下更改
[lua] opt.backgroundColor = customOptions.backgroundColor or {1,1,1} – iOS7 栏默认为白色背景[/lua]
和
[lua] local barContainer = display.newGroup()
local background = display.newRect(barContainer, opt.x, opt.y, opt.width, opt.height + statusBarPad )
background:setFillColor(unpack(opt.backgroundColor))
[/lua]
Archer
发布于 01月15日 07:38大家好,
在本教程的结尾,你提到它对 iOS 非常友好,但对 Android 不友好。你是暗示不应该在 Android 上使用它,还是经过一些小的设计更改后可以在跨平台使用?
其次,Corona 提供的 tabbar UI 没有缩放功能,这个在所有平台上都具有缩放功能吗?
最后,像 tabBar UI 一样,按钮可以在导航栏下方的空间中反映故事板场景吗?
谢谢,
Archer
Rob Miracle
发布于 01月15日 17:48在 Android 上,他们的顶部栏左边缘有一个被称为汉堡图标的图标,以及一个左对齐的图形品牌图标,比如 GMail 的徽标。然后在右侧,他们根据应用的需求有一系列按钮。这更像 iOS 的 Toolbar UI 小部件,而不像 navBar 小部件。
你可以很容易地扩展它,使其接受按钮列表和一个图标,以便在 Android 上工作。你仍然可以在 Android 上使用它,我认为人们会接受它,但你通常希望你的 UI 行为方式与操作系统想要的一致。
你必须记住,教程的空间有限。虽然我本可以编写一个完全可用的跨平台小部件,但对于教程来说,它会太长太复杂。教程的一部分也是为了激发你扩展教程中的想法,而不是提供代码解决方案。
对于你的第二个问题,如果你使用 320px 宽的内容区域,它在所有设备上都将是 320,所以不存在缩放问题。高度可能会有所不同。
如果你的 tabBar 按钮是半透明的,则下面的故事板场景应该会显示出来。
Rob
Archer
发布于 01月16日 08:55好的,谢谢你回答这些问题,Rob。我真的很感谢这个教程,非常棒的内容,只是为了确保一下,因为我刚开始使用这个平台几天,还在了解它。我在 main 中按钮的 onhandle 事件侦听器中传递信息时遇到了问题。演示代码没有传递信息,只是 print(“button pressed”)。我在看到你的回复之前提交了这个问题。我一直在思考为什么它没有按照我所想的那样工作,并且想不出原因。
Archer
发布于 01月16日 08:45main 中的 handLeftButton (event) 函数没有传递信息。我尝试在屏幕上打印按钮被按下,但什么也没得到。在我下载的演示代码中和我的项目中都一样。有什么建议吗?
Rob Miracle
发布于 01月16日 15:58我建议你在论坛中发帖,询问有关左按钮的问题。博客评论无法很好地处理代码发布。当你这样做时,你能发布你正在使用的代码吗?
Rob
phlo
发布于 04月02日 07:52我也有同样的问题,左按钮缺少 onEvent,只需在 widget_newNavBar.lua 中第 71 行之后添加即可
onEvent = opt.leftButton.onEvent
Sanghyun Lee
发布于 12月24日 11:03———————————————————————–
navbar.lua
—————-代码开始—————————————–
local widget = require( “widget” )
function widget.newNavigationBar( options )
local customOptions = options or {}
local opt = {}
opt.left = customOptions.left or nil
opt.top = customOptions.top or nil
opt.width = customOptions.width or display.contentWidth
opt.height = customOptions.height or 50
if ( customOptions.includeStatusBar == nil ) then
opt.includeStatusBar = true — 假设商业应用使用状态栏
else
opt.includeStatusBar = customOptions.includeStatusBar
end
— 确定调整状态栏显示所需的空间量
local statusBarPad = 0
if ( opt.includeStatusBar ) then
statusBarPad = display.topStatusBarContentHeight
end
opt.x = customOptions.x or display.contentCenterX
opt.y = customOptions.y or (opt.height + statusBarPad) * 0.5
opt.id = customOptions.id
opt.isTransluscent = customOptions.isTransluscent or true
opt.background = customOptions.background
opt.backgroundColor = customOptions.backgroundColor
opt.title = customOptions.title or “”
opt.titleColor = customOptions.titleColor or { 0, 0, 0 }
opt.font = customOptions.font or native.systemFontBold
opt.fontSize = customOptions.fontSize or 18
opt.leftButton = customOptions.leftButton or nil
opt.rightButton = customOptions.rightButton or nil
— 如果传递了 "left" 和 "top" 参数,则计算 X 和 Y
if ( opt.left ) then
opt.x = opt.left + opt.width * 0.5
end
if ( opt.top ) then
opt.y = opt.top + (opt.height + statusBarPad) * 0.5
end
local barContainer = display.newGroup()
local background = display.newRect( barContainer, opt.x, opt.y, opt.width, opt.height + statusBarPad )
if ( opt.background ) then
background.fill = { type="image", filename=opt.background }
elseif ( opt.backgroundColor ) then
background.fill = opt.backgroundColor
else
background.fill = { 1, 1, 1 }
end
if ( opt.title ) then
local title = display.newText( opt.title, background.x, background.y + statusBarPad * 0.5, opt.font, opt.fontSize )
title:setFillColor( unpack(opt.titleColor) )
barContainer:insert( title )
end
local leftButton
if ( opt.leftButton ) then
if ( opt.leftButton.defaultFile ) then — 构建一个图像按钮
leftButton = widget.newButton({
id = opt.leftButton.id,
width = opt.leftButton.width,
height = opt.leftButton.height,
baseDir = opt.leftButton.baseDir,
defaultFile = opt.leftButton.defaultFile,
overFile = opt.leftButton.overFile,
onEvent = opt.leftButton.onEvent
})
else — 否则,构建一个文本按钮
leftButton = widget.newButton({
id = opt.leftButton.id,
label = opt.leftButton.label,
onEvent = opt.leftButton.onEvent,
font = opt.leftButton.font or opt.font,
fontSize = opt.fontSize,
labelColor = opt.leftButton.labelColor or { default={ 1, 1, 1 }, over={ 0, 0, 0, 0.5 } },
labelAlign = “left”,
})
end
leftButton.x = 15 + leftButton.width * 0.5
leftButton.y = title.y
barContainer:insert( leftButton ) — 将按钮插入容器组
end
local rightButton
if ( opt.rightButton ) then
if ( opt.rightButton.defaultFile ) then — 构建一个图像按钮
rightButton = widget.newButton({
id = opt.rightButton.id,
width = opt.rightButton.width,
height = opt.rightButton.height,
baseDir = opt.rightButton.baseDir,
defaultFile = opt.rightButton.defaultFile,
overFile = opt.rightButton.overFile,
onEvent = opt.rightButton.onEvent
})
else — 否则,构建一个文本按钮
rightButton = widget.newButton({
id = opt.rightButton.id,
label = opt.rightButton.label or “Default”,
onEvent = opt.rightButton.onEvent,
font = opt.leftButton.font or opt.font,
fontSize = opt.fontSize,
labelColor = opt.rightButton.labelColor or { default={ 1, 1, 1 }, over={ 0, 0, 0, 0.5 } },
labelAlign = “right”,
})
end
rightButton.x = display.contentWidth – ( 15 + rightButton.width * 0.5 )
rightButton.y = title.y
barContainer:insert( rightButton ) — 将按钮插入容器组
end
return barContainer
end
—————————代码结束——————————–
在 main.lua 文件中,我添加了
local navbar = require( “navbar” )
然后,声明了侦听器和按钮
———————– 代码开始——————-
local function handleLeftButton( event )
if ( event.phase == “ended” ) then
— 执行操作
end
return true
end
local function handleRightButton( event )
if ( event.phase == “ended” ) then
— 执行操作
end
return true
end
local leftButton = {
onEvent = handleLeftButton,
width = 60,
height = 34,
defaultFile = “icon.png”,
–overFile = “images/backbutton_over.png”
}
local rightButton = {
onEvent = handleRightButton,
label = “Add”,
labelColor = { default = {1, 1, 1}, over = { 0.5, 0.5, 0.5} },
font = “HelveticaNeue-Light”,
isBackButton = false
}
local navBar = widget.newNavigationBar({
title = “Pickle”,
backgroundColor = { 0.96, 0.62, 0.34 },
titleColor = {1, 1, 1},
leftButton = leftButton,
rightButton = rightButton,
includeStatusBar = true
})
——————–代码结束——————————–
错误消息是
navbar.lua:79: 尝试索引全局 'title'(nil 值) 堆栈回溯
所以我删除了第 79 行,即
leftButton.y = title.y
和
rightButton.y = title.y
它可以工作,但 y 位置不正确。
如何使按钮的 Y 位置与标题的 Y 位置相同?
Rob Miracle
发布于 12月25日 11:31请在论坛中提问。在评论中处理代码真的很难。
Yang
发布于 03月07日 17:51嗨,Rob,
很棒的教程,我能够轻松地在我的应用程序中实现它,我唯一的问题(?)是我无法设法更改 navBar 的按钮或标题的字体大小,并且按钮似乎比按钮上的实际标签大得多(如果它是一个标签按钮),因此你可以点击标题,它实际上会读取为好像你点击了右按钮(我尝试更改按钮的宽度,但它不起作用,我不确定它是否打算这样工作)。
谢谢,
-Yang L.
Rob Miracle
发布于 03月07日 18:48我最近注意到了这一点。我还没有解决方案。但是任何人都可以查看教程中的代码,并确保大小正在传递。
Yang
发布于 03月08日 18:29我为了修复它所做的是添加了
width = opt.rightButton.width
(左按钮也是如此)在将 navBar 添加到小部件库时添加到文本按钮中。
我应该在在这里发布评论之前抓住这一点,希望这对可能遇到同样问题的任何人有所帮助!
-Yang L.