Go Gio 实战:煮蛋计时器的实现 01 — 空窗口
接下来我们将一步步实通过 gio 实现一个煮蛋计时器。
这是完全从头开始实现的,使用 Gio 这个 Go GUI 库实现的独立 Go 应用程序,会解释每一个步骤。
实现的最终效果如下:
本系列大纲:
第 1 章 - 空窗口 第 2 章 - 标题和大小 第 3 章 - 按钮 第 4 章 - 低按钮(Low Button) 第 5 章 - 重构 第 6 章 - 带边距的按钮 第 7 章 - 进度条 第 8 章 - 画圆 第 9 章 画鸡蛋 第 10 章 - 输入沸腾时间
先看如何实现一个空窗口。
01 目标
本节的目的是创建一个空白画布,我们稍后可以在其上进行绘制。
02 主要内容
本节代码主要做三件事:
导入 Gio 创建并调用 goroutine: 创建一个新窗口: w
启动一个无限循环,等待窗口中的事件,具体事件后续实现
接下来看看具体的代码。
03 代码
package main
import (
"gioui.org/app"
)
func main() {
go func() {
// 创建一个新窗口(Window)
w := app.NewWindow()
// 监听窗口的事件
for range w.Events() {
}
}()
app.Main()
}
04 代码详解
代码看起来很简单。不过,还是花点时间看看发生了什么,毕竟 GUI 编程大家接触的少。
1)我们导入了 gioui.org/app
,它干什么用的?
我们需要查看文档:https://pkg.go.dev/gioui.org/app:
包 app 为运行图形用户界面的操作系统功能提供了一个独立于平台的界面。
这是很棒的特性。Gio 为我们处理所有与平台相关的事情,这和 Go 一样,是跨平台的。
这可能比你意识到的要更重要。因为,即使你今天的应用程序是单平台的,但指不定哪天想迁移到其他平台。
上篇文章简单介绍了,Gio 对各个平台都有支持,包括移动端,甚至 tvOS。
2)goroutine 中的事件循环
事件循环的代码是:
for range w.Events()
,它循环监听窗口中的事件。现在我们只是监听,并没有对事件做任何处理。后续章节会实现。从 app.Main[1] 我们了解到:
因为 Main 在某些平台上也是阻塞的,所以 Window 的事件循环必须在 goroutine 中运行。
有 Go 经验的同学应该理解这块内容。因为如果 app.Main 不堵塞,最后 main 函数就返回,程序退出了。
一个没有名字的 goroutine,即匿名函数,被创建并运行事件循环。由于它在 goroutine 中,它将与程序的其余部分同时运行。
go func {
// ...
}()这是 Go 语言常见的写法。
3)最后上文提到的调用 app.Main()
启动程序,app.Main 的文档提到:
Main 函数必须从程序的 main 函数中调用,以便将主线程的控制权移交给需要它的操作系统。Main 的具体实现,不同系统不一样。
比如 Unix、Windows 等系统,直接调用 select {},感兴趣的可以查看 gio 的源码。
不过,上面的简单代码,在 Mac 下运行不正常,会 panic:
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x40f7f85]
可以先忽略。如果就想看看,可以用下面代码:
package main
import (
"gioui.org/app"
"gioui.org/io/system"
"gioui.org/layout"
"gioui.org/op"
)
func main() {
go func() {
w := app.NewWindow()
var ops op.Ops
for e := range w.Events() {
switch e := e.(type) {
case system.FrameEvent:
gtx := layout.NewContext(&ops, e)
e.Frame(gtx.Ops)
}
}
}()
app.Main()
}
05 小结
通过 gio 创建 GUI 应用程序,和普通服务端应用程序类似:
一个循环,处理各种事件 一个堵塞,好比 select{} 或 HTTP 服务中的 http.ListenAndServe
赶紧动手试试吧!
参考资料
app.Main: https://pkg.go.dev/gioui.org/app#hdr-Main
推荐阅读