Golang跨平臺(tái)GUI框架Fyne的使用教程詳解
Go 官方?jīng)]有提供標(biāo)準(zhǔn)的 GUI 框架,在 Go 實(shí)現(xiàn)的幾個(gè) GUI 庫(kù)中,F(xiàn)yne 算是最出色的,它有著簡(jiǎn)潔的API、支持跨平臺(tái)能力,且高度可擴(kuò)展。也就是說(shuō),F(xiàn)yne 是可以用來(lái)開(kāi)發(fā) App 的。
本文將嘗試介紹下 Fyne,希望對(duì)大家快速上手這個(gè) GUI 框架有所幫助。我最近產(chǎn)生了不少想法,其中有些是對(duì) GUI 有要求的,就想著折騰用 Go 實(shí)現(xiàn),而不是用那些已經(jīng)很流行和成熟的 GUI 框架。
在寫這篇文章時(shí),順手搞了下它的中文版文檔,文檔查看 www.poloxue.com/gofyne,希望對(duì)想繼續(xù)深入這個(gè)框架的朋友有所幫助。

安裝 fyne
開(kāi)始前,確保已成功安裝 Go,如果是 MacOS X 系統(tǒng),要確認(rèn)安裝了 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 提供的演示案例,通過(guò)命令檢查:
$ go run fyne.io/fyne/v2/cmd/fyne_demo@latest

看起來(lái),這里面的案例還是不夠豐富的。
安裝工作到此就完成了。Fyne 對(duì)不同系統(tǒng)有不同依賴,如果安裝過(guò)程中遇到問(wèn)題,細(xì)節(jié)可查看官方提供的安裝文檔。
創(chuàng)建第一個(gè)應(yīng)用
由于 Go 簡(jiǎn)潔的語(yǔ)法和 fyne 的設(shè)計(jì),使用 fyne 創(chuàng)建一個(gè) GUI 應(yīng)用異常簡(jiǎn)單。
以下是一個(gè)創(chuàng)建基礎(chǔ)窗口的例子:
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()
}這段代碼創(chuàng)建了一個(gè)包含標(biāo)簽的窗口。
通過(guò) app.New() 初始化一個(gè) Fyne 應(yīng)用實(shí)例。然后,創(chuàng)建一個(gè)標(biāo)題為 "Hello Fyne" 的窗口,并設(shè)置內(nèi)容為包含 "Welcome to Fyne!" 文本標(biāo)簽。最后,通過(guò)w.ShowAndRun()顯示窗口并啟動(dòng)應(yīng)用的事件循環(huán)。
fyne 中窗口的默認(rèn)大小是其包含的內(nèi)容決定,也可預(yù)置大小。
如在創(chuàng)建窗口后,提供 Resize 重新調(diào)整大小。
w.Resize(fyne.NewSize(100, 100))
還有,一個(gè) app 下可以有多個(gè)窗口的,示例代碼:
btn := widget.NewButton("Create a widnow", func() {
w2 := a.NewWindow("Window 02")
w2.Resize(fyne.NewSize(200, 200))
w2.Show()
})
w.SetContent(btn)我們創(chuàng)建一個(gè)按鈕,它的點(diǎn)擊事件是創(chuàng)建一個(gè)新的窗口并顯示。

布局和控件
布局和控件是 GUI 應(yīng)用程序設(shè)計(jì)中必不可少的兩類組件。Fyne 提供了多種布局管理器和標(biāo)準(zhǔn)的 UI 控件,支持創(chuàng)建更復(fù)雜的界面。
布局管理
Fyne 中的布局的實(shí)現(xiàn)位于 container 包中。它提供了多種不同布局方式安排窗口中的元素。最基本的布局方式有 HBox 水平布局和 VBox 垂直布局。
通過(guò) HBox 創(chuàng)建水平布局的代碼如下:
w.SetContent(container.NewHBox(widget.NewButton("Left", func() {
fmt.Println("Left button clicked")
}), widget.NewButton("Right", func() {
fmt.Println("Right button clicked")
})))顯示效果:

通過(guò) VBox 創(chuàng)建垂直布局的例子。
w.SetContent(container.NewVBox(widget.NewButton("Top", func() {
fmt.Println("Top button clicked")
}), widget.NewButton("Bottom", func() {
fmt.Println("Bottom button clicked")
})))顯示效果:

Fyne 除了基礎(chǔ)的水平(HBoxLayout)和垂直(VBoxLayout)布局,還提供了GridLayout、FormLayout 甚至是混合布局 CombinedLayout 等高級(jí)布局方式。
如 GridLayout可以將元素均勻地分布在網(wǎng)格中,而FormLayout適用于創(chuàng)建表單,自動(dòng)排列標(biāo)簽和字段。這些靈活的布局選項(xiàng)支持創(chuàng)建更復(fù)雜和功能豐富的GUI界面。
官方文檔中的布局列表查看:Layout List。
更多控件
前面的演示案例中,用到了兩個(gè)控件:Label 和 Button,F(xiàn)yne 還支持其他多種控件,它們都為于 widget 包中。
我嘗試在一份代碼中展示出來(lái),如下是常見(jiàn)控件一覽:
// 標(biāo)簽 Label
label := widget.NewLabel("Label")
// 按鈕 Button
button := widget.NewButton("Button", func() {})
// 輸入框 Entry
entry := widget.NewEntry()
entry.SetPlaceHolder("Entry")
// 復(fù)選框 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"}
// 進(jìn)度條
progressBar := widget.NewProgressBar()
// 滑塊
slider := widget.NewSlider(0, 100)
// 組合框
combo := widget.NewSelect([]string{"Option A", "Option B", "Option C"}, func(string) {})
// 表單項(xiàng)
formItem := widget.NewFormItem("FormItem", widget.NewEntry())
form := widget.NewForm(formItem)
// 手風(fēng)琴
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")),
)
// 彈出對(duì)話框示例按鈕
dialogButton := widget.NewButton("Show Dialog", func() {
dialog.ShowInformation("Dialog", "Dialog Content", w)
})
// 滾動(dòng)布局
content := container.NewVScroll(container.NewVBox(
label, button, entry, check, radio, selectEntry, progressBar, slider,
combo, form, accordion, tabs, dialogButton,
))
w.SetContent(content)演示效果:

Fyne 中的自定義
如果在實(shí)際項(xiàng)目中使用 Fyne,基本上是要使用 Fyne 的自定義能力。Fyne 提供了自定義控件、布局和主題等。
自定義控件
fyne 是支持實(shí)現(xiàn)自定義控件的,這涉及定義控件的繪制方法和布局邏輯。我們主要是實(shí)現(xiàn)兩個(gè)接口: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()
}這樣拆分的目標(biāo)是為將了控件的邏輯和 UI 繪制分離開(kāi)來(lái),在 Widget 中專注于邏輯,而 WidgetRenderer 中專注于渲染布局。
假設(shè)實(shí)現(xiàn)一個(gè)類似 Label 的控件,類型定義:
type CustomLabel struct {
widget.BaseWidget
Text string
}它繼承了 wiget.BaseWidget 基本控件實(shí)現(xiàn),Text 就是要 Label 顯示的文本。還要給給 CustomLabel 實(shí)現(xiàn) CreateRenderer 方法。
定義 CustomLabel 創(chuàng)建函數(shù):
func NewCustomLabel(text string) *CustomLabel {
label := &CustomLabel{Text: text}
label.ExtendBaseWidget(label)
return label
}customWidgetRenderer 類型定義如下:
type customWidgetRenderer struct {
text *canvas.Text // 使用canvas.Text來(lái)繪制文本
label *CustomLabel
}實(shí)現(xiàn) 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,
}
}構(gòu)建 Renderer 變量,使用 canvas 創(chuàng)建 Text 文本框,為適配主題使用主題配置前景色。還有,設(shè)置文本居中顯示。
而 customLabelRenderer 要實(shí)現(xiàn) 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 函數(shù)中,嘗試使用這個(gè)控件。
a := app.New()
w := a.NewWindow("Custom Label")
w.SetContent(NewCustomLabel("Hello"))
w.ShowAndRun()顯示的效果和 Label 控件是類似的。
其他自定義
其他自定義能力,如 Layout、Theme,我就不展開(kāi)介紹。如果有機(jī)會(huì),寫點(diǎn)實(shí)際應(yīng)用案例。如果基于案例介紹,會(huì)更有體悟吧。
還有,F(xiàn)yne 的官方文檔寫的挺易讀的,可直接看它的文檔。
數(shù)據(jù)綁定
Fyne 從 v2.0.0 開(kāi)始支持?jǐn)?shù)據(jù)綁定。它讓控件和與數(shù)據(jù)實(shí)時(shí)連接,數(shù)據(jù)更改會(huì)自動(dòng)反映在UI上,反之亦然。
控件為了支持?jǐn)?shù)據(jù)綁定能力,一般會(huì)提供如 NewXXXWithData 的接口。
直接通過(guò)一個(gè)場(chǎng)景說(shuō)明,目標(biāo)是編輯輸入框的內(nèi)容,可同時(shí)立刻顯示到 Label 上。
核心代碼如下所示:
// 創(chuàng)建一個(gè)字符串綁定 textBind := binding.NewString() // 創(chuàng)建一個(gè) Entry,將其內(nèi)容綁定到 textBind entry := widget.NewEntryWithData(textBind) // 創(chuàng)建一個(gè) Label,也將其內(nèi)容綁定到同一個(gè) textBind label := widget.NewLabelWithData(textBind) // 使用容器放置 Entry 和 Label,以便它們都顯示在窗口中 content := container.NewVBox(entry, label)
如上的代碼,創(chuàng)建一個(gè)數(shù)據(jù)綁定類型的變量 textBind,并將其通過(guò) NewWithData 將其綁定到兩個(gè)控件上。
顯示效果,如下所示:

嘗試讓前面自定義的 CustomLabel 支持?jǐn)?shù)據(jù)綁定能力。只要?jiǎng)?chuàng)建一個(gè) NewCustomLabelWithData 構(gòu)造函數(shù)。
如下所示:
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
}如上的代碼中,其實(shí)就是通過(guò) data 這個(gè)數(shù)據(jù)綁定類型變量監(jiān)聽(tīng)數(shù)據(jù)變化,監(jiān)聽(tīng)到變化后,更新空間內(nèi)容并立刻刷新控件顯示。

如上所示,我們這個(gè)自定義 Label 中的文本是居中顯示的。
數(shù)據(jù)綁定的核心是監(jiān)聽(tīng)器模式(Observer pattern)。每個(gè)綁定對(duì)象內(nèi)部維護(hù)了一個(gè)監(jiān)聽(tīng)器列表,當(dāng)數(shù)據(jù)變化時(shí),這些監(jiān)聽(tīng)器會(huì)被通知更新。
在 Fyne 中,通過(guò) data.AddListner() 將 UI 組件與數(shù)據(jù)綁定對(duì)象綁定時(shí),實(shí)際上是在數(shù)據(jù)對(duì)象上注冊(cè)了一個(gè)監(jiān)聽(tīng)器,這個(gè)監(jiān)聽(tīng)器會(huì)在數(shù)據(jù)變化時(shí)更新 UI 組件的狀態(tài)。
結(jié)語(yǔ)
Fyne 是簡(jiǎn)單、強(qiáng)大和跨平臺(tái)的 GUI 工具,使得用 GO 開(kāi)發(fā)現(xiàn)代 GUI 應(yīng)用多了一個(gè)優(yōu)秀選擇。隨著對(duì) Fyne 的深入,它能夠更加靈活地構(gòu)建出符合需求的應(yīng)用。
我喜歡用 Go 的原因,很重要的原因就是它的簡(jiǎn)潔性,很容易看到本質(zhì)的東西,但又無(wú)需理解太復(fù)雜的編程概念。
以上就是Golang跨平臺(tái)GUI框架Fyne的使用教程詳解的詳細(xì)內(nèi)容,更多關(guān)于Go Fyne的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Go語(yǔ)言實(shí)戰(zhàn)之實(shí)現(xiàn)均衡器功能
這篇文章主要為大家詳細(xì)介紹了如何利用Golang?實(shí)現(xiàn)一個(gè)簡(jiǎn)單的流浪均衡器,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-04-04
go實(shí)現(xiàn)服務(wù)優(yōu)雅關(guān)閉的示例
本文主要介紹了go實(shí)現(xiàn)服務(wù)優(yōu)雅關(guān)閉的示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-02-02
go mod 安裝依賴 unkown revision問(wèn)題的解決方案
這篇文章主要介紹了go mod 安裝依賴 unkown revision問(wèn)題的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-05-05
GoLang抽獎(jiǎng)系統(tǒng)簡(jiǎn)易實(shí)現(xiàn)流程
這篇文章主要介紹了GoLang抽獎(jiǎng)系統(tǒng)實(shí)現(xiàn)流程,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)吧2022-12-12
Go Time庫(kù)中時(shí)間和日期相關(guān)的操作方法整理
這篇文章主要為大家整理了Go語(yǔ)言中的time庫(kù),包括時(shí)間、日期和時(shí)區(qū)等相關(guān)概念及使用方法,希望通過(guò)掌握這些知識(shí),大家可以更好地處理時(shí)間、日期和時(shí)區(qū)相關(guān)的問(wèn)題2023-08-08
golang開(kāi)發(fā)安裝go-torch火焰圖操作步驟
這篇文章主要為大家介紹了golang開(kāi)發(fā)安裝go-torch火焰圖操作步驟2021-11-11

