本文档注意参考官网(developer.fyne.io/) 编写, 只保留基本用法

go代码展示为Go 1.16 及更高版本, idegoland2021.2

2.Fyne总览

2.1 Canvas(画布) and CanvasObject

在 Fyne 中Canvas是在其中绘制应用程序的区域。每个窗口都有一个您可以通过Window.Canvas()访问的Canvas,但通常您会在上面找到Window避免访问画布的功能。
可以在 Fyne中绘制的所有内容都是CanvasObject. 这里的示例打开一个新窗口,然后通过设置窗口画布的内容来显示不同类型的原始图形元素。如文本和圆形示例所示,可以通过多种方式自定义每种类型的对象。
除了更改显示Canvas.SetContent()的内容外,还可以更改当前可见的内容。例如,如果您更改FillColour矩形的 ,则可以使用 请求刷新此现有组件rect.Refresh()

package main

import (
    "image/color"
    "time"

    "fyne.io/fyne/v2"
    "fyne.io/fyne/v2/app"
    "fyne.io/fyne/v2/canvas"
)

func main() {
    myApp := app.New()
    myWindow := myApp.NewWindow("Canvas")
    myCanvas := myWindow.Canvas()

    blue := color.NRGBA{R: 0, G: 0, B: 180, A: 255}
    rect := canvas.NewRectangle(blue)
    myCanvas.SetContent(rect)

    go func() {
        time.Sleep(time.Second)
        green := color.NRGBA{R: 0, G: 180, B: 0, A: 255}
        rect.FillColor = green
        rect.Refresh()
    }()

    myWindow.Resize(fyne.NewSize(100, 100))
    myWindow.ShowAndRun()
}

我们可以用同样的方式绘制很多不同的绘图元素,比如圆形和文字。

func setContentToText(c fyne.Canvas) {
    green := color.NRGBA{R: 0, G: 180, B: 0, A: 255}
    text := canvas.NewText("Text", green)
    text.TextStyle.Bold = true
    c.SetContent(text)
}

func setContentToCircle(c fyne.Canvas) {
    red := color.NRGBA{R: 0xff, G: 0x33, B: 0x33, A: 0xff}
    circle := canvas.NewCircle(color.White)
    circle.StrokeWidth = 4
    circle.StrokeColor = red
    c.SetContent(circle)
}

小部件(Widget)

fyne.Widget是一种特殊类型的画布对象,它具有与之关联的交互元素。在小部件中,逻辑与其外观(也称为 WidgetRenderer)是分开的。
小部件也是CanvasObject一种类型,因此我们可以将窗口的内容设置为单个小部件。在这个例子中看看我们如何创建一个新的 widget.Entry并将其设置为窗口的内容。

package main

import (
    "fyne.io/fyne/v2/app"
    "fyne.io/fyne/v2/widget"
)

func main() {
    myApp := app.New()
    myWindow := myApp.NewWindow("Widget")

    myWindow.SetContent(widget.NewEntry())
    myWindow.ShowAndRun()
}

2.2 Container and Layouts(容器和布局)

在前面的示例中,我们看到了如何将 CanvasObject 设置为Canvas的内容,但只显示一个视觉元素并不是很有用。为了显示多个项目,我们使用Container类型。
由于fyne.Container也是 fyne.CanvasObject,我们可以将其设置为fyne.Canvas 的内容。在本例中,我们创建了 3 个文本对象,然后使用container.NewWithoutLayout()函数将它们放入容器中。由于没有布局集,我们可以用text2.Move()移动元素。

fyne.Layout实现了一种在容器内组织项目的方法。通过取消注释container.New()此示例中的行,您可以更改容器以使用具有 2 列的网格布局。运行此代码并尝试调整窗口大小以查看布局如何自动配置窗口的内容。另请注意,text2布局代码忽略了手动位置。
要查看更多信息,您可以查看Layout list.

2.3 小部件(Widget)列表

2.3.1 标准小部件(widget包)

手风琴(Accordion )

Accordion 显示 AccordionItems 列表。每个项目都由一个按钮表示,点击该按钮会显示详细视图。

按钮(Button)

按钮小部件具有文本标签和图标,两者都是可选的。

卡片(Card)

卡片小部件使用标题和子标题对元素进行分组,所有这些都是可选的。

复选框(Check )

检查小部件有一个文本标签和一个选中(或未选中)的图标。

输入框(Entry)

输入框小部件允许在获得焦点时输入简单的文本。

PasswordEntry 小部件隐藏文本输入并添加一个按钮来显示文本。

文件图标(FileIcon)

FileIcon 为各种类型的文件提供有用的标准图标。它将文件类型显示为指示器图标并显示文件类型的扩展名。

表单(Form)

表单小部件是两列网格,其中每一行都有一个标签和一个小部件(通常是一个输入)。最后一行将包含适当的表单控制按钮。

超链接(Hyperlink)

超链接小部件是具有适当填充和布局的文本组件。单击后,URL 将在您的默认 Web 浏览器中打开。

图标(Icon)

图标小部件是一个基本的图像组件,它加载其资源以匹配主题。

标签(Label )

标签小部件是具有适当填充和布局的标签组件。

进度条(ProgressBar)

ProgressBar 小部件创建一个指示进度的水平面板。

ProgressBarInfinite 小部件创建一个水平面板,指示无限进度条 0% -> 100% 重复循环,直到调用 Stop()。

单选框组(RadioGroup)

RadioGroup 小部件有一个文本标签列表和每个旁边的单选检查图标。

下拉选择框(Select)

选择小部件有一个选项列表,显示当前选项,并在单击时触发事件函数。

下拉选择框可输入选择(SelectEntry)

小部件将可编辑组件添加到下拉选择框小部件。用户可以选择一个选项或输入自己的值。

分割线(Separator)

分隔小部件显示其他元素之间的分界线。

滑块(Slider)

Slider小部件可以在两个固定值之间滑动切换。

文本网格(TextGrid )

TextGrid 是一个等宽的字符网格。这旨在供文本编辑器、代码预览或终端仿真器使用。

工具栏(Toolbar)

工具栏小部件创建工具按钮的水平列表。

2.3.2 集成小部件(widget包)

集成小部件提供高级缓存功能,以提供海量数据的高性能呈现。这确实导致了一个更复杂的构造函数,但是对于它实现的结果来说是一个很好的平衡。这些小部件中的每一个都使用一系列回调,最小集合由它们的构造函数定义,其中包括数据大小、可重复使用的模板的创建、数据应用于小部件的函数。

列表(List)

List 提供了许多子项的高性能垂直滚动。

表格(Table)

表格提供了许多子项的高性能滚动二维显示。

树(Tree)

树提供了一个高性能的垂直滚动项目,可以展开以显示子元素。

2.3.3 容器小部件(container包)

容器小部件类似于常规容器,但它们提供了一些额外的功能。

应用选项卡(AppTabs)

AppTabs 小部件允许从 TabItems 列表切换可见内容。每个项目都由小部件顶部的按钮表示。

滚动容器 (ScrollContainer)

ScrollContainer 定义了一个小于 Content 的容器。

分裂容器(SplitContainer)

SplitContainer 定义了一个容器,其大小在两个子项之间拆分。

2.4 布局(layout)列表

水平框(HBox)

水平框将项目排列在水平行中。每个元素都将具有相同的高度(容器中最高项目的高度),并且对象将在其最小宽度处左对齐。

垂直框 (VBox)

垂直框将项目排列在垂直列中。每个元素将具有相同的宽度(容器中最宽项的宽度),并且对象将在其最小高度处顶部对齐。

居中(Center )

居中布局将所有容器元素放置在容器的中心。每个对象都将设置为其最小尺寸。

表单布局(Form)

表单布局将项目成对排列,其中第一列的宽度最小。这对于在表单中标记元素通常很有用,其中标签位于第一列,而它描述的项目位于第二列。您应该始终向表单布局添加偶数个元素。

网格(Grid)

网格布局在可用空间中平均排列项目。指定了列数,对象水平放置,直到达到列数,此时新行开始。所有对象具有相同的大小,即宽度除以总列,高度将是总高度除以所需的行数。减去填充。

网格环绕(GridWrap)

GridWrap 布局将所有项目排列成一行,如果空间不足,则换行到新行。所有对象都将设置为相同的大小,即传递给布局的大小。此布局可能不尊重项目 MinSize 来管理此统一布局。通常用于文件管理器或图像缩略图列表。

边框(Border)

边框布局支持将项目定位在可用空间之外。边框被传递给(上、左、下、右)对象的指针。容器中未定位在边框上的所有项目将填充剩余空间。

最大(max)

最大布局定位所有容器元素以填充可用空间。这些对象都将是全尺寸的,并按照它们添加到容器中的顺序绘制(最后一个在顶部)。

填充(Padded)

填充布局定位所有容器元素以填充可用空间,但在外部有一个小填充。填充的大小是特定于主题的。所有对象都将按照它们添加到容器中的顺序绘制(最后一个在顶部)。

组合布局

可以嵌套多个容器,每个容器都有自己的布局,以仅使用上面列出的标准布局创建完整的用户界面排列。例如标题的水平框,左侧文件面板的垂直框和内容区域中的网格环绕布局 - 使用边框布局的所有容器内都可以构建如下所示的结果。

2.5 对话框(Dialog)列表

颜色

允许用户从标准集(或高级模式下的任何颜色)中选择一种颜色。

确认

询问动作的构象

文件打开

展示此内容以要求用户选择要在应用内使用的文件。实际显示的对话框取决于当前的操作系统。

表单

在对话框中获取各种输入元素,并进行验证。

信息

一种向应用程序用户呈现一些信息的简单方法。

通用

在对话框容器中呈现任何内容。

2.6 快捷方式

快捷方式是可以通过键盘组合或上下文菜单触发的常见任务。快捷方式,很像键盘事件,可以附加到焦点元素或注册Window.Canvas
画布注册
定义了许多标准快捷键(例如fyne.ShortcutCopy),它们连接到标准键盘快捷键和右键菜单。第一步添加Shortcut是定义快捷方式。对于大多数用途,这将是一个键盘触发的快捷方式,它是一个桌面扩展。为此,我们使用desktop.CustomShortcut,例如使用 Tab 键和Control 修饰符,您可以执行以下操作:

    ctrlTab := desktop.CustomShortcut{KeyName: fyne.KeyTab, Modifier: desktop.ControlModifier}

请注意,此快捷方式可以重复使用,因此您也可以将其附加到菜单或其他项目。对于此示例,我们希望它始终可用,因此我们将其注册到我们的窗口Canvas中,如下所示:

    ctrlTab := desktop.CustomShortcut{KeyName: fyne.KeyTab, Modifier: desktop.ControlModifier}
    w.Canvas().AddShortcut(&ctrlTab, func(shortcut fyne.Shortcut) {
        log.Println("We tapped Ctrl+Tab")
    })

如您所见,以这种方式注册快捷方式有两个部分 - 传递快捷方式定义和回调函数。如果用户键入键盘快捷键,则将调用该函数并打印输出。
输入框(Entry)添加快捷方式
仅在当前项目获得焦点时应用快捷方式也很有帮助。这种方法可用于任何可聚焦的小部件,并通过扩展该小部件并添加TypedShortcut处理程序来管理。这很像添加键处理程序,除了传入的值将是fyne.Shortcut.

type myEntry struct {
    widget.Entry
}

func (m *myEntry) TypedShortcut(s fyne.Shortcut) {
    if _, ok := s.(*desktop.CustomShortcut); !ok {
        m.Entry.TypedShortcut(s)
        return
    }

    log.Println("Shortcut typed:", s)
}

您可以看到如何实现TypedShortcut处理程序。在此函数中,您应该检查快捷方式是否属于之前使用的自定义类型。如果快捷方式是标准快捷方式,最好调用原始快捷方式处理程序(如果小部件有一个)。完成这些检查后,您可以将快捷方式与您正在处理的各种类型(如果有多个)进行比较。

2.7 使用[Preferences] API

存储用户配置和值是应用程序开发人员的一项常见任务,但跨多个平台实现它可能是乏味且耗时的。为了使它更容易,Fyne 有一个 API 用于在文件系统上以一种清晰易懂的方式存储值,同时为您处理复杂的部分。
让我们从 API 的设置开始。它是Preferences接口的一部分,其中存在 Bool、Float、Int 和 String 值的存储和加载函数。它们每个都包含三个不同的功能,一个用于加载,一个用于加载备用值,最后一个用于存储值。下面是 String 类型的三个函数及其行为的示例:

// String looks up a string value for the key
String(key string) string
// StringWithFallback looks up a string value and returns the given fallback if not found
StringWithFallback(key, fallback string) string
// SetString saves a string value for the given key
SetString(key string, value string)

这些函数可以通过创建的应用程序变量和调用Preferences()方法来访问。请注意,必须创建具有唯一 ID 的应用程序(通常像反向 url)。这意味着需要使用创建应用程序app.NewWithID()来拥有自己的位置来存储值。它大致可以像下面的例子一样使用:

a := app.NewWithID("com.example.tutorial.preferences")
[...]
a.Preferences().SetBool("Boolean", true)
number := a.Preferences().IntWithFallback("ApplicationLuckyNumber", 21)
expression := a.Preferences().String("RegularExpression")
[...]

为了展示这一点,我们将构建一个简单的小应用程序,它总是在设定的时间后关闭。此超时应该是用户可更改的,并在应用程序的下一次启动时应用。
让我们首先创建一个名为的变量,该变量timeout将用于以 的形式存储时间time.Duration

var timeout time.Duration

然后我们可以创建一个选择小部件,让用户从几个预定义的字符串中选择超时,然后将超时乘以字符串相关的秒数。最后,该"AppTimeout"键用于将字符串值设置为选定的值。

timeoutSelector := widget.NewSelect([]string{"10 seconds", "30 seconds", "1 minute"}, func(selected string) {
    switch selected {
    case "10 seconds":
        timeout = 10 * time.Second
    case "30 seconds":
        timeout = 30 * time.Second
    case "1 minute":
        timeout = time.Minute
    }

    a.Preferences().SetString("AppTimeout", selected)
})

现在我们想要获取设置的值,如果不存在,我们想要一个后备,将超时设置为尽可能短的时间,以节省用户等待时的时间。这可以通过将 的选定值设置timeoutSelector为加载的值或在这种情况下的回退来完成。通过这样做,选择小部件内的代码将为该特定值运行。

timeoutSelector.SetSelected(a.Preferences().StringWithFallback("AppTimeout", "10 seconds"))

最后一部分将只是拥有一个在单独的 goroutine 中启动的函数,并告诉应用程序在选定的超时后退出。

go func() {
    time.Sleep(timeout)
    a.Quit()
}()

最后,生成的代码应如下所示:

package main

import (
    "time"

    "fyne.io/fyne/v2/app"
    "fyne.io/fyne/v2/widget"
)

func main() {
    a := app.NewWithID("com.example.tutorial.preferences")
    w := a.NewWindow("Timeout")

    var timeout time.Duration

    timeoutSelector := widget.NewSelect([]string{"10 seconds", "30 seconds", "1 minute"}, func(selected string) {
        switch selected {
        case "10 seconds":
            timeout = 10 * time.Second
        case "30 seconds":
            timeout = 30 * time.Second
        case "1 minute":
            timeout = time.Minute
        }

        a.Preferences().SetString("AppTimeout", selected)
    })

    timeoutSelector.SetSelected(a.Preferences().StringWithFallback("AppTimeout", "10 seconds"))

    go func() {
        time.Sleep(timeout)
        a.Quit()
    }()

    w.SetContent(timeoutSelector)
    w.ShowAndRun()
}

2.8 数据绑定

Fyne v2.0.0中引入了数据绑定,使将许多小部件连接到将随时间更新的数据源变得更加容易。该data/binding包有许多有用的绑定,可以管理将在应用程序中使用的大多数标准类型。可以使用绑定 API(例如NewString)来管理数据绑定,也可以将其连接到外部数据项,例如 (BindInt(*int))。
支持绑定的小部件通常有一个…WithData构造函数来在创建小部件时设置绑定。您还可以调用Bind()Unbind()管理现有小部件的数据。以下示例显示了如何管理String绑定到简单Label小部件的数据项。

package main

import (
    "time"

    "fyne.io/fyne/v2/app"
    "fyne.io/fyne/v2/data/binding"
    "fyne.io/fyne/v2/widget"
)

func main() {
    a := app.New()
    w := a.NewWindow("Hello")

    str := binding.NewString()
    go func() {
        dots := "....."
        for i := 5; i >= 0; i-- {
            str.Set("Count down" + dots[:i])
            time.Sleep(time.Second)
        }
        str.Set("Blast off!")
    }()

    w.SetContent(widget.NewLabelWithData(str))
    w.ShowAndRun()
}

2.9 编译选项

Fyne 通常会通过选择驱动程序和配置来为目标平台适当地配置您的应用程序。支持以下构建标签,它们可以帮助您的开发。例如,如果您希望在台式计算机上运行时模拟移动应用程序,您可以使用以下命令:

go run -tags mobile main.go
标签 描述
gles 强制使用嵌入式 OpenGL (GLES) 而不是完整的 OpenGL。这通常由目标设备控制,通常不需要。
hints 显示改进或优化的开发人员提示。当您的应用程序不遵循材料设计或其他建议时,运行hints将记录下来。
mobile 此标签在模拟的移动窗口中运行应用程序。当您想在移动平台上预览您的应用程序而不编译和安装到设备时很有用。
no_native_menus 此标志专门用于 macOS,表示应用程序不应使用 macOS 原生菜单。相反,菜单将显示在应用程序窗口内。对于在 macOS 上测试应用程序以模拟 Windows 或 Linux 上的行为最有用。