画布(Canvas)和画布对象(CanvasObject)
**画布就是一块用来显示应用内容的屏幕区域。**每一个应用窗口都有一个可以通过Window.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"
"fyne.io/fyne/v2/widget"
)
func main() {
myApp := app.New()
myWindow := myApp.NewWindow("Canvas")
myWindow.SetContent(widget.NewLabel("label1"))
myCnavas := myWindow.Canvas()
blue := color.NRGBA{R: 0, G: 0, B: 180, A: 255}
rect := canvas.NewRectangle(blue)
myCnavas.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()
}
这个例子做了如下几个事情:
- 创建一个fyne应用对象。
- 为应用打开一个窗口。
- 为窗口加上“Label1”的标签。
- 为窗口创建一个画布对象。
- 创建一个蓝色的长方体对象
- 将蓝色长方体填入画布中。
- 创建一条协程,并在其中执行以下操作:
7.1 等待1秒钟
7.2 将长方体的填充颜色用绿色取代。
7.3 刷新长方体的显示内容。 - 将应用窗口的大小调整为100*100的大小。
- 启动fyne应用的显示。
除了上面的例子,我们也可以用不同的方法绘制其它元素,例如圆和文本:
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
是一种结合了互动要素的特殊的画布对象。Widget中的逻辑和显示(也叫做WidgetRenderer)是分离的。
由于Widget是画布对象的一种,因此我们可以把窗口的内容设置为一个widget。示例如下:
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()
}
运行效果如下:
容器(Container)和布局(Layouts)
上个小节我们学习了如何为一个Canvas设置Canvas Object,但是这种用法在实操中并不常见,因为只显示一个可视元素并不是很有用。我们通常使用Container
来处理多元素同时显示的情况。
fyne.Container
也是fyne.CanvasObject
的一种,因此我们可以将其设置为fyne.Canvas
的内容。下面我们通过container.NewWithoutLayout()
把3个文本对象放置在一个Container
中为例来说明:
package main
import (
"image/color"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/container"
)
func main() {
myApp := app.New()
myWindow := myApp.NewWindow("Container")
green := color.NRGBA{R: 0, G: 164, B: 0, A: 255}
text1 := canvas.NewText("Hello", green)
text2 := canvas.NewText("there", green)
text2.Move(fyne.NewPos(60, 60))
content := container.NewWithoutLayout(text1, text2)
// content := container.New(layout.NewGridLayout(2), text1, text2)
myWindow.SetContent(content)
myWindow.ShowAndRun()
}
运行结果如下:
fyne.Layout
可以提供一种在容器中组织元素显示位置的方法。放开上面示例代码中被注释掉的代码,可以通过有两行的网格布局显示容器中的内容。
运行效果如下:
注意:调用layout命令后,会覆盖手工布局的配置。
Widget list
fyne提供了3种类型的小组件,其中标准组件和收集组件在widget包中实现,容器组件是在容器包中实现的。
package | type | name |
Widget | standard | Accordion |
Button | ||
Card | ||
Check | ||
Entry | ||
FileIcon | ||
Form | ||
Hyperlink | ||
Icon | ||
Label | ||
Progress bar | ||
RadioGroup | ||
Select | ||
SelectEntry | ||
Separator | ||
Slider | ||
TextGrid | ||
Toolbar | ||
Collection | List | |
Table | ||
Tree | ||
Container | container | AppTabs |
Split | ||
Scroll |
Layout List
布局分为两种:标准布局和联合布局。
type | name |
Standard Layouts | Horizontal Box (HBox) |
Vertical Box (VBox) | |
Center | |
Form | |
Grid | |
GridWrap | |
Border | |
Max | |
Padded | |
Combining Layouts | multiple layouts |
Dialog List
type | name |
Standard Dialogs | Color |
Confirm | |
FileOpen | |
Form | |
Information | |
Custom |
Shortcuts
快捷键是可以由键盘按键组合或者上下文菜单触发的通用任务。快捷键很像键盘事件,可以和焦点元素绑定或者注册到画布上以在窗口上持续工作。
注册至画布
有许多关联至标准键盘快捷键和右键菜单定义的快捷键(例如fyne.ShortcutCopy),快捷键在增加之前首先需要进行定义。在大多数情况下是由键盘触发的快捷键,它是desktop
的扩展。这一点可以通过desktop.CustomShortcut
实现,例如如果你想使用tab键和control修饰符,可以通过如下代码实现:
ctrlTab := desktop.CustomShortcut{KeyName: fyne.KeyTab, Modifier: desktop.ControlModifier}
注意快捷键可以被重用,所以你也可以把它附加至菜单或者其它的项目。对于上面的例子,如果我们想让这个快捷键一直生效,我们可以把它注册至应用窗口的画布:
ctrlTab := desktop.CustomShortcut{KeyName: fyne.KeyTab, Modifier: desktop.ControlModifier}
w.Canvas().AddShortcut(&ctrlTab, func(shortcut fyne.Shortcut) {
log.Println("We tapped Ctrl+Tab")
})
如你所见,通过这种方式注册快捷键需要两步:传递快捷键的定义和对应的回调函数,如果用户键入了快捷键那么回调函数会被执行并输出打印信息。
为入口设置快捷键
在一些情况下,需要为一些项目注册只在其获取焦点的时候生效的快捷键。这种方法适用于任何可以获取焦点的widget,并且通过扩展widget并增加一个TypedShortcut
进行管理。这更像是增加密钥句柄,除了输入的值是yne.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
管理者是如何实现的。从这个函数中你应该查看快捷方式是否是之前使用的自定义类型。如果是标准快捷键,通过调用原始快捷键管理器(如果widget有)也是一个好方法。这些检查完成后,你可以拿快捷键和所有其他各种类型进行比较。
Preferences
API的使用
存储用户配置和数据对所有应用开发者来说都是常见的任务,但是在不同平台上分别实现很耗时且乏味。为了使开发更简单,fyne提供了在文件系统上以干净且容易理解的方式存储数据的API,该API接管了这些处理中最复杂的部分。
从API的设置开始,它是Preferences
包的一部分,存储和加载功能适用于Bool,Float,Int和String类型。它们当中每一个由3个不同的功能组成,一个用于加载,一个带fallback value and lastly的加载,一个用于存储数据。以下是通过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()
方法调用。注意必须创建带unique id的应用。这意味着应用需要通过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")
[...]
为了展示这一点,我们需要创建一个简单的总是会在一定时间内自动关闭的APP,这个超时时间可以由用户设置并可以被下个要启动的应用继承。
创建一个名为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)
})
现在我们要获取设置的数值,如果不存在,我们想要有一个fallback来将超时时间设置为尽可能小的时间来节省用户的等待时间。这可以通过将选择的timeoutSelector
的值设置为加载的值或者在发生回退时设置为回退的值来实现。这样被选择的widget中的代码将会按照指定的值来运行。
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 v2.0.0开始引入,以便方便的关联多个widget至一个需要不定时更新的数据源。data/binding
包中有很多有用的可以处理大部分标准类型的封装。
支持绑定的widget一般都有...WithData
的构建方法,你也可以通过调用Bind()
和Unbind()
来管理已创建widget的绑定数据。以下是一个绑定至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("Bingding")
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()
}
运行结果如下:
编译选项
构建标签
Fyne一般通过选择驱动和配置来为你的应用真对目标系统进行配置。以下是支持的可以在开发过程中提供帮助的标签。例如如果你想模拟一个运行于桌面电脑上的移动应用,可以通过如下选项实现:
go run -tags mobile main.go
Tag | Description |
gles | 强制使用嵌入式OpenGL取代全功能OpenGL。这通常受目标设备控制且一般不需要。 |
hints | 显示开发者建议以提升或优化。配置```hints``会在应用不遵守材料设计或其他建议时产生记录 |
mobile | 这个标签会让应用在模拟的手机窗口中运行。在你想预览你的手机应用的时候很有用。 |
no_native_menus | 这是macOS的专用标签,表示应用不应该使用macOS的原生菜单。而是在应用窗口中直接显示。在使用macOS开发windows或Linux应用是很有用。 |