go 官方没有提供标准的 gui 框架,在 go 实现的几个 gui 库中,fyne 算是最出色的,它有着简洁的api、支持跨平台能力,且高度可扩展。也就是说,fyne 是可以用来开发 app 的。
本文将尝试介绍下 fyne,希望对大家快速上手这个 gui 框架有所帮助。我最近产生了不少想法,其中有些是对 gui 有要求的,就想着折腾用 go 实现,而不是用那些已经很流行和成熟的 gui 框架。
在写这篇文章时,顺手搞了下它的中文版文档,文档查看 www.poloxue.com/gofyne,希望对想继续深入这个框架的朋友有所帮助。

安装 fyne
开始前,确保已成功安装 go,如果是 macos x 系统,要确认安装了 xcode。
如下使用 go get 命令安装 fyne。
$ mkdir hellofyne $ cd helloyfyne $ go mod init hellofyne $ go get fyne.io/fyne/v2@latest $ go install fyne.io/fyne/v2/cmd/fyne@latest
如果想立刻查看 fyne 提供的演示案例,通过命令检查:
$ go run fyne.io/fyne/v2/cmd/fyne_demo@latest

看起来,这里面的案例还是不够丰富的。
安装工作到此就完成了。fyne 对不同系统有不同依赖,如果安装过程中遇到问题,细节可查看官方提供的安装文档。
创建第一个应用
由于 go 简洁的语法和 fyne 的设计,使用 fyne 创建一个 gui 应用异常简单。
以下是一个创建基础窗口的例子:
package main
import (
"fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/widget"
)
func main() {
a := app.new()
w := a.newwindow("hello fyne")
w.setcontent(widget.newlabel("welcome to fyne!"))
w.showandrun()
}这段代码创建了一个包含标签的窗口。
通过 app.new() 初始化一个 fyne 应用实例。然后,创建一个标题为 "hello fyne" 的窗口,并设置内容为包含 "welcome to fyne!" 文本标签。最后,通过w.showandrun()显示窗口并启动应用的事件循环。
fyne 中窗口的默认大小是其包含的内容决定,也可预置大小。
如在创建窗口后,提供 resize 重新调整大小。
w.resize(fyne.newsize(100, 100))
还有,一个 app 下可以有多个窗口的,示例代码:
btn := widget.newbutton("create a widnow", func() {
w2 := a.newwindow("window 02")
w2.resize(fyne.newsize(200, 200))
w2.show()
})
w.setcontent(btn)我们创建一个按钮,它的点击事件是创建一个新的窗口并显示。

布局和控件
布局和控件是 gui 应用程序设计中必不可少的两类组件。fyne 提供了多种布局管理器和标准的 ui 控件,支持创建更复杂的界面。
布局管理
fyne 中的布局的实现位于 container 包中。它提供了多种不同布局方式安排窗口中的元素。最基本的布局方式有 hbox 水平布局和 vbox 垂直布局。
通过 hbox 创建水平布局的代码如下:
w.setcontent(container.newhbox(widget.newbutton("left", func() {
fmt.println("left button clicked")
}), widget.newbutton("right", func() {
fmt.println("right button clicked")
})))显示效果:

通过 vbox 创建垂直布局的例子。
w.setcontent(container.newvbox(widget.newbutton("top", func() {
fmt.println("top button clicked")
}), widget.newbutton("bottom", func() {
fmt.println("bottom button clicked")
})))显示效果:

fyne 除了基础的水平(hboxlayout)和垂直(vboxlayout)布局,还提供了gridlayout、formlayout 甚至是混合布局 combinedlayout 等高级布局方式。
如 gridlayout可以将元素均匀地分布在网格中,而formlayout适用于创建表单,自动排列标签和字段。这些灵活的布局选项支持创建更复杂和功能丰富的gui界面。
官方文档中的布局列表查看:layout list。
更多控件
前面的演示案例中,用到了两个控件:label 和 button,fyne 还支持其他多种控件,它们都为于 widget 包中。
我尝试在一份代码中展示出来,如下是常见控件一览:
// 标签 label
label := widget.newlabel("label")
// 按钮 button
button := widget.newbutton("button", func() {})
// 输入框 entry
entry := widget.newentry()
entry.setplaceholder("entry")
// 复选框 check
check := widget.newcheck("check", func(bool) {})
// 单选框 check
radio := widget.newradiogroup([]string{"option 1", "option 2"}, func(string) {})
// 选择框
selectentry := widget.newselectentry([]string{"option a", "option b"}
// 进度条
progressbar := widget.newprogressbar()
// 滑块
slider := widget.newslider(0, 100)
// 组合框
combo := widget.newselect([]string{"option a", "option b", "option c"}, func(string) {})
// 表单项
formitem := widget.newformitem("formitem", widget.newentry())
form := widget.newform(formitem)
// 手风琴
accordion := widget.newaccordion(widget.newaccordionitem("accordion", widget.newlabel("content")))
// tab 选择
tabs := container.newapptabs(
container.newtabitem("tab 1", widget.newlabel("content 1")),
container.newtabitem("tab 2", widget.newlabel("content 2")),
)
// 弹出对话框示例按钮
dialogbutton := widget.newbutton("show dialog", func() {
dialog.showinformation("dialog", "dialog content", w)
})
// 滚动布局
content := container.newvscroll(container.newvbox(
label, button, entry, check, radio, selectentry, progressbar, slider,
combo, form, accordion, tabs, dialogbutton,
))
w.setcontent(content)演示效果:

fyne 中的自定义
如果在实际项目中使用 fyne,基本上是要使用 fyne 的自定义能力。fyne 提供了自定义控件、布局和主题等。
自定义控件
fyne 是支持实现自定义控件的,这涉及定义控件的绘制方法和布局逻辑。我们主要是实现两个接口:fyne.widget 和 fyne.widgetrenderer。
fyne.widget 的定义如下所示:
type widget interface {
canvasobject
createrenderer() widgetrenderer
}createrenderer 方法返回的就是 widdgetrenderer,用于定义控件渲染和布局的逻辑。
type widgetrenderer interface {
destroy()
layout(size)
minsize() size
objects() []canvasobject
refresh()
}这样拆分的目标是为将了控件的逻辑和 ui 绘制分离开来,在 widget 中专注于逻辑,而 widgetrenderer 中专注于渲染布局。
假设实现一个类似 label 的控件,类型定义:
type customlabel struct {
widget.basewidget
text string
}它继承了 wiget.basewidget 基本控件实现,text 就是要 label 显示的文本。还要给给 customlabel 实现 createrenderer 方法。
定义 customlabel 创建函数:
func newcustomlabel(text string) *customlabel {
label := &customlabel{text: text}
label.extendbasewidget(label)
return label
}customwidgetrenderer 类型定义如下:
type customwidgetrenderer struct {
text *canvas.text // 使用canvas.text来绘制文本
label *customlabel
}实现 customlabel 的 createrenderer 方法。
func (label *customlabel) createrenderer() fyne.widgetrenderer {
text := canvas.newtext(label.text, theme.foregroundcolor())
text.alignment = fyne.textaligncenter
return &customlabelrenderer{
text: text,
label: label,
}
}构建 renderer 变量,使用 canvas 创建 text 文本框,为适配主题使用主题配置前景色。还有,设置文本居中显示。
而 customlabelrenderer 要实现 widgetrender 接口定义的所有方法。
func (r *customlabelrenderer) minsize() fyne.size {
return r.text.minsize()
}
func (r *customlabelrenderer) layout(size fyne.size) {
r.text.resize(size)
}
func (r *customlabelrenderer) refresh() {
r.text.text = r.label.text
r.text.color = theme.foregroundcolor() // 确保文本颜色更新
r.text.refresh()
}
func (r *customlabelrenderer) backgroundcolor() color.color {
return theme.backgroundcolor()
}
func (r *customlabelrenderer) objects() []fyne.canvasobject {
return []fyne.canvasobject{r.text}
}
func (r *customlabelrenderer) destroy() {}在 main 函数中,尝试使用这个控件。
a := app.new()
w := a.newwindow("custom label")
w.setcontent(newcustomlabel("hello"))
w.showandrun()显示的效果和 label 控件是类似的。
其他自定义
其他自定义能力,如 layout、theme,我就不展开介绍。如果有机会,写点实际应用案例。如果基于案例介绍,会更有体悟吧。
还有,fyne 的官方文档写的挺易读的,可直接看它的文档。
数据绑定
fyne 从 v2.0.0 开始支持数据绑定。它让控件和与数据实时连接,数据更改会自动反映在ui上,反之亦然。
控件为了支持数据绑定能力,一般会提供如 newxxxwithdata 的接口。
直接通过一个场景说明,目标是编辑输入框的内容,可同时立刻显示到 label 上。
核心代码如下所示:
// 创建一个字符串绑定 textbind := binding.newstring() // 创建一个 entry,将其内容绑定到 textbind entry := widget.newentrywithdata(textbind) // 创建一个 label,也将其内容绑定到同一个 textbind label := widget.newlabelwithdata(textbind) // 使用容器放置 entry 和 label,以便它们都显示在窗口中 content := container.newvbox(entry, label)
如上的代码,创建一个数据绑定类型的变量 textbind,并将其通过 newwithdata 将其绑定到两个控件上。
显示效果,如下所示:

尝试让前面自定义的 customlabel 支持数据绑定能力。只要创建一个 newcustomlabelwithdata 构造函数。
如下所示:
func newcustomlabelwithdata(data binding.string) *customlabel {
label := &customlabel{}
label.extendbasewidget(label)
data.addlistener(binding.newdatalistener(func() {
text, _ := data.get()
label.text = text
label.refresh()
}))
return label
}如上的代码中,其实就是通过 data 这个数据绑定类型变量监听数据变化,监听到变化后,更新空间内容并立刻刷新控件显示。

如上所示,我们这个自定义 label 中的文本是居中显示的。
数据绑定的核心是监听器模式(observer pattern)。每个绑定对象内部维护了一个监听器列表,当数据变化时,这些监听器会被通知更新。
在 fyne 中,通过 data.addlistner() 将 ui 组件与数据绑定对象绑定时,实际上是在数据对象上注册了一个监听器,这个监听器会在数据变化时更新 ui 组件的状态。
结语
fyne 是简单、强大和跨平台的 gui 工具,使得用 go 开发现代 gui 应用多了一个优秀选择。随着对 fyne 的深入,它能够更加灵活地构建出符合需求的应用。
我喜欢用 go 的原因,很重要的原因就是它的简洁性,很容易看到本质的东西,但又无需理解太复杂的编程概念。
以上就是golang跨平台gui框架fyne的使用教程详解的详细内容,更多关于go fyne的资料请关注代码网其它相关文章!
发表评论