Go Gio 实战:煮蛋计时器的实现 03— 按钮
争做团队核心程序员,关注「幽鬼」
大家好,我是程序员幽鬼。
上篇文章介绍了空窗口的实现。今天看看如何控制窗口,比如标题、大小等。
01 目标
GUI 程序,按钮是少不了的,对按钮点击事件的响应,是必须掌握的知识点。通过本文的学习,可以掌握 GUI 事件响应的编程方法。
本节效果如下:
02 主要内容
本节将介绍许多新组件,不过不会太深入细节,而是专注于程序的整体结构。虽然只是增加按钮,但需要导入不少相关新包,因此需要花点时间了解下。之后会看看如何联合 operations
和widgets
来制作一个按钮。
最后,我们谈到了 Material Design[1],这是一个完善的用户界面框架(Google 出品的),也可以在 Gio 中使用。
03 需要用的到包
本节会导入如下包:
import (
"gioui.org/app"
"gioui.org/font/gofont"
"gioui.org/io/system"
"gioui.org/layout"
"gioui.org/op"
"gioui.org/unit"
"gioui.org/widget"
"gioui.org/widget/material"
)
对这些包做一个说明,其中 app
和 unit
之前有介绍过,因此介绍其他的包。
font/gofont[2] - Go 自己有一套高质量的 True Type 字体,这是对其进行封装,导出给 gioui.org/text 包使用。关于官方的这套字体,可以阅读 Go 官方的博文:https://go.dev/blog/go-fonts。
io/system[3] - 提供从窗口发送的高级事件。最重要的是
system.FrameEvent
。它实际上是执行以下两项操作之一的操作列表:详细说明如何处理输入并描述要显示的内容。layout[4] - 定义布局中的有用可变部分,例如尺寸、约束和方向。此外,它还包括称为 Flexbox[5] 的布局概念。它被广泛用于 Web 和用户界面开发,详情可以参考 Mozilla 上的介绍[6]。
op[7] - Operations 或 ops 是 Gio 的核心。它们用于更新用户界面。有一些操作用于绘制、处理输入、更改窗口属性、缩放、旋转等。有趣的是还有宏[8],可以记录稍后要执行的操作。总之,这意味着操作列表是一个可变堆栈,你可以在其中控制流程。
widget[9] - 小部件提供 UI 组件的底层功能,例如状态跟踪和事件处理。鼠标是否悬停在按钮上?是否被点击过,如果有,点击了多少次?
widget/material[10] - 虽然
widget
提供功能,但widget/material
定义了一个主题。请注意,该界面实际上分为两部分:这是为了提高小部件的可重用性和灵活性。我们稍后会用到它。
默认看起来不错,也是我们将使用的,但通过设置颜色、文本大小字体属性等属性也很容易调整。
注意:Gio 在名为 gio-x[11] 的专用存储库中扩展了基本功能,其中正在开发更多材质组件[12],包括导航栏和工具提示。
具有状态的实际小部件 小部件的绘制,完全无状态
04 功能实现
看看本文开头提到的按钮功能如何实现,代码如下:
func main() {
go func() {
// 创建一个新窗口
w := app.NewWindow(
app.Title("煮蛋计时器"),
app.Size(unit.Dp(400), unit.Dp(600)),
)
// ops 表示 UI 上的操作
var ops op.Ops
// startButton 时候一个可点击的小部件
var startButton widget.Clickable
// th 定义 material design(材料设计)的风格
th := material.NewTheme(gofont.Collection())
// 循环监听窗口上的事件
for e := range w.Events() {
// 监听事件的类型
switch e := e.(type) {
// 当应用程序需要重新渲染是发送该事件
case system.FrameEvent:
gtx := layout.NewContext(&ops, e)
btn := material.Button(th, &startButton, "Start")
btn.Layout(gtx)
e.Frame(gtx.Ops)
}
}
}()
app.Main()
}
05 代码详解
主要关注这次新增的内容。
1)设置了三个新变量
ops
从用户界面定义操作,具体的作用说明,介绍 op 包时讲解了。startButton
是我们的按钮,一个可点击的小部件。th
是材料设计主题,设置字体为 gofonts。
2)事件循环代码:
for e:= range w.Events()
是重点:
w.Events()
方法的返回值类型是:<-chan event.Event
,为我们提供了传递事件的 channel。我们只是循环监听。event.Event
是一个接口。因此,在循环里面,我们需要进行断言,对不同的事件做不同的响应。在这个例子中,我们只对事件 system.FrameEvent
进行处理。具体处理逻辑:请注意,我们如何轻松获得所有鼠标悬停和点击动画。它们都是主题(theme)的一部分,很赞。 我们定义一个新的图形上下文即 gtx
。它接收指向ops
的指针以及事件system.FrameEvent
。btn
是实际的按钮,material.Button
接收主题th
和指向startButton
小部件的指针。我们还定义了显示的文本(注意,文本是纯粹显示在按钮上的,而不是按钮这个有状态小部件的一部分。)btn.Layout(gtx)
表示按钮btn
要求在上下文gtx
中显示自己。这是关键。布局不布局按钮,按钮自己布局。这非常方便,例如尝试调整窗口大小。无论画布的大小或形状如何,按钮都会重新调整自己。最终,我们实际将操作 ops
从上下文gtx
发送到 FrameEvente
。
最后,记得实际动手运行看看。我们并没有设置实际的按钮点击样式,Gio 自动帮我们做了。
06 小结
本节虽然只是实现一个按钮,但涉及到的知识点不少,关键代码就几行,但需要花些时间了解下相关 API 的用法,掌握使用 Gio 实现 GUI 的关键步骤和思路。
参考:https://jonegil.github.io/gui-with-gio/egg_timer/03_button.html。
参考资料
[1]Material Design: https://material.io/
[2]font/gofont: https://pkg.go.dev/gioui.org/font/gofont
[3]io/system: https://pkg.go.dev/gioui.org/io/system
[4]layout: https://pkg.go.dev/gioui.org/layout
[5]Flexbox: https://pkg.go.dev/gioui.org/layout#Flex
[6]Mozilla 上的介绍: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Flexible_Box_Layout/Basic_Concepts_of_Flexbox
[7]op: https://pkg.go.dev/gioui.org/op
[8]宏: https://pkg.go.dev/gioui.org/op#MacroOp
[9]widget: https://pkg.go.dev/gioui.org/widget
[10]widget/material: https://pkg.go.dev/gioui.org/widget/material
[11]gio-x: https://pkg.go.dev/gioui.org/x
[12]更多材质组件: https://pkg.go.dev/gioui.org/x/component
往期推荐
欢迎关注「幽鬼」,像她一样做团队的核心。