go fyne开发桌面应用

go fyne开发桌面应用

fyne介绍

组织和包

Fyne项目分为许多包,每个包提供不同类型的功能。它们如下所示:

fyne.io/fyne/v2此导入提供了所有 Fyne 代码通用的基本定义包括数据类型和接口。

fyne.io/fyne/v2/app应用包提供用于启动新应用程序的 API。通常,您只需要app.New()app.NewWithID()

fyne.io/fyne/v2/canvas画布包提供了 Fyne 中的所有绘图 API。完整的 Fyne 工具包由这些原始图形类型组成。fyne.io/fyne/v2/container容器包提供用于布局和组织应用程序的容器。

fyne.io/fyne/v2/data/binding绑定包包含将数据源绑定到小组件的方法。

fyne.io/fyne/v2/data/validation验证包提供用于验证小组件内部数据的收费。

fyne.io/fyne/v2/dialog对话包包含确认、错误和文件保存/打开等对话框。

fyne.io/fyne/v2/layout布局包提供了各种布局实现以供使用使用容器(在后面的教程中讨论)。

fyne.io/fyne/v2/storage存储包提供存储访问和管理功能。

fyne.io/fyne/v2/test使用测试中的工具可以更轻松地测试应用程序包。

fyne.io/fyne/v2/widget大多数图形应用程序都是使用小部件集合创建的。Fyne中的所有小部件和交互式元素都在此包中。

示例代码:

package main

import (
    "fmt"
    "fyne.io/fyne/v2/app"
    "fyne.io/fyne/v2/widget"
)
// Layout 界面ui设计
func Layout() *widget.Button {
    return widget.NewButton(
        "button1", func() {
            fmt.Println("button is clicked")
        })
}

func main() {
    // 创建程序
    thisApp := app.New()
    // 创建窗口对象、传入窗口名
    w := thisApp.NewWindow("Title")
    // 设置窗口内容
    w.SetContent(Layout())
    // 窗口的显示和运行
    w.ShowAndRun()
}

封包

打包多个操作系统可能是一项复杂的任务。图形应用程序通常具有与之关联的图标和元数据,以及与每个环境集成所需的特定格式。

该命令支持准备要在工具包支持的所有平台上分发的应用程序。运行"fyne package"将创建一个应用程序,只需从当前目录中复制创建的文件,即可将其安装在您的计算机上并分发到其他计算机。

对于Windows,它将创建一个嵌入了图标的文件。对于macOS计算机,它将创建一个捆绑包,对于Linux,它将生成一个可以以通常方式(或在提取的文件夹中运行)安装的文件。.exe .app .tar.xz make install

当然,如果您愿意,您仍然可以使用标准的Go工具运行应用程序。

#!/bin/sh

go get fyne.io/fyne/v2/cmd/fyne

go build
fyne package -icon mylogo.png

# result is a platform specific package
# for the current operating system.

Demo文件

go run fyne.io/fyne/v2/cmd/fyne_demo

fyne的基本操作

应用程序和运行循环

要使 GUI 应用程序正常工作,它需要运行一个事件循环(有时称为 runloop),main()用于处理用户交互和绘制事件。在 Fyne 中,这是使用App.Run()Window.ShowAndRun()函数开始的。其中一个必须从函数中的设置代码末尾调用。

一个应用程序应该只有一个 runloop,因此在代码中Run()只应调用一次。再次调用它将导致错误。

对于桌面运行时,可以通过调用直接退出应用(移动应用不支持此功能) - 开发人员代码中通常不需要。关闭所有窗口后,应用程序也将退出。另请参阅在应用程序退出之前不会调用之后执行的函数。App.Quit() Run()

package main

import (
    "fmt"

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

func main() {
    myApp := app.New()
    myWindow := myApp.NewWindow("Hello")
    myWindow.SetContent(widget.NewLabel("Hello"))

    myWindow.Show()
    myApp.Run()
    tidyUp()
}

func tidyUp() {
    fmt.Println("Exited")

窗口处理

窗口fyne.Window是使用App.NewWindow()创建的,需要使用Show()该函数显示。ShowAndRun()上的帮助程序方法允许您显示窗口并同时运行应用程序。

如果要显示第二个窗口,则只能调用showAnother该函数。Show()函数中对此进行了说明。

默认情况下,窗口的大小将适合通过检查Window.Resize()函数来显示其内容(在后面的示例中将对此进行详细介绍)。可以通过调用MinSize()函数来设置更大的大小。

请注意,桌面环境可能具有导致窗口小于请求的限制。

package main

import (
    "time"

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

func main() {
    myApp := app.New()
    myWindow := myApp.NewWindow("Hello")
    myWindow.SetContent(widget.NewLabel("Hello"))

    go showAnother(myApp)
    myWindow.ShowAndRun()
}

func showAnother(a fyne.App) {
    time.Sleep(time.Second * 5)

    win := a.NewWindow("Shown later")
    win.SetContent(widget.NewLabel("5 seconds later"))
    win.Resize(fyne.NewSize(200, 200))
    win.Show()

    time.Sleep(time.Second * 2)
    win.Close()
}

Canvas 和 CanvasObject

在 Fyne 中,a 是在其中绘制应用程序的区域。每个窗口Window都有一个画布,您可以使用Window.Canvas()它来访问,但通常你会通过访问画布Canvas来寻找函数声明。

在Fyne中可以绘制的所有内容都是一种.此处的示例打开一个新窗口,然后通过设置窗口画布的内容来显示不同类型的基元图形元素。可以通过多种方式自定义每种类型的对象,如文本和圆圈示例所示。通过在 goroutine(使用前缀)中运行,这些图形更改将在窗口显示后执行。CanvasObject changeContent go

除了使用更改显示的内容外,还可以更改当前可见的内容。例如,如果更改矩形的 ,则可以使用 请求刷新此现有组件。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"
    "fyne.io/fyne/v2/theme"
)

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

    green := color.NRGBA{R: 0, G: 180, B: 0, A: 255}
    text := canvas.NewText("Text", green)
    text.TextStyle.Bold = true
    myCanvas.SetContent(text)
    go changeContent(myCanvas)

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

func changeContent(c fyne.Canvas) {
    time.Sleep(time.Second * 2)

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

    time.Sleep(time.Second * 2)
    c.SetContent(canvas.NewLine(color.Gray{Y: 180}))

    time.Sleep(time.Second * 2)
    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)

    time.Sleep(time.Second * 2)
    c.SetContent(canvas.NewImageFromResource(theme.FyneLogo()))
}

容器和布局

在前面的示例中,我们看到了如何将 a 设置为 a 的内容,但是只显示一个可视元素并不是很有用。为了显示多个项目,我们使用类型。CanvasObject Canvas Container

由于 也是 a ,我们可以将其设置为 的内容。在此示例中,我们创建 3 个文本对象,然后使用该函数将它们放置在容器中。由于没有布局集,我们可以像您看到的那样移动元素。fyne.Container fyne.CanvasObject fyne.Canvas container.NewWithoutLayout() text2.Move()

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

要了解更多信息,请跳转到本教程的布局容器部分。

package main

import (
    "image/color"

    "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("Container")
    green := color.NRGBA{R: 0, G: 180, B: 0, A: 255}

    text1 := canvas.NewText("Hello", green)
    text2 := canvas.NewText("There", green)
    text2.Move(fyne.NewPos(20, 20))
    content := container.NewWithoutLayout(text1, text2)
    // content := container.New(layout.NewGridLayout(2), text1, text2)

    myWindow.SetContent(content)
    myWindow.ShowAndRun()
}

部件

A 是一种特殊类型的容器,具有与之关联的其他逻辑。在 widget 中,逻辑与它的外观(也称为 ) 是分开的。fyne.Widget WidgetRenderer

小部件也是一种类型,因此我们可以将窗口的内容设置为单个小部件。在此示例中,请参阅我们如何创建新窗口并将其设置为窗口的内容。CanvasObject widget.Entry

正如我们在前面的示例中看到的那样,您还可以使用 a 将多个对象添加到画布中,并且可以使用一组小部件开始构建图形应用程序界面。Container

接下来花一些时间了解其他"画布对象"。

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()
}

画布

矩形

canvas.Rectangle是 Fyne 中最简单的画布对象。它显示指定颜色的块。您还可以使用字段设置颜色。FillColor

在此示例中,矩形填充窗口,因为它是唯一的内容元素。

其他类型具有更多配置,让我们看一下。fyne.CanvaObject canvas.Text

package main

import (
    "image/color"

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

func main() {
    myApp := app.New()
    w := myApp.NewWindow("Rectangle")

    rect := canvas.NewRectangle(color.White)
    w.SetContent(rect)

    w.Resize(fyne.NewSize(150, 100))
    w.ShowAndRun()
}

文本框

canvas.Text用于 Fyne 中的所有文本呈现。它是通过指定文本和文本的颜色来创建的。文本使用由当前主题指定的默认字体呈现。

文本对象允许某些配置,如 and 字段。如此处的示例所示。要改用等宽字体,可以指定 。Alignment TextStyle fyne.TextStyle{Monospace: true}

可以通过指定环境变量来使用替代字体。使用此选项可以设置要使用的文件,而不是 Fyne 工具包或当前主题中提供的文件。FYNE_FONT .ttf

package main

import (
    "image/color"

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

func main() {
    myApp := app.New()
    w := myApp.NewWindow("Text")

    text := canvas.NewText("Text Object", color.White)
    text.Alignment = fyne.TextAlignTrailing
    text.TextStyle = fyne.TextStyle{Italic: true}
    w.SetContent(text)

    w.ShowAndRun()
}

绘画直线

对象绘制一条从(默认值为顶部,左侧)到(默认值为底部,右侧)的线条。您可以指定其颜色,并且可以改变描边宽度,否则默认为 。canvas.Line Position1 Position2 1

可以使用 或 字段或使用 and 函数来操作线位置。例如,宽度为 0 的区域将显示一条垂直线,而 0 高度将显示为水平线。Position1 Position2 Move() Resize()

线条通常用于自定义布局或手动控制。与文本不同,它们没有自然(最小)大小,但可以在复杂的布局中产生巨大的效果。

绘制圆圈

canvas.Circle定义由指定颜色填充的圆形。您也可以设置一个,因此设置一个不同的,如本示例所示。StrokeWidth StrokeColor

圆圈将填充通过调用或由其控制的布局指定的空间。作为示例,将圆圈设置为窗口内容,它将在基本填充(由主题控制)内调整大小以填充窗口。Resize()

所有这些都是基本类型,可以由我们的驱动程序呈现,而无需其他信息。接下来,我们将从图像开始看更复杂的类型。

package main

import (
    "image/color"

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

func main() {
    myApp := app.New()
    w := myApp.NewWindow("Circle")

    circle := canvas.NewCircle(color.White)
    circle.StrokeColor = color.Gray{0x99}
    circle.StrokeWidth = 5
    w.SetContent(circle)

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

图像

A 表示 Fyne 中的可缩放图像资源。可以从资源(如示例中所示)、图像文件、包含图像的 URI 位置、内存中的 Go 加载它。canvas.Image io.Reader image.Image

默认图像填充模式将导致它填充指定的空间(通过或布局)。或者,您可以使用来确保保持纵横比并且图像在边界内。此外,您可以使用(如此处的示例中所用)确保其最小大小也等于原始图像大小的最小大小。canvas.ImageFillStretch Resize() canvas.ImageFillContain canvas.ImageFillOriginal

图像可以是基于位图的(如 PNG 和 JPEG),也可以是基于矢量的(如 SVG)。在可能的情况下,我们建议使用可缩放的图像,因为它们会随着大小的变化而继续渲染良好。使用原始图像大小时要小心,因为它们在不同的用户界面比例下的行为可能与预期不完全一致。由于Fyne允许整个用户界面缩放25px图像文件可能与25高度fyne对象的高度不同。

package main

import (
    "fyne.io/fyne/v2/app"
    "fyne.io/fyne/v2/canvas"
    "fyne.io/fyne/v2/theme"
)

func main() {
    myApp := app.New()
    w := myApp.NewWindow("Image")

    image := canvas.NewImageFromResource(theme.FyneLogo())
    // image := canvas.NewImageFromURI(uri)
    // image := canvas.NewImageFromImage(src)
    // image := canvas.NewImageFromReader(reader, name)
    // image := canvas.NewImageFromFile(fileName)
    image.FillMode = canvas.ImageFillOriginal
    w.SetContent(image)

    w.ShowAndRun()
}

光栅(像素点)

就像一个图像,但屏幕上的每个像素只画一个点。这意味着,随着用户界面的缩放或图像的调整大小,将请求更多的像素来填充空间。为此,我们使用本示例中所示的函数 - 它将用于返回每个像素的颜色。canvas.Raster Generator

生成器函数可以基于像素(如本例所示,我们为每个像素生成新的随机颜色),也可以基于完整图像。生成完整的图像(使用)更有效,但有时直接控制像素更方便。canvas.NewRaster()

如果您的像素数据存储在图像中,您可以通过该功能加载它,该功能将加载图像以在屏幕上显示完美的像素。NewRasterFromImage()

package main

import (
    "image/color"
    "math/rand"

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

func main() {
    myApp := app.New()
    w := myApp.NewWindow("Raster")

    raster := canvas.NewRasterWithPixels(
        func(_, _, w, h int) color.Color {
            return color.RGBA{uint8(rand.Intn(255)),
                uint8(rand.Intn(255)),
                uint8(rand.Intn(255)), 0xff}
        })
    // raster := canvas.NewRasterFromImage()
    w.SetContent(raster)
    w.Resize(fyne.NewSize(120, 100))
    w.ShowAndRun()
}

渐变

最后一个画布基元类型是渐变,它用作 和 用于以各种模式绘制从一种颜色到另一种颜色的渐变。可以使用 或 创建渐变。canvas.LinearGradient canvas.RadialGradient NewHorizontalGradient() NewVerticalGradient() NewRadialGradient()

要创建渐变,您需要开始和结束颜色 - 中间的每种颜色都由画布计算。在此示例中,我们用于演示渐变(或任何其他类型)如何使用 alpha 值在后面的内容上保持半透明。color.Transparent

这就是我们在Fyne的画布元素之旅。接下来,请花一些时间了解可用于排列界面元素的布局

布局

水平/垂直布局

如容器和布局中所述,可以使用布局来排列容器中的元素。本节探讨内置布局以及如何使用它们。

最常用的布局是,它有两个变体,水平和垂直。框布局将所有元素排列在一行或一列中,并带有可选空格以帮助对齐。layout.BoxLayout

创建时的水平框布局可在一行中创建项目的排列方式。框中的每个项目的宽度都将设置为其宽度,并且所有项目的高度将相等,这是所有值中最大的项目。布局可以在容器中使用,也可以使用 框小部件 。layout.NewHBoxLayout() MinSize().Width MinSize().Height widget.NewHBox()

垂直框布局类似,但它在列中排列项目。每个项目的高度将设置为最小值,并且所有宽度将相等,设置为最小宽度中的最大宽度。

要在元素之间创建扩展空间(例如,使某些元素左对齐,而其他元素右对齐),请将 a 添加为项之一。垫片将展开以填充所有可用空间。在垂直框布局的开头添加间隔条将导致所有项目底部对齐。可以在水平排列的开头和结尾添加一个以创建中心对齐。layout.NewSpacer()

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("Hello", color.White)
    text2 := canvas.NewText("There", color.White)
    text3 := canvas.NewText("(right)", color.White)
    content := container.New(layout.NewHBoxLayout(), text1, text2, layout.NewSpacer(), text3)

    text4 := canvas.NewText("centered", color.White)
    centered := container.New(layout.NewHBoxLayout(), layout.NewSpacer(), text4, layout.NewSpacer())
    myWindow.SetContent(container.New(layout.NewVBoxLayout(), content, centered))
    myWindow.ShowAndRun()
}
image.png

网格布局

网格布局以具有固定列数的网格模式布置容器的元素。项目将填充一行,直到达到列数,在此之后将创建一个新行。垂直空间将在每行对象之间平均分配。

您可以使用其中 cols 创建网格布局,其中 cols 是您希望在每行中拥有的项(列)数。然后,此布局将作为第一个参数传递给 。layout.NewGridLayout(cols) container.New(...)

如果调整容器大小,则每个单元格的大小将平均调整以共享可用空间。

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("Grid Layout")

    text1 := canvas.NewText("1", color.White)
    text2 := canvas.NewText("2", color.White)
    text3 := canvas.NewText("3", color.White)
    grid := container.New(layout.NewGridLayout(2), text1, text2, text3)
    myWindow.SetContent(grid)
    myWindow.ShowAndRun()
}
image.png

网格环绕布局

与之前的网格布局一样,网格环绕布局在网格模式中创建元素的排列。但是,此网格没有设置的列数,而是对每个单元格使用固定大小,然后将内容排列到显示项目所需的任意数量的行。

您可以使用大小指定应用于所有子元素的大小来创建网格环绕布局。然后,此布局将作为第一个参数传递给 。列数和行数将根据容器的当前大小进行计算。layout.NewGridWrapLayout(size) container.New(...)

最初,网格环绕布局将具有单个列,如果调整其大小(如右侧的代码注释所示),它将重新排列子元素以填充空间。

package main

import (
    "image/color"

    "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("Grid Wrap Layout")

    text1 := canvas.NewText("1", color.White)
    text2 := canvas.NewText("2", color.White)
    text3 := canvas.NewText("3", color.White)
    grid := container.New(layout.NewGridWrapLayout(fyne.NewSize(50, 50)),text1, text2, text3)
    myWindow.SetContent(grid)

    // myWindow.Resize(fyne.NewSize(180, 75))
    myWindow.ShowAndRun()
}

image.png

可以换行显示

边框布局

边框布局可能是用于构建用户界面的最广泛用途,因为它允许将项目定位在中心元素周围,该元素将扩展以填充空间。要创建边框布局,您需要将应放置在边框位置的 s 传递到布局(以及像往常一样传递容器)。此语法与其他布局略有不同,但基本上与右侧的示例中所示。fyne.CanvasObject layout.NewBorderLayout(top, bottom, left, right)

传递到容器且未出现在特定边界位置的任何项目都将定位到中心区域,并将展开以填充可用空间。还可以将要留空的参数传递给边框。nil

请注意,中心中的所有项目都将展开以填充空间(就像它们在布局中一样。MaxLayout容器)。要自己管理该区域,您可以创建新的(使用)并使用所需的任何布局。fyne.Container container.New()

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("Border Layout")

    top := canvas.NewText("top bar", color.White)
    left := canvas.NewText("left", color.White)
    middle := canvas.NewText("content", color.White)
    content := container.New(layout.NewBorderLayout(top, nil, left, nil),
        top, left, middle)
    myWindow.SetContent(content)
    myWindow.ShowAndRun()
}

image.png

表单布局

它类似于 2 列网格布局,但进行了调整以在应用程序中布局表单。每个项目的高度将是每行中两个最小高度中的较大者。左侧项目的宽度将是第一列中所有项目的最大最小宽度,而每行中的第二个项目将展开以填充空间。layout.FormLayout

此布局更通常用于(用于验证、提交和取消按钮等),但它也可以直接用于传递给 的第一个参数。widget.Form layout.NewFormLayout() container.New(...)

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("Form Layout")

    label1 := canvas.NewText("Label 1", color.Black)
    value1 := canvas.NewText("Value", color.White)
    label2 := canvas.NewText("Label 2", color.Black)
    value2 := canvas.NewText("Something", color.White)
    grid := container.New(layout.NewFormLayout(), label1, value1, label2, value2)
    myWindow.SetContent(grid)
    myWindow.ShowAndRun()
}

image.png

中心布局

layout.CenterLayout将容器中的所有项目组织到可用空间中的中心。对象将按传递到容器的顺序绘制,最后一个对象绘制在最上面。

中心布局使所有项目都保持其最小大小,如果您希望展开项目以填充空间,请参阅布局。最大伸缩.

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"
    "fyne.io/fyne/v2/theme"
)

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

    img := canvas.NewImageFromResource(theme.FyneLogo())
    img.FillMode = canvas.ImageFillOriginal
    text := canvas.NewText("Overlay", color.Black)
    content := container.New(layout.NewCenterLayout(), img, text)

    myWindow.SetContent(content)
    myWindow.ShowAndRun()
}

image.png

最大布局

这是最简单的布局,它将容器中的所有项设置为与容器相同的大小。这在一般容器中通常没有用,但在编写小部件时可能很合适。layout.MaxLayout

最大布局会将容器扩展为至少最大项的最小大小。对象将按传递到容器的顺序绘制,最后一个对象绘制在最上面。

现在我们已经知道如何布局用户界面,我们将继续使用容器包,该包简化了布局,并允许您以更多方式布局对象。

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"
    "fyne.io/fyne/v2/theme"
)

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

    img := canvas.NewImageFromResource(theme.FyneLogo())
    text := canvas.NewText("Overlay", color.Black)
    content := container.New(layout.NewMaxLayout(), img, text)

    myWindow.SetContent(content)
    myWindow.ShowAndRun()
}

image.png

容器

应用选项卡

AppTabs 容器用于允许用户在不同的内容面板之间切换。选项卡要么只是文本,要么只是文本和图标。建议不要混合一些有图标的选项卡和一些没有图标的选项卡。选项卡容器是使用和传递项创建的(可以使用 创建)。container.NewAppTabs(...) container.TabItem container.NewTabItem(...)

选项卡容器可以通过设置选项卡的位置来配置,选项卡是 、 和 。默认位置为顶部。container.TabLocationTop container.TabLocationBottom container.TabLocationLeading container.TabLocationTrailing

在移动设备上加载时,选项卡位置可能会被忽略。在纵向方向中,行距或尾随位置将更改为底部。横向时,顶部或底部位置将移动到前导位置。

package main

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

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

    tabs := container.NewAppTabs(
        container.NewTabItem("Tab 1", widget.NewLabel("Hello")),
        container.NewTabItem("Tab 2", widget.NewLabel("World!")),
    )

    //tabs.Append(container.NewTabItemWithIcon("Home", theme.HomeIcon(), widget.NewLabel("Home tab")))

    tabs.SetTabLocation(container.TabLocationLeading)

    myWindow.SetContent(tabs)
    myWindow.ShowAndRun()
}
image.png
image.png

框微件是一个简单的水平或垂直容器,它使用框布局来布置子组件。可以传递要包含在 或 构造函数中的对象。container.NewHBox() container.NewVBox()

还可以在创建框构件后(在现有内容之后)向框构件添加项目,或使用 删除项目。Add() Remove()

package main

import (
    "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")

    content := container.NewVBox(
        widget.NewLabel("The top row of the VBox"),
        container.NewHBox(
            widget.NewLabel("Label 1"),
            widget.NewLabel("Label 2"),
        ),
    )

    content.Add(widget.NewButton("Add more items", func() {
        content.Add(widget.NewLabel("Added"))
    }))

    myWindow.SetContent(content)
    myWindow.ShowAndRun()
}
image.png

控件

标签

小部件是Fyne应用程序GUI的主要组件,它们可以在基本可以的任何地方使用。它们管理用户交互,并始终与当前主题匹配。fyne.CanvasObject

"标签"微件是其中最简单的 - 它向用户显示文本。与它不同,它可以处理一些简单的格式(如)和换行(通过设置字段)。您可以通过调用 来创建标签,结果可以分配给变量或直接传递到容器中。canvas.Text \n Wrapping widget.NewLabel("some text")

package main

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

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

    content := widget.NewLabel("text")

    myWindow.SetContent(content)
    myWindow.ShowAndRun()
}

image.png

按钮

按钮小部件可以包含文本、图标或两者,构造函数为 和 。要创建文本按钮,只有 2 个参数,即内容和一个 0 参数,该参数将在点击按钮时调用。请参阅示例,了解如何创建它。widget.NewButton() widget.NewButtonWithIcon() string func()

带有图标的按钮构造函数包含一个附加参数,该参数是包含图标数据的参数。包中的内置图标都适当地适应了主题的变化。如果将自己的映像作为资源加载,则可以传入该映像 - 辅助工具(如可能提供帮助),但建议尽可能捆绑资源。fyne.Resource theme fyne.LoadResourceFromPath()

要创建仅包含图标的按钮,应将 "" 作为 label 参数传递给 。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")

    content := widget.NewButton("click me", func() {
        log.Println("tapped")
    })

    //content := widget.NewButtonWithIcon("Home", theme.HomeIcon(), func() {
    //  log.Println("tapped home")
    //})

    myWindow.SetContent(content)
    myWindow.ShowAndRun()
}

image.png

输入框

输入微件用于用户输入简单文本内容。可以使用简单的构造函数创建条目。创建微件时,请保留引用,以便以后可以访问其字段。也可以使用回调函数在每次内容更改时收到通知。widget.NewEntry() Text OnChanged

条目小组件还可以进行验证,以验证输入到其中的文本输入。这可以通过将字段设置为 .您还可以设置文本,并将条目设置为接受多行文本。Validator fyne.StringValidator PlaceHolder MultiLine

您还可以使用该函数创建密码条目(其中内容被遮挡)。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("Enter text...")

    content := container.NewVBox(input, widget.NewButton("Save", func() {
        log.Println("Content was:", input.Text)
    }))

    myWindow.SetContent(content)
    myWindow.ShowAndRun()
}
image.png

选择框

有各种小部件可供用户选择,其中包括复选框,单选按钮组和选择弹出窗口。

提供了一个简单的是/否选项,并使用字符串标签创建。这些小部件中的每一个还采用"已更改",其中参数是适当的类型。 因此,为标签获取一个参数,为更改处理程序获取一个参数。您还可以使用该字段获取布尔值。widget.Check func(...) widget.NewCheck(..) string func(bool) Checked

单选按钮小部件类似,但第一个参数是表示每个选项的 s 切片。change 函数希望这次有一个参数返回当前选定的值。调用以构造单选集组小部件,您可以稍后使用此引用来读取字段,而不是使用更改回调。string string widget.NewRadioGroup(...) Selected

选择微件在构造函数签名中与单选按钮微件相同。"调用"将显示一个按钮,该按钮在点击时显示一个弹出窗口,用户可以从中进行选择。这更适合长选项列表。widget.NewSelect(...)

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("Optional", func(value bool) {
        log.Println("Check set to", value)
    })
    radio := widget.NewRadioGroup([]string{"Option 1", "Option 2"}, func(value string) {
        log.Println("Radio set to", value)
    })
    combo := widget.NewSelect([]string{"Option 1", "Option 2"}, func(value string) {
        log.Println("Select set to", value)
    })

    myWindow.SetContent(container.NewVBox(check, radio, combo))
    myWindow.ShowAndRun()
}

image.png

表单

表单构件用于布置许多带有标签和可选取消和提交按钮的输入字段。在最裸露的形式中,它将标签对齐到每个输入小部件的左侧。通过设置 OnCancel 或 OnSubmit,表单将添加一个按钮栏,其中包含在适当的时候调用的指定处理程序。

可以通过传递 s 列表或使用示例中所示的语法来创建小部件。还有一个有用的,可用于替代语法。widget.NewForm(...) widget.FormItem &widget.Form{} Form.Append(label, widget)

在此示例中,我们创建两个条目,其中一个是"多行"(如 HTML 文本区域)来保存值。有一个OnSubmit处理程序,它在关闭窗口(以及应用程序)之前打印信息。

package main

import (
    "log"

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

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

    entry := widget.NewEntry()
    textArea := widget.NewMultiLineEntry()

    form := &widget.Form{
        Items: []*widget.FormItem{ // we can specify items in the constructor
            {Text: "Entry", Widget: entry}},
        OnSubmit: func() { // optional, handle form submission
            log.Println("Form submitted:", entry.Text)
            log.Println("multiline:", textArea.Text)
            myWindow.Close()
        },
    }

    // we can also append items
    form.Append("Text", textArea)

    myWindow.SetContent(form)
    myWindow.ShowAndRun()
}
image.png

进度条

进度条小部件有两种形式,标准进度条显示已到达的用户,从 到 。缺省最小值为 ,最大值缺省为 。要使用默认值,只需调用 .创建后,您可以设置字段。Value Min Max 0.0 1.0 widget.NewProgressBar() Value

要设置自定义范围,您可以手动设置和字段。标签将始终显示完成百分比。Min Max

进度小部件的另一种形式是无限进度条。此版本只是通过将条形的一段从左向右再向后移动来显示某些活动正在进行中。您可以使用它创建它,它将在显示后立即开始动画。widget.NewProgressBarInfinite()

package main

import (
    "time"

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

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

    progress := widget.NewProgressBar()
    infinite := widget.NewProgressBarInfinite()

    go func() {
        for i := 0.0; i <= 1.0; i += 0.1 {
            time.Sleep(time.Millisecond * 250)
            progress.SetValue(i)
        }
    }()

    myWindow.SetContent(container.NewVBox(progress, infinite))
    myWindow.ShowAndRun()
}

image.png

工具栏

工具栏小组件使用图标来表示每个按钮,从而创建一行操作按钮。构造函数采用参数列表。工具栏项的内置类型包括操作、分隔符和间隔符。widget.NewToolbar(...) widget.ToolbarItem

最常用的项是使用该函数创建的操作。操作采用两个参数,第一个是要绘制的图标资源,后者是点击时要调用的参数。这将创建一个标准工具栏按钮。widget.NewToolbarItemAction(..) func()

您可以使用在工具栏中的项目之间创建小分隔线(通常是一条细竖线)。最后,您可以使用在元素之间创建灵活的空间。这对于右对齐在垫片后列出的工具栏项最有用。widget.NewToolbarSeparator() widget.NewToolbarSpacer()

工具栏应始终位于内容区域的顶部,因此将其添加到使用 将其与其他内容对齐是正常的。fyne.Container layout.BorderLayout

package main

import (
    "log"

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

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

    toolbar := widget.NewToolbar(
        widget.NewToolbarAction(theme.DocumentCreateIcon(), func() {
            log.Println("New document")
        }),
        widget.NewToolbarSeparator(),
        widget.NewToolbarAction(theme.ContentCutIcon(), func() {}),
        widget.NewToolbarAction(theme.ContentCopyIcon(), func() {}),
        widget.NewToolbarAction(theme.ContentPasteIcon(), func() {}),
        widget.NewToolbarSpacer(),
        widget.NewToolbarAction(theme.HelpIcon(), func() {
            log.Println("Display help")
        }),
    )

    content := container.NewBorder(toolbar, nil, nil, nil, widget.NewLabel("Content"))
    myWindow.SetContent(content)
    myWindow.ShowAndRun()
}
image.png

列表

该小部件是工具包的集合小部件之一。这些小部件旨在帮助在呈现大量数据时构建真正高性能的界面。您还可以看到具有类似 API 的表和小部件。由于这种设计,它们使用起来有点复杂。List Tree

在需要时使用回调函数来请求数据。有 3 个主要回调,和 。Length 回调(首先传递)是最简单的,它返回要呈现的数据中有多少项。其他的与模板有关 - 如何创建,缓存和重用图形元素。List Length CreateItem UpdateItem

回调返回一个新的模板对象。当小部件显示时,这将与真实数据一起重复使用。此对象的 将影响最小大小。最后调用 以将数据项应用于缓存的模板。使用此选项可设置准备显示的内容。CreateItem MinSize List UpdateItem

package main

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

var data = []string{"a", "string", "list"}

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

    list := widget.NewList(
        func() int {
            return len(data)
        },
        func() fyne.CanvasObject {
            return widget.NewLabel("template")
        },
        func(i widget.ListItemID, o fyne.CanvasObject) {
            o.(*widget.Label).SetText(data[i])
        })

    myWindow.SetContent(list)
    myWindow.ShowAndRun()
}

image.png

部件集合

小部件类似于具有二维索引的小部件(工具包的另一个集合小部件)。像这样,旨在帮助在呈现大量数据时构建真正高性能的接口。因此,创建小组件时不会嵌入所有数据,而是在需要时调用数据源。Table List List

在需要时使用回调函数来请求数据。有 3 个主要回调,和 。Length 回调(首先传递)是最简单的,它返回要呈现的数据中有多少个项目,它返回的两个整数表示行和 colum 计数。另外两个与内容模板相关。Table Length CreateCell UpdateCell

回调返回一个新的模板对象,就像 list 一样。不同之处在于,这将定义每个单元格的标准大小以及表的最小大小(它至少显示一个单元格)。如前所述,调用 用于将数据应用于单元格模板。传入的索引是同一个整数对。CreateCell MinSize UpdateCell (row, col)

在下一个教程部分中,我们将探讨数据绑定。

package main

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

var data = [][]string{[]string{"top left", "top right"},
    []string{"bottom left", "bottom right"}}

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

    list := widget.NewTable(
        func() (int, int) {
//获取行数和列数
            return len(data), len(data[0])
        },
        func() fyne.CanvasObject {
            return widget.NewLabel("wide content")
        },
        func(i widget.TableCellID, o fyne.CanvasObject) {
            o.(*widget.Label).SetText(data[i.Row][i.Col])
        })

    myWindow.SetContent(list)
    myWindow.ShowAndRun()
}
image.png

数据绑定

数据绑定

数据绑定是版本中引入的Fyne工具包的强大新增功能。通过使用数据绑定,我们可以避免手动管理许多标准对象,如s,s和s。v2.0.0 Label Button List

内置绑定支持许多基元类型(如 、等)、列表(如 、 )以及绑定。这些类型中的每一个都可以使用简单的构造函数创建。例如,要创建一个值为零的新字符串绑定,可以使用 。可以使用 和 方法获取或设置大多数数据绑定的值。Int String Float StringList BoolList Map Struct binding.NewString() Get Set

也可以使用名称开头的类似函数绑定到现有值,并且它们都接受指向绑定类型的指针。要绑定到现有的,我们可以使用 .通过保留对绑定值的引用而不是原始变量,我们可以配置小部件和函数以自动响应任何更改。如果直接更改外部数据,请确保调用 () 以确保绑定系统读取新值。Bind int binding.BindInt(&myInt) Reload

接下来,我们将开始学习简单的值小部件绑定。

package main

import (
    "log"

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

func main() {
    vcs:="123"
    boundString := binding.BindString(&vcs)
    s, _ := boundString.Get()
    log.Printf("Bound = '%s'", s)

    myInt := 5
    boundInt := binding.BindInt(&myInt)
    i, _ := boundInt.Get()
    log.Printf("Source = %d, bound = %d", myInt, i)
}
image.png

绑定简单小部件

绑定小部件的最简单形式是将其作为值而不是静态值传递给它绑定项。许多小组件都提供了一个构造函数,该构造函数将接受类型化的数据绑定项。要设置绑定,您需要做的就是传入正确的类型。WithData

尽管这在初始代码中看起来没有多大好处,但您可以看到它如何确保显示的内容始终与数据源保持同步。您会注意到我们不需要调用小部件,甚至不需要保留对它的引用,但它会相应地更新。Refresh() Label

在下一步中,我们将介绍如何设置通过双向绑定编辑值的小部件。

package main

import (
    "time"

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

func main() {
    myApp := app.New()
    w := myApp.NewWindow("Simple")

    str := binding.NewString()
    str.Set("Initial value")

    text := widget.NewLabelWithData(str)
    w.SetContent(text)

    go func() {
        time.Sleep(time.Second * 2)
        str.Set("A new string")
    }()

    w.ShowAndRun()
}
image.png

双向绑定

到目前为止,我们已经将数据绑定视为使用户界面元素保持最新的一种方式。但是,更常见的是需要更新 UI 小部件中的值,并使数据在任何地方都保持最新。值得庆幸的是,Fyne中提供的绑定是"双向的",这意味着可以将值推入其中并读出。数据中的更改将传达给所有连接的代码,而无需任何其他代码。

若要查看此操作的实际效果,我们可以更新上一个测试应用,以显示绑定到相同值的 a 和 a。通过设置,您可以看到通过条目编辑值也会更新标签中的文本。这一切都是可能的,而无需调用刷新或引用代码中的小部件。Label``Entry

通过将应用程序移动到使用数据绑定,您可以停止保存指向所有小部件的指针。相反,通过将数据捕获为一组绑定值,您的用户界面可以是完全独立的代码。更易于阅读,更易于管理。

接下来,我们将介绍如何在数据中添加转化次数

package main

import (
    "fyne.io/fyne/v2/app"
    "fyne.io/fyne/v2/container"
    "fyne.io/fyne/v2/data/binding"
    "fyne.io/fyne/v2/widget"
)

func main() {
    myApp := app.New()
    w := myApp.NewWindow("Two Way")

    str := binding.NewString()
    str.Set("Hi!")

    w.SetContent(container.NewVBox(
        widget.NewLabelWithData(str),
        widget.NewEntryWithData(str),
    ))

    w.ShowAndRun()
}
image.png

数据转换

到目前为止,我们已经使用了数据绑定,其中数据类型与输出类型匹配(例如和/或)。通常,最好提供尚未采用正确格式的数据。为此,该软件包提供了许多有用的转换函数。String``Label``Entry``binding

最常见的是,这将用于将不同类型的数据转换为字符串以显示在或小部件中。在代码中,请参阅如何将 a 转换为 using 。可以通过移动滑块来编辑原始值。每次数据更改时,它都会运行转换代码并更新任何连接的小组件。Label``Entry``Float``String``binding.FloatToString

也可以使用格式字符串为用户界面添加更自然的输出。您可以看到,我们的绑定也在将 转换为 ,但通过使用帮助程序,我们可以传递格式字符串(类似于包)以提供自定义输出。short``Float``String``WithFormat``fmt

最后,在本节中,我们将查看列表数据。

package main

import (
    "fyne.io/fyne/v2/app"
    "fyne.io/fyne/v2/container"
    "fyne.io/fyne/v2/data/binding"
    "fyne.io/fyne/v2/widget"
)

func main() {
    myApp := app.New()
    w := myApp.NewWindow("Conversion")

    f := binding.NewFloat()
    str := binding.FloatToString(f)
    short := binding.FloatToStringWithFormat(f, "%0.0f%%")
    f.Set(25.0)

    w.SetContent(container.NewVBox(
        widget.NewSliderWithData(0, 100.0, f),
        widget.NewLabelWithData(str),
        widget.NewLabelWithData(short),
    ))

    w.ShowAndRun()
}
image.png
列表数据

为了演示如何连接更复杂的类型,我们将介绍小部件以及数据绑定如何使其更易于使用。首先,我们创建一个数据绑定,它是数据类型的列表。一旦我们有了列表类型的数据,我们就可以将其连接到标准小部件。为此,我们使用构造函数,就像其他小部件一样。List``StringList``String``List``widget.NewListWithData

将此代码与列表教程进行比较 您将看到2个主要更改,第一个是我们传递数据类型作为第一个参数而不是长度回调函数。第二个变化是最后一个参数,我们的回调。修订后的版本采用一个值而不是 。使用此回调结构时,我们应该使用模板标签小部件,而不是调用 。这意味着,如果数据源中的任何字符串发生更改,则表的每个受影响的行都将刷新。UpdateItem``binding.DataItem``widget.ListIndexID``Bind``SetText

在我们的演示代码中有一个"Append",当点击时,它会将一个新值附加到数据源。这样做将自动触发数据更改处理程序,并展开小组件以显示新数据。Button``List

package main

import (
    "fmt"

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

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

    data := binding.BindStringList(
        &[]string{"Item 1", "Item 2", "Item 3"},
    )

    list := widget.NewListWithData(data,
        func() fyne.CanvasObject {
            return widget.NewLabel("template")
        },
        func(i binding.DataItem, o fyne.CanvasObject) {
            o.(*widget.Label).Bind(i.(binding.String))
        })

    add := widget.NewButton("Append", func() {
        val := fmt.Sprintf("Item %d", data.Length()+1)
        data.Append(val)
    })
    myWindow.SetContent(container.NewBorder(nil, add, nil, nil, list))
    myWindow.ShowAndRun()
}
image.png

教程

扩展小部件

编辑我标准的Fyne小部件提供了最少的功能和定制,以支持大多数用例。在某些时候,可能需要它具有更高级的功能。与其让开发人员构建自己的小部件,不如扩展现有的小部件。

例如,我们将扩展图标小部件以支持被点击。为此,我们声明了一个嵌入该类型的新结构。我们创建一个调用重要函数的构造函数。widget.Icon``ExtendBaseWidget

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

type tappableIcon struct {
    widget.Icon
}

func newTappableIcon(res fyne.Resource) *tappableIcon {
    icon := &tappableIcon{}
    icon.ExtendBaseWidget(icon)
    icon.SetResource(res)

    return icon
}

注意:像这样的小部件构造函数可能不会用于扩展,因为它已经调用了 。widget.NewIcon ExtendBaseWidget

然后,我们添加新函数来实现接口,添加这些函数后,每次用户点击我们的新图标时,都会调用新函数。所需的接口有两个函数,和 ,因此我们将同时添加这两个函数。fyne.Tappable Tapped Tapped(*PointEvent) TappedSecondary(*PointEvent)

import "log"

func (t *tappableIcon) Tapped(_ *fyne.PointEvent) {
    log.Println("I have been tapped")
}

func (t *tappableIcon) TappedSecondary(_ *fyne.PointEvent) {
}

我们可以使用一个简单的应用程序来测试这个新的小部件,如下所示。

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

func main() {
    a := app.New()
    w := a.NewWindow("Tappable")
    w.SetContent(newTappableIcon(theme.FyneLogo()))
    w.ShowAndRun()
}

构建自定义布局

编辑我在Fyne应用程序中,每个元素都使用简单的布局算法排列其子元素。Fyne定义了软件包中可用的许多布局。如果你看一下代码,你会发现它们都实现了接口。Container``fyne.io/fyne/v2/layout``Layout

type Layout interface {
    Layout([]CanvasObject, Size)
    MinSize(objects []CanvasObject) Size
}

任何应用程序都可以提供自定义布局,以非标准方式排列小部件。为此,您需要在自己的代码中实现上面的接口。为了说明这一点,我们将创建一个新布局,该布局将元素排列在对角线上,并排列在其容器的左下角。

首先,我们将定义一个新类型,并定义它的最小大小。为了计算这一点,我们只需将所有子元素的宽度和高度(指定为参数添加到 .diagonal``[]fyne.CanvasObject``MinSize

import "fyne.io/fyne/v2"

type diagonal struct {
}

func (d *diagonal) MinSize(objects []fyne.CanvasObject) fyne.Size {
    w, h := float32(0), float32(0)
    for _, o := range objects {
        childSize := o.MinSize()

        w += childSize.Width
        h += childSize.Height
    }
    return fyne.NewSize(w, h)
}

对于此类型,我们添加了一个函数,该函数应将所有指定的对象移动到第二个参数中的指定对象中。Layout()``fyne.Size

在我们的实现中,我们计算小部件的左上角(这是 x 参数,并且具有 y 位置,即容器的高度减去所有子项高度的总和)。从顶部位置开始,我们只需将每个项目位置按前一个子项的大小前进即可。0

func (d *diagonal) Layout(objects []fyne.CanvasObject, containerSize fyne.Size) {
    pos := fyne.NewPos(0, containerSize.Height - d.MinSize(objects).Height)
    for _, o := range objects {
        size := o.MinSize()
        o.Resize(size)
        o.Move(pos)

        pos = pos.Add(fyne.NewPos(size.Width, size.Height))
    }
}

这就是创建自定义布局的全部内容。现在代码都写好了,我们可以将其用作 的参数。下面的代码设置了3个小部件,并将它们放置在具有我们新布局的容器中。如果运行此应用程序,您将看到对角线小部件排列,并且在调整窗口大小时,它们将与可用空间的左下角对齐。layout``container.New``Label

package main

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

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

    text1 := widget.NewLabel("topleft")
    text2 := widget.NewLabel("Middle Label")
    text3 := widget.NewLabel("bottomright")

    w.SetContent(container.New(&diagonal{}, text1, text2, text3))
    w.ShowAndRun()
}
image.png

捆绑资源

编辑我基于Go的应用程序通常构建为单个二进制可执行文件,对于Fyne应用程序也是如此。单个文件可以更轻松地分发安装我们的软件。遗憾的是,GUI 应用程序通常需要额外的资源来呈现用户界面。为了应对这一挑战,Go 应用程序可以将资源捆绑到二进制文件本身中。Fyne工具包更喜欢使用"fyne捆绑包",因为它具有各种好处,我们将在下面进行探讨。

捆绑资产的基本方法是执行"fyne bundle"命令。此工具具有各种参数来自定义输出,但在其基本形式中,要捆绑的文件将转换为可以内置到应用程序中的Go源代码。

$ ls
image.png   main.go
$ fyne bundle image.png >> bundled.go
$ ls
bundled.go  image.png   main.go
$ 

的内容将是一个资源变量列表,然后我们可以在代码中访问这些变量。例如,上面的代码将生成一个包含以下内容的文件:bundled.go

var resourceImagePng = &fyne.StaticResource{
    StaticName: "image.png",
    StaticContent: []byte{
...
    }}

如您所见,默认命名为"资源."。此文件中使用的名称和包可以在命令参数中自定义。然后,我们可以使用此名称,例如,在画布上加载图像:

img := canvas.NewImageFromResource(resourceImagePng)

fyne资源只是具有唯一名称的字节的集合,因此这可能是字体,声音文件或要加载的任何其他数据。此外,您还可以使用该参数将许多资源捆绑到单个文件中。如果您要捆绑许多文件,建议将命令保存在shell脚本中,例如此文件:-append``gen.sh

#!/bin/bash
fyne bundle image1.png > bundled.go
fyne bundle -append image2.png >> bundled.go

如果随后更改任何资产或添加新资产,则可以更新此文件并运行一次以更新文件。然后,您应该添加到版本控制中,以便其他人可以构建您的应用程序,而无需运行"fyne bundle"。添加也是一个好主意,以便其他人可以在需要时重新生成捆绑的资源。bundled.go``bundled.go``gen.sh

编写自定义小部件

编辑我Fyne附带的标准小部件旨在支持标准用户交互和要求。由于 GUI 通常必须提供自定义功能,因此可能需要编写自定义小部件。本文概述了如何操作。

小部件分为两个区域 - 每个区域实现一个标准接口 - 和 .微件定义行为和状态,渲染器用于定义应如何将其绘制到屏幕上。fyne.Widget``fyne.WidgetRenderer

控件

Fyne中的小部件只是一个有状态的画布对象,其渲染定义与主逻辑分离。从界面中可以看出,没有太多必须实现的内容。fyne.Widget

type Widget interface {
    CanvasObject

    CreateRenderer() WidgetRenderer
}

由于小部件需要像我们从同一界面继承的任何其他画布项目一样使用。为了节省编写所需的所有函数,我们可以使用处理基础知识的类型。widget.BaseWidget

每个小部件定义将包含比接口所需的更多内容。在 Fyne 小部件中,导出定义行为的字段是标准配置(就像包中定义的基元一样)。canvas

例如,查看类型:widget.Button

type Button struct {
    BaseWidget
    Text  string
    Style ButtonStyle
    Icon  fyne.Resource

    OnTapped func()
}

您可以看到这些项目中的每一个如何存储有关小部件行为的状态,但看不到有关其呈现方式的任何内容。

WidgetRenderer

小部件渲染器负责管理一个基元列表,这些基元组合在一起以创建小部件的设计。它很像具有自定义布局和一些附加主题处理的。fyne.CanvasObject``fyne.Container

每个小组件都必须提供一个渲染器,但完全可以从另一个小组件重用渲染器 - 特别是如果您的小组件是另一个标准控件的轻量级包装器。

type WidgetRenderer interface {
    Layout(Size)
    MinSize() Size

    Refresh()
    Objects() []CanvasObject
    Destroy()
}

如您所见,and 函数类似于界面,没有参数 - 这是因为小部件确实需要布局,但它控制将包含哪些对象。Layout(Size)``MinSize()``fyne.Layout``[]fyne.CanvasObject

当此渲染器绘制的小部件已更改或主题已更改时,将触发该方法。在任何一种情况下,我们可能需要对它的外观进行调整。最后,当不再需要此渲染器时,将调用该方法,因此它应该清除任何否则会泄漏的资源。Refresh()``Destroy()

再次与按钮小部件进行比较 - 它的实现基于以下类型:fyne.WidgetRenderer

type buttonRenderer struct {
    icon   *canvas.Image
    label  *canvas.Text
    shadow *fyne.CanvasObject

    objects []fyne.CanvasObject
    button  *Button
}

如您所见,它具有用于缓存实际图像,文本和阴影画布对象以进行绘制的字段。为了方便起见,它会跟踪所需的对象切片。fyne.WidgetRenderer

最后,它保留了对所有状态信息的引用。在该方法中,它将根据基础类型中的任何更改更新图形状态。widget.Button``Refresh()``widget.Button

整合在一起

基本小组件将扩展类型并声明小组件所持有的任何状态。该函数必须存在并返回新实例。Fyne中的小部件和驱动程序代码将确保相应地缓存它 - 此方法可以多次调用(例如,如果小部件被隐藏然后显示)。如果再次调用,则应返回新的渲染器实例,因为旧实例可能已被销毁。widget.BaseWidget``CreateRenderer()``fyne.WidgetRenderer``CreateRenderer()

请注意不要在渲染器中保留任何重要状态 - 动画自动取号非常适合该位置,但用户状态不适合。隐藏的微件可能会破坏其渲染器,如果再次显示,则新渲染器必须能够反映相同的微件状态。

数字输入

编辑我在传统意义上,GUI程序使用回调来自定义小部件的操作。Fyne 不会公开插入自定义回调来捕获小部件上的事件,但不需要这样做。Go语言具有足够的可扩展性,可以使其工作。

相反,我们可以简单地使用类型嵌入并扩展小部件,以便仅输入数值。

首先创建一个新的类型结构,我们将它称为 。numericalEntry

type numericalEntry struct {
    widget.Entry
}

如扩展现有小部件中所述,我们遵循良好做法并创建一个构造函数来扩展 .BaseWidget

func newNumericalEntry() *numericalEntry {
    entry := &numericalEntry{}
    entry.ExtendBaseWidget(entry)
    return entry
}

现在,我们需要使条目仅接受数字。这可以通过重写作为接口一部分的方法来完成。这将使我们能够拦截从按键接收的符文的标准处理,并且只通过我们想要的符文。在此方法中,我们将使用条件来检查符文是否与零到九之间的任何数字匹配。如果他们这样做,我们将其委托给嵌入条目的标准方法。如果他们不这样做,我们只是忽略输入。此实现将仅允许输入整数,但可以根据需要轻松地扩展以在将来检查其他键。TypedRune(rune)``fyne.Focusable``TypedRune(rune)

func (e *numericalEntry) TypedRune(r rune) {
    switch r {
    case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
        e.Entry.TypedRune(r)
    }
}

如果我们想更新实现以允许十进制数字,我们可以简单地添加并允许的符文列表(某些语言在十进制符号的点上使用逗号)。.``,

func (e *numericalEntry) TypedRune(r rune) {
    switch r {
    case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', ',':
        e.Entry.TypedRune(r)
    }
}

这样,该条目现在只允许用户在按下键时输入数值。但是,粘贴快捷方式仍将允许输入文本。要解决此问题,我们可以覆盖作为接口一部分的方法。首先,我们需要做一个类型断言来检查给定的快捷方式是否属于 类型 。如果不是,我们可以将快捷方式委托回嵌入的条目。如果是,我们检查剪贴板内容是否是数字的,通过使用(如果你只想允许整数,那就好了),然后将快捷方式委派回嵌入的条目,如果剪贴板内容可以被解析而不会出错。TypedShortcut(fyne.Shortcut)``fyne.Shortcutable``*fyne.ShortcutPaste``strconv.ParseFloat()``strconv.Atoi()

func (e *numericalEntry) TypedShortcut(shortcut fyne.Shortcut) {
    paste, ok := shortcut.(*fyne.ShortcutPaste)
    if !ok {
        e.Entry.TypedShortcut(shortcut)
        return
    }

    content := paste.Clipboard.Content()
    if _, err := strconv.ParseFloat(content, 64); err == nil {
        e.Entry.TypedShortcut(shortcut)
    }
}

作为奖励,我们还可以确保移动操作系统打开数字键盘而不是默认键盘。这可以通过首先导入包并覆盖作为接口一部分的方法来完成。在函数内部,我们只需返回类型。fyne.io/fyne/v2/driver/mobile``Keyboard() mobile.KeyboardType``m̀obile.Keyboardable``mobile.NumberKeyboard

func (e *numericalEntry) Keyboard() mobile.KeyboardType {
    return mobile.NumberKeyboard
}

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

package main

import (
    "strconv"

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

type numericalEntry struct {
    widget.Entry
}

func newNumericalEntry() *numericalEntry {
    entry := &numericalEntry{}
    entry.ExtendBaseWidget(entry)
    return entry
}

func (e *numericalEntry) TypedRune(r rune) {
    switch r {
    case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', ',':
        e.Entry.TypedRune(r)
    }
}

func (e *numericalEntry) TypedShortcut(shortcut fyne.Shortcut) {
    paste, ok := shortcut.(*fyne.ShortcutPaste)
    if !ok {
        e.Entry.TypedShortcut(shortcut)
        return
    }

    content := paste.Clipboard.Content()
    if _, err := strconv.ParseFloat(content, 64); err == nil {
        e.Entry.TypedShortcut(shortcut)
    }
}

func (e *numericalEntry) Keyboard() mobile.KeyboardType {
    return mobile.NumberKeyboard
}

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

    entry := newNumericalEntry()

    w.SetContent(entry)
    w.ShowAndRun()
}

向应用添加快捷方式

  • Registering with a Canvas
  • Adding shortcuts to an Entry

编辑我快捷键是可由键盘组合或上下文菜单触发的常见任务。快捷键与键盘事件非常相似,可以附加到聚焦元素或注册到 .Canvas``Window

使用画布注册

定义了许多标准快捷键(如 ),这些快捷键连接到标准键盘快捷键和右键单击菜单。添加新的第一步是定义快捷方式。对于大多数用途,这将是键盘触发的快捷方式,这是一个桌面扩展。为此,我们使用 ,例如,要使用 Tab 键和 Control 修饰符,您可以执行以下操作:fyne.ShortcutCopy Shortcut desktop.CustomShortcut

    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")
    })

如您所见,以这种方式注册快捷方式有两个部分 - 传递快捷方式定义和回调函数。如果用户键入键盘快捷键,则将调用该函数并打印输出。

向条目添加快捷方式

仅当当前项目处于焦点时才应用快捷方式也很有帮助。此方法可用于任何可聚焦的小部件,并通过扩展该小部件和添加处理程序进行管理。这很类似于添加键处理程序,只是传入的值将是 .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

使用首选项 API

编辑我存储用户配置和值是应用程序开发人员的常见任务,但跨多个平台实现它可能既繁琐又耗时。为了使它更容易,Fyne有一个API,用于在为您处理复杂部分时,以干净易懂的方式在文件系统上存储值。

让我们从 API 的设置开始。它是首选项界面的一部分,其中存在 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)

可以通过创建的应用程序变量并调用该方法来访问这些函数。请注意,有必要使用唯一的ID创建应用程序(通常类似于反向URL)。这意味着需要使用 创建应用程序,以便有自己的位置来存储值。它可以大致如下例所示:Preferences()``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()
}

创建自定义主题

编辑我应用程序能够加载自定义主题,这些主题可以对标准主题应用小的更改或提供完全独特的外观。主题需要实现接口的功能,定义如下:fyne.Theme

type Theme interface {
    Color(ThemeColorName, ThemeVariant) color.Color
    Font(TextStyle) Resource
    Icon(ThemeIconName) Resource
    Size(ThemeSizeName) float32
}

为了应用我们的主题更改,我们将首先定义一个实现此接口的新类型。

定义主题

我们首先定义一个新类型,它将是我们的主题,一个简单的空结构就可以了:

type myTheme struct {}

最好断言我们实现了一个接口,以便编译错误更接近定义类型。

var _ fyne.Theme = (*myTheme)(nil)

此时,您可能会看到编译错误,因为我们仍然需要实现这些方法,我们从颜色开始。

自定义颜色

界面中定义的函数要求我们定义命名颜色,并为用户所需的变体(例如 或 ) 提供提示。在我们的主题中,我们将返回自定义背景色 - 使用不同的浅色和深色值。Color``Theme``theme.VariantLight``theme.VariantDark

func (m myTheme) Color(name fyne.ThemeColorName, variant fyne.ThemeVariant) color.Color {
    if name == theme.ColorNameBackground {
        if variant == theme.VariantLight {
            return color.White
        }
        return color.Black
    }

    return theme.DefaultTheme().Color(name, variant)
}

您将看到此处的最后一行引用了要查找的标准值。这使我们能够提供自定义值,但在我们不想提供自己的值时回退到标准主题。theme.DefaultTheme()

当然,颜色比资源更简单,我们看看自定义图标。

覆盖默认图标

图标(和字体)用作值,而不是简单的类型,如(用于大小)或颜色。我们可以使用 构建自己的资源,或者您可以传入使用资源嵌入创建的值。fyne.Resource``int``color.Color``fyne.NewStaticResource

func (m myTheme) Icon(name fyne.ThemeIconName) fyne.Resource {
    if name == theme.IconNameHome {
        fyne.NewStaticResource("myHome", homeBytes)
    }

    return theme.DefaultTheme().Icon(name)
}

如上所述,如果我们不想提供特定的覆盖,我们将返回默认主题图标。

加载主题

在我们加载主题之前,您还需要实现 and 方法。如果您愿意使用默认值,则可以使用这些空实现。Size``Font

func (m myTheme) Font(style fyne.TextStyle) fyne.Resource {
    return theme.DefaultTheme().Font(style)
}

func (m myTheme) Size(name fyne.ThemeSizeName) float32 {
    return theme.DefaultTheme().Size(name)
}

若要为应用设置主题,你需要添加以下代码行:

app.Settings().SetTheme(&myTheme{})

通过这些更改,您可以应用自己的样式,进行小的调整或提供完全自定义的外观应用程序!

结构布局

几何学

  • 位置
  • 像素大小

编辑我Fyne 应用基于每个窗口 1 个画布。每个画布都有一个根 CanvasObject,它可以是单个小部件,也可以是许多子对象的容器,其大小和位置由布局控制。

位置


每个画布的原点位于左上角 (0, 0), UI 的每个元素都可以根据输出设备进行缩放,因此 API 不描述像素或精确度量值。位置(10,10)可能是从原点向右向下10像素,例如,120DPI显示器,但在HiDPI(或"视网膜")显示器上,这可能更接近20像素。

CanvasObject 引用的每个位置都相对于其父级。这对于布局算法很重要,但对于处理程序等情况下的开发人员也很重要。此处的 X/Y 坐标将从按钮的左上角开始计算,而不是从整个画布开始计算。这旨在允许代码尽可能独立。Tappable.Tapped(PointEvent)

像素大小


与其他基于矢量的GUI库一样,Fyne坐标需要基于一些基线监视器分辨率。所有缩放都与此值相关。对于fyne,分辨率为120DPI。这意味着,当显示器为 120DPI 且比例值全部设置为 1 时,中引用的大小将为 1=1px。如上所述,对于HiDPI屏幕,实际DPI可能更接近240,在移动设备上甚至可能是360或更高。为了管理处理这种复杂性,工具包在内部管理扩展,以便您的应用程序始终看起来合适。如果用户将比例设置为较小,则其应用将始终具有比正常字体、按钮等更小的字体、按钮等,当他们指定较大时,你的应用将按比例放大以适应。fyne.Size

与材质设计相比,我们可以看到它们的基线DPI是160,尽管数学是相似的,但实际数字会有所不同。这意味着Fyne中与设备无关的尺寸使用较小的数字来表示相同的物理尺寸。例如,在Fyne中很高的图标将在标准材料设计(例如Android)应用程序中调整大小。这在构建应用程序时无关紧要,但在与具有材质设计的设计人员或专家合作时可能很重要。18``24

像素大小是否开始加载位图图像时,像素大小将变得重要。通常这些缩放适当,但是如果您指定,则由于像素密度,不同设备上的实际图像大小将有所不同。通常,此功能将在容器内使用。Fyne还定义了一个基元,它将以输出设备的像素密度精确地绘制像素。这使你的代码能够以尽可能高的输出分辨率绘制,而无需知道正在运行的设备的详细信息。如果由于某种原因,您需要"像素完美"定位,则需要乘以.FillMode=fyne.FillOriginal``Scroll``canvas.Raster``CanvasObject.Size()``Canvas.Scale()

缩放

编辑我Fyne完全使用矢量图形构建,这意味着使用Fyne编写的应用程序可以很好地扩展到任何大小(而不仅仅是整数增量)。这对移动设备和高端计算机上高密度显示器的日益普及是一个很大的好处。默认比例值的计算与您的操作系统相匹配 - 在某些系统上这是用户配置,而在其他系统上,这是屏幕像素密度(DPI - 每英寸点数) 。如果将Fyne窗口移动到另一个屏幕,它将重新缩放并相应地调整窗口大小!我们称之为"自动缩放",它旨在使应用程序用户界面在更改监视器时保持相同的大小。

您可以使用应用程序或使用环境变量设置特定比例来调整应用程序的大小。这些值可以使内容大于或小于系统设置,使用"1.5"将使内容大50%,或者设置0.8会使内容变小20%。fyne_settings``FYNE_SCALE

你好正常大小

标准尺寸
你好小尺寸

FYNE_SCALE=0.5
你好大尺寸

FYNE_SCALE=2.5

部件

  • 行为 API
  • 内容填充

编辑我Fyne工具包中的小部件旨在实现干净愉快的用户交互,遵循标准主题,并支持快速应用程序开发,可靠的测试和易于维护。有各种设计考虑因素可以促进这种雄心壮志,我们将在此页面中进行探讨。

行为 API


关于标准小部件,您会注意到的一件事是,API完全是关于行为和状态的 - 但控制元素实际外观的很少。这是设计使然。它使我们的代码和应用程序开发人员的代码能够专注于小部件的行为,以便它的呈现过程可以留给其他代码。这使得测试变得更加容易,事实上,完整的应用程序可以通过内存中的单元测试运行,而不必渲染应用程序。

您可以扩展现有微件以添加新行为,而无需担心其呈现方式。也可以编写自己的组件,应用程序不限于使用提供的小部件集。在构建自己的小部件时,您会注意到渲染细节与状态完全分开 - 这是上述设计的一部分。A(呈现的代码)通常包含对它将呈现以访问状态或其他信息的小部件的引用。当小部件状态发生更改时,将调用 - 然后将要求渲染器刷新,并应更新显示以反映新状态。建议使用自定义小部件以使用当前窗口小部件,但可以选择在需要的位置指定自己的大小,颜色和图标。WidgetRenderer``Widget``Refresh()``Theme

内容填充


标准微件使用主题指定的填充,以在其图形组件周围留出适当的空间。在该版本中,大多数小部件的高度和基线都进行了标准化,以确保标准布局在默认情况下保持良好对齐。如果要构建自定义小部件,建议遵循以下准则。v2.0.0

的值用于布局中,以空间化容器的元素,它在应用程序的各个部分周围创建标准化空间。但是,某些微件具有应从范围边缘嵌入的内容。考虑 ,它有一个背景和一个延伸到边缘的下划线,但它的内容应该是嵌入的。因此,我们标准化了用于嵌入的间距量,以便对齐匹配。theme.Padding()``Entry

小部件的标准插图或内容填充当前定义为 。填充的标准值为 ,这意味着标准插图为 。您可以看到(文本)内容的插入方式,以便它们的内容在彼此相邻放置时水平和垂直对齐。2*theme.Padding()``4``8``Label``Entry

image

建议自定义小部件包含相似的尺寸,以便它们与标准小部件很好地匹配。

常见问题

布局和小部件大小

  • 移动和调整大小
  • 容器和布局

编辑我介绍

移动和调整大小

问:如何将我的微件移动到其他位置或调整其大小?

一个: Fyne 应用中元素的位置和大小由元素所在的容器布局控制。如果 UI 的元素太小,请考虑使用其他布局或容器。

一个新的将扩展传递给的任何元素以填充其大小。每次向其中添加容器时,它都会根据布局划分可用空间。布局喜欢并将内容缩小到一个维度或另一个维度以打包内容。布局喜欢或将扩展内容以填充空间。通过编写自定义布局,您可以完全控制容器中的项目。Window``SetContent()``HBox``VBox``MinSize()``Max``Border

问:为什么我的图片这么小?

一个: 使用完全可扩展的用户界面工具包(如 Fyne)的困难之一是坐标系与设备无关。这允许应用以正确的分辨率或像素密度进行绘制,以根据连接的硬件获得最佳效果。对于基于像素的图像,这意味着它们的大小可能会根据编译时未知的细节而变化。

由于这种复杂性,使用或类似调用加载的图像将没有大小集,导致它非常小或默认情况下显示为隐藏。当放置在适当的布局中时,图像将根据其属性拉伸。如果希望始终将映像设置为特定大小(或更大),则可以调用并为映像指定与设备无关的大小。canvas.NewImageFromFile()``FillMode``Image.SetMinSize()

容器和布局

问:如何手动控制元素的位置

一个:在某些情况下,您可能希望完全控制容器中元素的位置和大小。为此,请创建一个没有布局的容器。该函数将创建一个用于手动定位的容器 - 您应该向该构造函数传递要在此容器中管理的图形元素的列表。container.NewWithoutLayout()

设置完成后,您可以使用并在每个元素上根据需要放置它。执行此操作时,请注意它不会随着可用空间的变化而调整 - 并且它也没有明确的最小大小。要添加这些功能中的任何一个,您应该将手动定位替换为自定义布局。Move()``Resize()

主题和定制

  • 客制化
  • 主题接口

编辑我在本页中,我们回答了有关Fyne主题和小部件设计的一些常见问题。

定制化

问:如何更改"标签"构件的文本颜色?

一个:所有标准小部件都使用当前定义来设置颜色,字体和大小。若要对应用程序进行更改,请考虑使用自定义主题。Theme

如果您的应用程序需要不同颜色的文本,则可以改用该类型。这允许直接设置文本的颜色和大小。执行此操作时要小心,因为用户可以在浅色或深色主题变体之间进行选择,因此您应该同时使用两者进行测试。canvas.Text

问:如何从我的"条目"小部件中删除背景颜色?

一个:输入背景由主题颜色设置。您可以将其更改为删除所有输入背景框。无法编辑单个输入元素的样式 - 主题 API 旨在提供可自定义但一致的设计。InputBackground``color.Transparent

主题接口

问:如何使用在 v2.0.0 之前编写的自定义主题?

一个:随着时间的推移,您应该考虑更新以使用新的主题 API。但是,可以使用包含的简单适配器,以允许在过渡期间使用旧主题。您将找到可以将旧主题实例适应新 API 的函数。theme.FromLegacy

myTheme := &myOldThemeType{}
updated := theme.FromLegacy(myTheme)
app.Settings().SetTheme(updated)

在此模式下使用主题时不会降低性能,但在将来的版本中,此 API 将被删除。

你可能感兴趣的:(go fyne开发桌面应用)