引言
Fyne 是一个使用 Go 语言编写的、易于使用的跨平台 GUI 工具包和应用程序 API。它旨在通过单一代码库构建在桌面和移动设备上运行的应用程序。本文档面向有一定 Go 语言开发经验的开发者,将详细介绍 Fyne 最新版的核心功能,包括基础组件、布局系统、事件处理、自定义组件和主题定制,并提供简洁、可运行的代码示例。
在开始之前,请确保您已安装 Go (建议 1.19 或更高版本) 和 GCC 编译器。
安装 Fyne 最新版:
go get fyne.io/fyne/v2@latest
基础组件 (Basic Widgets)
Fyne 提供了一系列丰富的内置组件,用于构建用户界面。这些组件会自动适应当前主题,并处理用户交互。
1. Label (标签)
widget.Label
用于显示静态文本。它可以处理简单的格式化(如换行 \n
)和文本换行(通过设置 Wrapping
字段)。
package main
import (
"fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/widget"
)
func main() {
myApp := app.New()
myWindow := myApp.NewWindow("Label Widget")
// 创建一个简单的标签
label := widget.NewLabel("这是一个 Fyne 标签")
myWindow.SetContent(label)
myWindow.ShowAndRun()
}
2. Button (按钮)
widget.Button
用于触发操作。它可以包含文本、图标或两者兼有。构造函数包括 widget.NewButton
和 widget.NewButtonWithIcon
。
package main
import (
"log"
"fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/widget"
// "fyne.io/fyne/v2/theme" // 如果使用图标,取消注释
)
func main() {
myApp := app.New()
myWindow := myApp.NewWindow("Button Widget")
// 创建一个文本按钮,并设置点击回调
button := widget.NewButton("点我", func() {
log.Println("按钮被点击了")
})
// 创建一个带图标的按钮 (需要 theme 包)
/*
iconButton := widget.NewButtonWithIcon("主页", theme.HomeIcon(), func() {
log.Println("带图标的按钮被点击了")
})
*/
myWindow.SetContent(button) // 或 iconButton
myWindow.ShowAndRun()
}
3. Entry (输入框)
widget.Entry
用于接收用户输入的单行或多行文本。可以通过 Text
字段获取内容,或使用 OnChanged
回调实时获取变化。
widget.NewPasswordEntry
用于创建密码输入框。
package main
import (
"log"
"fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/widget"
)
func main() {
myApp := app.New()
myWindow := myApp.NewWindow("Entry Widget")
// 创建一个输入框
input := widget.NewEntry()
input.SetPlaceHolder("请输入文本...") // 设置占位符
// 创建一个密码输入框
password := widget.NewPasswordEntry()
password.SetPlaceHolder("请输入密码...")
// 获取输入内容的按钮
submitBtn := widget.NewButton("提交", func() {
log.Println("输入内容:", input.Text)
log.Println("密码内容:", password.Text)
})
content := container.NewVBox(input, password, submitBtn)
myWindow.SetContent(content)
myWindow.ShowAndRun()
}
4. Choices (选择控件)
Fyne 提供了多种选择控件,如复选框 (widget.Check
)、单选按钮组 (widget.RadioGroup
) 和下拉选择框 (widget.Select
)。
package main
import (
"log"
"fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/widget"
)
func main() {
myApp := app.New()
myWindow := myApp.NewWindow("Choice Widgets")
// 复选框
check := widget.NewCheck("启用选项", func(checked bool) {
log.Println("复选框状态:", checked)
})
// 单选按钮组
radio := widget.NewRadioGroup([]string{"选项 A", "选项 B"}, func(selected string) {
log.Println("单选按钮选择:", selected)
})
radio.Horizontal = true // 水平排列
// 下拉选择框
selectEntry := widget.NewSelect([]string{"选项 1", "选项 2", "选项 3"}, func(selected string) {
log.Println("下拉框选择:", selected)
})
content := container.NewVBox(check, radio, selectEntry)
myWindow.SetContent(content)
myWindow.ShowAndRun()
}
5. Form (表单)
widget.Form
用于方便地布局标签和输入字段,并可选择性地添加提交和取消按钮。
package main
import (
"log"
"fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/widget"
)
func main() {
myApp := app.New()
myWindow := myApp.NewWindow("Form Widget")
nameEntry := widget.NewEntry()
emailEntry := widget.NewEntry()
messageEntry := widget.NewMultiLineEntry() // 多行输入
// 创建表单项
form := &widget.Form{
Items: []*widget.FormItem{
{Text: "姓名", Widget: nameEntry},
{Text: "邮箱", Widget: emailEntry},
},
OnSubmit: func() {
log.Println("表单提交:")
log.Println("姓名:", nameEntry.Text)
log.Println("邮箱:", emailEntry.Text)
log.Println("消息:", messageEntry.Text)
myWindow.Close()
},
OnCancel: func() {
log.Println("表单取消")
myWindow.Close()
},
SubmitText: "发送", // 自定义提交按钮文本
CancelText: "放弃", // 自定义取消按钮文本
}
// 动态添加表单项
form.Append("消息", messageEntry)
myWindow.SetContent(form)
myWindow.ShowAndRun()
}
布局系统 (Layout System)
Fyne 的布局系统负责排列和调整界面元素的大小。布局管理器决定了容器中元素的位置和尺寸。
1. Box 布局
Box 布局是最常用的布局,有水平 (HBox) 和垂直 (VBox) 两种变体。
package main
import (
"image/color"
"fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/layout"
)
func main() {
myApp := app.New()
myWindow := myApp.NewWindow("Box Layout")
// 创建三个文本对象
text1 := canvas.NewText("左侧", color.White)
text2 := canvas.NewText("中间", color.White)
text3 := canvas.NewText("右侧", color.White)
// 水平布局,使用弹性空间将第三个元素推到右侧
hBox := container.New(layout.NewHBoxLayout(),
text1,
text2,
layout.NewSpacer(), // 弹性空间
text3,
)
// 垂直布局
vBox := container.New(layout.NewVBoxLayout(),
canvas.NewText("顶部", color.White),
canvas.NewText("中部", color.White),
layout.NewSpacer(), // 弹性空间
canvas.NewText("底部", color.White),
)
// 组合布局
content := container.New(layout.NewVBoxLayout(), hBox, vBox)
myWindow.SetContent(content)
myWindow.ShowAndRun()
}
2. Grid 布局
Grid 布局将元素排列在固定大小的网格中。
package main
import (
"image/color"
"strconv"
"fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/layout"
)
func main() {
myApp := app.New()
myWindow := myApp.NewWindow("Grid Layout")
// 创建一个 3x3 的网格
var gridItems []interface{}
for i := 1; i <= 9; i++ {
text := canvas.NewText(strconv.Itoa(i), color.White)
text.Alignment = fyne.TextAlignCenter
gridItems = append(gridItems, text)
}
// 使用 GridLayout,3 列
grid := container.New(layout.NewGridLayout(3), gridItems...)
myWindow.SetContent(grid)
myWindow.ShowAndRun()
}
3. GridWrap 布局
GridWrap 布局类似于 Grid,但不固定列数,而是根据容器大小自动调整。
package main
import (
"image/color"
"strconv"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/layout"
)
func main() {
myApp := app.New()
myWindow := myApp.NewWindow("GridWrap Layout")
// 创建多个文本对象
var items []interface{}
for i := 1; i <= 12; i++ {
text := canvas.NewText(strconv.Itoa(i), color.White)
text.Alignment = fyne.TextAlignCenter
items = append(items, text)
}
// 使用 GridWrapLayout,每个单元格大小为 50x50
gridWrap := container.New(layout.NewGridWrapLayout(fyne.NewSize(50, 50)), items...)
myWindow.Resize(fyne.NewSize(300, 200))
myWindow.SetContent(gridWrap)
myWindow.ShowAndRun()
}
4. Border 布局
Border 布局允许在容器的四个边缘和中心放置元素。
package main
import (
"fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/widget"
)
func main() {
myApp := app.New()
myWindow := myApp.NewWindow("Border Layout")
// 创建边框布局
top := widget.NewLabel("顶部")
bottom := widget.NewLabel("底部")
left := widget.NewLabel("左侧")
right := widget.NewLabel("右侧")
center := widget.NewLabel("中心内容")
border := container.New(layout.NewBorderLayout(top, bottom, left, right),
top, bottom, left, right, center)
myWindow.SetContent(border)
myWindow.ShowAndRun()
}
5. Center 布局
Center 布局将单个元素居中显示。
package main
import (
"fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/widget"
)
func main() {
myApp := app.New()
myWindow := myApp.NewWindow("Center Layout")
// 创建一个按钮并居中显示
button := widget.NewButton("居中按钮", func() {})
centered := container.New(layout.NewCenterLayout(), button)
myWindow.SetContent(centered)
myWindow.ShowAndRun()
}
事件处理 (Event Handling)
Fyne 提供了多种方式来处理用户交互事件。从 Fyne v2.6.0 开始,所有事件和回调都在同一个 goroutine 上执行,这提高了性能并改善了线程安全性。
1. 基本回调函数
最简单的事件处理方式是通过组件构造函数提供回调函数。
package main
import (
"log"
"fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/widget"
)
func main() {
myApp := app.New()
myWindow := myApp.NewWindow("Event Callbacks")
// 按钮点击事件
button := widget.NewButton("点击我", func() {
log.Println("按钮被点击")
})
// 输入框内容变化事件
entry := widget.NewEntry()
entry.OnChanged = func(text string) {
log.Println("输入内容变化:", text)
}
// 复选框状态变化事件
check := widget.NewCheck("选择", func(checked bool) {
log.Println("复选框状态:", checked)
})
content := container.NewVBox(button, entry, check)
myWindow.SetContent(content)
myWindow.ShowAndRun()
}
2. 使用 Goroutines
虽然 Fyne 的事件处理在单一 goroutine 上执行,但有时需要在后台执行耗时操作。
package main
import (
"log"
"time"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/widget"
)
func main() {
myApp := app.New()
myWindow := myApp.NewWindow("Goroutine Example")
progress := widget.NewProgressBar()
status := widget.NewLabel("就绪")
// 启动耗时操作的按钮
startBtn := widget.NewButton("开始处理", func() {
status.SetText("处理中...")
progress.SetValue(0)
// 在后台 goroutine 中执行耗时操作
go func() {
// 模拟耗时操作
for i := 0.0; i <= 1.0; i += 0.1 {
time.Sleep(200 * time.Millisecond)
// 更新 UI 必须在主 goroutine 上执行
fyne.CurrentApp().Driver().RunOnMain(func() {
progress.SetValue(i)
})
}
// 完成后更新 UI
fyne.CurrentApp().Driver().RunOnMain(func() {
status.SetText("处理完成")
})
}()
})
content := container.NewVBox(startBtn, progress, status)
myWindow.SetContent(content)
myWindow.ShowAndRun()
}
3. 键盘和鼠标事件
Fyne 允许监听键盘和鼠标事件。
package main
import (
"fmt"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/widget"
)
func main() {
myApp := app.New()
myWindow := myApp.NewWindow("Input Events")
eventLog := widget.NewMultiLineEntry()
eventLog.Wrapping = fyne.TextWrapWord
// 创建一个可以接收键盘焦点的画布
canvas := container.NewVBox()
canvas.Resize(fyne.NewSize(300, 200))
// 键盘事件
myWindow.Canvas().SetOnTypedKey(func(key *fyne.KeyEvent) {
eventLog.Text += fmt.Sprintf("键盘事件: %v\n", key.Name)
eventLog.Refresh()
})
// 鼠标事件
myWindow.Canvas().SetOnTapped(func(pe *fyne.PointEvent) {
eventLog.Text += fmt.Sprintf("点击事件: 位置 x=%v, y=%v\n", pe.Position.X, pe.Position.Y)
eventLog.Refresh()
})
content := container.NewVBox(
widget.NewLabel("在窗口中点击或按键:"),
canvas,
widget.NewLabel("事件日志:"),
eventLog,
)
myWindow.SetContent(content)
myWindow.Resize(fyne.NewSize(400, 300))
myWindow.ShowAndRun()
}
自定义组件 (Custom Widgets)
Fyne 允许开发者创建自定义组件,以满足特定需求。
1. 创建自定义布局
自定义布局需要实现 fyne.Layout
接口,该接口定义了如何排列和调整容器中的对象大小。
package main
import (
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/widget"
)
// 自定义布局:将所有元素堆叠在一起,但每个元素有一个偏移量
type OffsetLayout struct {
Offset fyne.Position
}
// MinSize 计算所有子元素所需的最小尺寸
func (o *OffsetLayout) MinSize(objects []fyne.CanvasObject) fyne.Size {
minSize := fyne.NewSize(0, 0)
for _, obj := range objects {
objMin := obj.MinSize()
minSize.Width = fyne.Max(minSize.Width, objMin.Width+o.Offset.X*float32(len(objects)-1))
minSize.Height = fyne.Max(minSize.Height, objMin.Height+o.Offset.Y*float32(len(objects)-1))
}
return minSize
}
// Layout 排列子元素
func (o *OffsetLayout) Layout(objects []fyne.CanvasObject, containerSize fyne.Size) {
pos := fyne.NewPos(0, 0)
for _, obj := range objects {
size := obj.MinSize()
obj.Resize(size)
obj.Move(pos)
pos = pos.Add(o.Offset)
}
}
func main() {
myApp := app.New()
myWindow := myApp.NewWindow("Custom Layout")
// 创建三个按钮
btn1 := widget.NewButton("按钮 1", nil)
btn2 := widget.NewButton("按钮 2", nil)
btn3 := widget.NewButton("按钮 3", nil)
// 使用自定义布局
customLayout := &OffsetLayout{Offset: fyne.NewPos(10, 10)}
content := container.New(customLayout, btn1, btn2, btn3)
myWindow.SetContent(content)
myWindow.Resize(fyne.NewSize(200, 200))
myWindow.ShowAndRun()
}
2. 创建自定义小部件
自定义小部件需要实现 fyne.Widget
接口,通常通过嵌入 widget.BaseWidget
来简化实现。
package main
import (
"image/color"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/widget"
)
// 自定义彩色按钮小部件
type ColoredButton struct {
widget.BaseWidget
Text string
BgColor color.Color
OnTapped func()
}
// 创建新的彩色按钮
func NewColoredButton(text string, bgColor color.Color, tapped func()) *ColoredButton {
button := &ColoredButton{
Text: text,
BgColor: bgColor,
OnTapped: tapped,
}
button.ExtendBaseWidget(button)
return button
}
// CreateRenderer 是实现 Widget 接口的必要方法
func (b *ColoredButton) CreateRenderer() fyne.WidgetRenderer {
background := canvas.NewRectangle(b.BgColor)
text := canvas.NewText(b.Text, color.White)
text.Alignment = fyne.TextAlignCenter
return &coloredButtonRenderer{
button: b,
background: background,
text: text,
objects: []fyne.CanvasObject{background, text},
}
}
// 按钮渲染器
type coloredButtonRenderer struct {
button *ColoredButton
background *canvas.Rectangle
text *canvas.Text
objects []fyne.CanvasObject
}
func (r *coloredButtonRenderer) MinSize() fyne.Size {
return fyne.NewSize(100, 40) // 固定大小
}
func (r *coloredButtonRenderer) Layout(size fyne.Size) {
r.background.Resize(size)
r.text.Resize(size)
}
func (r *coloredButtonRenderer) Refresh() {
r.background.FillColor = r.button.BgColor
r.text.Text = r.button.Text
r.background.Refresh()
r.text.Refresh()
}
func (r *coloredButtonRenderer) Objects() []fyne.CanvasObject {
return r.objects
}
func (r *coloredButtonRenderer) Destroy() {}
// 实现 Tappable 接口
func (b *ColoredButton) Tapped(*fyne.PointEvent) {
if b.OnTapped != nil {
b.OnTapped()
}
}
func main() {
myApp := app.New()
myWindow := myApp.NewWindow("Custom Widget")
// 创建自定义彩色按钮
redButton := NewColoredButton("红色按钮", color.NRGBA{R: 200, G: 30, B: 30, A: 255}, func() {
println("红色按钮被点击")
})
blueButton := NewColoredButton("蓝色按钮", color.NRGBA{R: 30, G: 30, B: 200, A: 255}, func() {
println("蓝色按钮被点击")
})
greenButton := NewColoredButton("绿色按钮", color.NRGBA{R: 30, G: 200, B: 30, A: 255}, func() {
println("绿色按钮被点击")
})
// 垂直排列按钮
content := container.NewVBox(redButton, blueButton, greenButton)
myWindow.SetContent(content)
myWindow.ShowAndRun()
}
主题定制 (Theme Customization)
Fyne 允许通过实现 fyne.Theme
接口来自定义应用程序的外观。
package main
import (
"image/color"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget"
)
// 自定义主题
type myTheme struct{}
// 确保实现了 fyne.Theme 接口
var _ fyne.Theme = (*myTheme)(nil)
// 自定义颜色
func (m myTheme) Color(name fyne.ThemeColorName, variant fyne.ThemeVariant) color.Color {
if name == theme.ColorNameBackground {
if variant == theme.VariantLight {
return color.NRGBA{R: 0xf0, G: 0xf0, B: 0xff, A: 0xff} // 浅蓝色背景
}
return color.NRGBA{R: 0x30, G: 0x30, B: 0x40, A: 0xff} // 深蓝色背景
}
if name == theme.ColorNamePrimary {
return color.NRGBA{R: 0x80, G: 0x80, B: 0xff, A: 0xff} // 紫蓝色主色调
}
// 其他颜色使用默认主题
return theme.DefaultTheme().Color(name, variant)
}
// 使用默认字体
func (m myTheme) Font(style fyne.TextStyle) fyne.Resource {
return theme.DefaultTheme().Font(style)
}
// 使用默认图标
func (m myTheme) Icon(name fyne.ThemeIconName) fyne.Resource {
return theme.DefaultTheme().Icon(name)
}
// 自定义尺寸
func (m myTheme) Size(name fyne.ThemeSizeName) float32 {
if name == theme.SizeNamePadding {
return 10 // 自定义内边距
}
return theme.DefaultTheme().Size(name)
}
func main() {
myApp := app.New()
// 应用自定义主题
myApp.Settings().SetTheme(&myTheme{})
myWindow := myApp.NewWindow("Custom Theme")
// 创建一些组件来展示主题
label := widget.NewLabel("自定义主题示例")
button := widget.NewButton("按钮", func() {})
entry := widget.NewEntry()
entry.SetPlaceHolder("输入文本...")
check := widget.NewCheck("复选框", nil)
radio := widget.NewRadioGroup([]string{"选项1", "选项2"}, nil)
content := container.NewVBox(
label,
button,
entry,
check,
radio,
)
myWindow.SetContent(content)
myWindow.Resize(fyne.NewSize(300, 300))
myWindow.ShowAndRun()
}
四则运算计算器示例
下面是一个完整的四则运算计算器 GUI 示例,展示了 Fyne 的实际应用。
package main
import (
"fmt"
"strconv"
"strings"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget"
)
func main() {
myApp := app.New()
myWindow := myApp.NewWindow("四则运算计算器")
// 显示结果的输入框
display := widget.NewLabel("")
display.SetText("0")
// 存储操作数和运算符
var (
firstNumber float64 = 0
operator string = ""
resetDisplay bool = true
)
// 数字按钮处理函数
numberPressed := func(num string) {
if resetDisplay {
display.SetText(num)
resetDisplay = false
} else {
current := display.Text
if current == "0" {
display.SetText(num)
} else {
display.SetText(current + num)
}
}
}
// 运算符按钮处理函数
operatorPressed := func(op string) {
var err error
firstNumber, err = strconv.ParseFloat(display.Text, 64)
if err != nil {
display.SetText("错误")
return
}
operator = op
resetDisplay = true
}
// 等号按钮处理函数
equalsPressed := func() {
if operator == "" {
return
}
secondNumber, err := strconv.ParseFloat(display.Text, 64)
if err != nil {
display.SetText("错误")
return
}
var result float64
switch operator {
case "+":
result = firstNumber + secondNumber
case "-":
result = firstNumber - secondNumber
case "*":
result = firstNumber * secondNumber
case "/":
if secondNumber == 0 {
display.SetText("除数不能为零")
return
}
result = firstNumber / secondNumber
}
// 格式化结果,去除不必要的小数点和零
resultStr := fmt.Sprintf("%.6f", result)
resultStr = strings.TrimRight(strings.TrimRight(resultStr, "0"), ".")
display.SetText(resultStr)
operator = ""
resetDisplay = true
}
// 清除按钮处理函数
clearPressed := func() {
display.SetText("0")
firstNumber = 0
operator = ""
resetDisplay = true
}
// 创建数字按钮 (0-9)
buttons := make([]*widget.Button, 10)
for i := 0; i <= 9; i++ {
num := strconv.Itoa(i)
buttons[i] = widget.NewButton(num, func(n string) func() {
return func() {
numberPressed(n)
}
}(num))
}
// 创建运算符按钮
addButton := widget.NewButton("+", func() { operatorPressed("+") })
subtractButton := widget.NewButton("-", func() { operatorPressed("-") })
multiplyButton := widget.NewButton("*", func() { operatorPressed("*") })
divideButton := widget.NewButton("/", func() { operatorPressed("/") })
equalsButton := widget.NewButton("=", equalsPressed)
clearButton := widget.NewButton("C", clearPressed)
// 创建小数点按钮
decimalButton := widget.NewButton(".", func() {
if !strings.Contains(display.Text, ".") {
display.SetText(display.Text + ".")
resetDisplay = false
}
})
// 布局计算器界面
buttonGrid := container.New(layout.NewGridLayout(4),
buttons[7], buttons[8], buttons[9], addButton,
buttons[4], buttons[5], buttons[6], subtractButton,
buttons[1], buttons[2], buttons[3], multiplyButton,
buttons[0], decimalButton, equalsButton, divideButton,
)
// 组合显示区域和按钮区域
content := container.NewVBox(
display,
clearButton,
buttonGrid,
)
myWindow.SetContent(content)
myWindow.Resize(fyne.NewSize(300, 250))
myWindow.ShowAndRun()
}
总结
本文档详细介绍了 Fyne GUI 库最新版的核心功能,包括基础组件、布局系统、事件处理、自定义组件和主题定制,并提供了简洁、可运行的代码示例。通过这些示例,有经验的 Go 开发者可以快速上手 Fyne,构建跨平台的 GUI 应用程序。
Fyne 的优势在于其简洁的 API 设计、跨平台能力和现代化的外观。它适合开发各种规模的应用程序,从简单的工具到复杂的企业级应用。
要深入了解 Fyne 的更多功能,请参考官方文档:https://docs.fyne.io/
参考资源
- Fyne 官方文档:https://docs.fyne.io/
- Fyne GitHub 仓库:https://github.com/fyne-io/fyne
- Fyne API 文档:https://pkg.go.dev/fyne.io/fyne/v2
- Fyne 扩展组件:https://addons.fyne.io/