Drawer、Dialog 与 Toast¶
三种现成的弹窗:抽屉 widget.Drawer 从屏幕边缘滑进来,放一组操作或选项;对话框 widget.Dialog 落在屏幕中间,请用户确认一件事;widget.Toast 在底部冒出一行字,两秒后自己消失。它们都建立在层栈的 ui.Open 上,边框和内边距由层栈画、由层栈留,弹窗内容自己不要再加。
抽屉¶

share := widget.NewListItem("Share score", widget.OnClick(func() {
ui.Do(func() {
d := widget.NewDrawer(widget.Bottom, "Send to")
for _, name := range []string{"Phone", "Laptop"} {
ui.Add(d, widget.NewListItem(name, widget.OnClick(func() {
ui.Do(func() {
d.Close()
widget.Toast("Sent to " + name)
})
})))
}
d.Open()
})
}))
NewDrawer(edge, title) 建一个竖向排列的抽屉,标题是一行灰色小字,传空字符串就没有标题。用 ui.Add 往里放内容,标题下面依次排列;它本身是一个 ui.Flex,ui.Gap 之类的选项也能用。Open 和 Close 都要在 UI goroutine 上调用,所以例子在回调里包了 ui.Do;列表项的回调里先 Close 再弹 Toast,两件事在同一个 ui.Do 里完成。
抽屉的大小由内容决定:
| 边 | 位置与大小 | 弹窗种类 |
|---|---|---|
widget.Bottom |
贴底,宽度占满,高度等于内容 | ui.DrawerBottom |
widget.Top |
贴顶,宽度占满,高度等于内容 | ui.DrawerTop |
widget.Left |
贴左,高度占满,宽度等于内容 | ui.DrawerLeft |
widget.Right |
贴右,高度占满,宽度等于内容 | ui.DrawerRight |
抽屉的边框最多和屏幕一样大,内容再多也不会自动滚动。选项很多时放一个 List 并给它固定高度,Select 就是这样做的:它把列表高度限制在屏幕高度的 80% 减去 TitleHeight()(标题连同内边距的高度)和上边的内边距。
侧边抽屉适合做导航菜单:

menu := widget.NewDrawer(widget.Left, "Menu")
ui.Add(menu,
widget.NewListItem("Home"),
widget.NewListItem("Maps"),
widget.NewListItem("Settings"),
)
open := widget.NewListItem("Open menu", widget.OnClick(func() {
ui.Do(menu.Open)
}))
抽屉可以先建好、多次打开,ui.Do(menu.Open) 直接把方法值交给 ui.Do。抽屉打开期间独占焦点和按键:打开时焦点移到它的第一个可聚焦节点,关闭后回到打开前的位置。按下 Backspace 时抽屉朝来的方向让开一点,光标跟着歪过去,松开就关掉;这套返回手势对所有页面和弹窗都一样(见返回)。
抽屉贴着的那一边没有边框和内边距,边框从屏幕外绕过来,只露出里侧的两个圆角,圆角是主题 Radius 的两倍。进出用 anim.Smooth。
对话框¶

del := widget.NewListItem("Delete map", widget.OnClick(func() {
ui.Do(func() {
widget.NewDialog("Delete map?", "Blue Zenith goes for good, scores and all",
widget.NewButton("Keep"),
widget.NewButton("Delete", widget.OnClick(func() {
time.Sleep(700 * time.Millisecond) // removing the files
ui.Do(func() { widget.Toast("Deleted") })
})),
).Open()
})
}))
NewDialog(title, message, buttons...) 从上往下排:居中的标题、居中的一行小字说明、一排按钮。说明只有一行,放不下时一直滚动(跑马灯),传空字符串则不显示。按钮平分对话框的宽度,对话框本身占满屏幕宽度减去两边各 4 个单位。
对话框落进屏幕中间,第一个按钮获得焦点。每个按钮的 OnClick 照常执行,加载点和失败的抖动都和普通 Button 一样;回调返回 nil 后对话框自动关闭,返回 error 时对话框留着,用户可以重试或换一个按钮。没有设 OnClick 的按钮(例子里的 Keep)按下就关闭对话框。按 Backspace 也会关闭。
所以回调里不需要自己关对话框,只管做事:例子里 Delete 的回调睡 700ms 模拟删除文件,期间按钮显示加载点,返回后对话框从上方退出,回调里弹出的 Toast 留在页面上。
Title() 和 Message() 返回标题和说明的 Label,可以在打开前后改文字。
Toast¶

save := widget.NewListItem("Save to slot 2", widget.OnClick(func() {
ui.Do(func() { widget.Toast("Saved to slot 2") })
}))
load := widget.NewListItem("Load slot 2", widget.OnClick(func() {
ui.Do(func() { widget.Toast("Loaded slot 2") })
}))
widget.Toast(text) 在屏幕底部显示一行小字,2 秒后自动关闭。它不拿焦点、不收按键,所以显示期间用户照常操作页面,动图里光标一直可以上下移动。新的 Toast 会立刻替换还在显示的旧的,不会排队。文字太长时一直滚动。
Toast 会改动节点树,必须在 UI goroutine 上调用,回调里要包在 ui.Do 里。
与层栈的关系¶
三种组件都是对 ui.Open(node, kind) 的包装,kind 决定弹窗的位置、进出方向以及是否独占按键:
ui.Popup |
位置 | 独占焦点和按键 | 对应组件 |
|---|---|---|---|
ui.DrawerBottom、ui.DrawerTop、ui.DrawerLeft、ui.DrawerRight |
贴边 | 是 | Drawer、软键盘 |
ui.Dialog |
屏幕中间 | 是 | Dialog |
ui.Toast |
底部 | 否 | Toast |
要做别的弹窗,直接把任意节点交给 ui.Open,用 ui.Close(node) 关掉,ui.Pop() 关掉最上面的一层;自定义输入法里的抽屉就是例子。弹窗和 ui.Push 推入的页面共用一个层栈,规则见页面与弹窗。
API¶
type Edge uint8
const (
Bottom Edge = iota
Top
Left
Right
)
func NewDrawer(edge Edge, title string, opts ...ui.Option) *Drawer
func (d *Drawer) Open() // 滑入,在 UI goroutine 上调用
func (d *Drawer) Close() // 滑出
func (d *Drawer) Edge() Edge
func (d *Drawer) Title() *Label // 标题,没有标题时为 nil
func (d *Drawer) TitleHeight() float32 // 标题占的高度(像素),没有标题时为 0
func NewDialog(title, message string, buttons ...*Button) *Dialog
func (d *Dialog) Open() // 显示并聚焦第一个按钮,在 UI goroutine 上调用
func (d *Dialog) Close()
func (d *Dialog) Title() *Label
func (d *Dialog) Message() *Label
func Toast(text string) // 显示 2 秒,在 UI goroutine 上调用
| 选项 | 作用 |
|---|---|
ui.Gap(g) |
抽屉里各项之间的间距 |
ui.Padding、ui.Width 等 |
任何 ui.Option 都可以用在 NewDrawer 上 |
NewDialog 不接受选项,需要时对返回值调用 ui.Apply。
默认样式取自主题:弹窗底色 Background,边框 Foreground,圆角 Radius,边框内留 PanelPadding;抽屉标题和 Toast 用 SmallFont 和 RowPadding,抽屉标题的颜色是 Muted;对话框的说明用 SmallFont,各行之间和按钮之间隔 Gap(见主题与单位)。