使用Fyne工具包构建跨平台应用是非常简单的,在此之前我们需要做一些准备功能做,比如安装一些gcc基础图形依赖库,还有go语言本身的运行开发环境都是必要的。
在此之前我们希望你是go语言的已入门用户,掌握go的协程,管道,以及大多数语法,这样会让你学习起来更轻松
下面是实现的代码,有一部分还没有实现,如果需要可以继续研究.
package main
import (
"image/color"
"math"
"strconv"
"strings"
"time"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget"
"fyne.io/fyne/v2/driver/desktop"
)
// 自定义按钮样式
type calculatorButton struct {
widget.Button
bgColor color.Color
pressScale float32 // 按下时的缩放比例
isPressed bool // 按钮是否被按下
animating bool // 是否正在动画中
}
func newCalculatorButton(label string, bgColor color.Color, tapped func()) *calculatorButton {
button := &calculatorButton{
bgColor: bgColor,
pressScale: 0.95, // 按下时缩小到95%
isPressed: false,
animating: false,
}
button.ExtendBaseWidget(button)
button.Text = label
button.OnTapped = tapped
return button
}
// 添加鼠标按下事件处理
func (b *calculatorButton) MouseDown(*desktop.MouseEvent) {
b.isPressed = true
b.animating = true
b.Refresh()
}
// 添加鼠标释放事件处理
func (b *calculatorButton) MouseUp(*desktop.MouseEvent) {
b.isPressed = false
b.animating = true
b.Refresh()
}
func (b *calculatorButton) CreateRenderer() fyne.WidgetRenderer {
background := &canvas.Circle{
FillColor: b.bgColor,
}
text := canvas.NewText(b.Text, color.White)
text.TextSize = 20
text.Alignment = fyne.TextAlignCenter
renderer := &calculatorButtonRenderer{
button: b,
background: background,
text: text,
objects: []fyne.CanvasObject{background, text},
}
// 创建动画
renderer.animation = fyne.NewAnimation(
time.Millisecond*100, // 动画持续100毫秒
func(progress float32) {
if b.isPressed {
// 从1.0缩pressScale
scale := 1.0 - (1.0-b.pressScale)*progress
renderer.updateScale(scale)
} else {
// 从pressScale恢复到1.0
scale := b.pressScale + (1.0-b.pressScale)*progress
renderer.updateScale(scale)
}
renderer.Refresh()
},
)
return renderer
}
type calculatorButtonRenderer struct {
button *calculatorButton
background *canvas.Circle
text *canvas.Text
objects []fyne.CanvasObject
animation *fyne.Animation // 添加动画对象
}
// 添加缩放更新函数
func (r *calculatorButtonRenderer) updateScale(scale float32) {
baseSize := fyne.NewSize(60, 45) // 使用固定的基础大小
scaledSize := fyne.NewSize(
float32(baseSize.Width)*scale,
float32(baseSize.Height)*scale,
)
// 计算偏移以保持按钮居中
offset := fyne.NewPos(
(float32(baseSize.Width)-scaledSize.Width)/2,
(float32(baseSize.Height)-scaledSize.Height)/2,
)
r.background.Resize(scaledSize)
r.background.Move(offset)
// 更新文本位置
textSize := r.text.MinSize()
r.text.Resize(textSize)
r.text.Move(fyne.NewPos(
(baseSize.Width-textSize.Width)/2,
(baseSize.Height-textSize.Height)/2,
))
}
// 修改 Refresh 函数
func (r *calculatorButtonRenderer) Refresh() {
if r.button.animating {
r.animation.Start()
r.button.animating = false
}
r.background.FillColor = r.button.bgColor
r.background.Refresh()
r.text.Text = r.button.Text
r.text.Refresh()
}
// 修改 Destroy 函数
func (r *calculatorButtonRenderer) Destroy() {
r.animation.Stop()
}
// 添加Layout方法
func (r *calculatorButtonRenderer) Layout(size fyne.Size) {
// 设置背景大小和位置
r.background.Resize(size)
// 文本居中
textSize := r.text.MinSize()
r.text.Resize(textSize)
r.text.Move(fyne.NewPos(
(size.Width-textSize.Width)/2,
(size.Height-textSize.Height)/2,
))
}
// 添加MinSize方法
func (r *calculatorButtonRenderer) MinSize() fyne.Size {
return fyne.NewSize(60, 45)
}
// 添加Objects方法
func (r *calculatorButtonRenderer) Objects() []fyne.CanvasObject {
return r.objects
}
// 添加自定义显示组件
type calculatorDisplay struct {
widget.BaseWidget
text string
process string // 添加计算过程
}
func newCalculatorDisplay() *calculatorDisplay {
display := &calculatorDisplay{text: "0", process: ""}
display.ExtendBaseWidget(display)
return display
}
func (d *calculatorDisplay) CreateRenderer() fyne.WidgetRenderer {
result := canvas.NewText(d.text, color.White)
result.TextSize = 32
result.Alignment = fyne.TextAlignTrailing
process := canvas.NewText(d.process, color.Gray{Y: 180}) // 灰色显示计算过程
process.TextSize = 20
process.Alignment = fyne.TextAlignTrailing
return &calculatorDisplayRenderer{
display: d,
result: result,
process: process,
objects: []fyne.CanvasObject{result, process},
}
}
type calculatorDisplayRenderer struct {
display *calculatorDisplay
result *canvas.Text
process *canvas.Text
objects []fyne.CanvasObject
}
func (r *calculatorDisplayRenderer) MinSize() fyne.Size {
return fyne.NewSize(280, 70) // 增加高度以容纳两行文本
}
func (r *calculatorDisplayRenderer) Layout(size fyne.Size) {
processHeight := float32(25)
r.process.Resize(fyne.NewSize(size.Width, processHeight))
r.process.Move(fyne.NewPos(0, 0))
r.result.Resize(fyne.NewSize(size.Width, size.Height-processHeight))
r.result.Move(fyne.NewPos(0, processHeight))
}
func (r *calculatorDisplayRenderer) Refresh() {
r.result.Text = r.display.text
r.process.Text = r.display.process
r.result.Refresh()
r.process.Refresh()
}
func (r *calculatorDisplayRenderer) Objects() []fyne.CanvasObject {
return r.objects
}
func (r *calculatorDisplayRenderer) Destroy() {}
func main() {
a := app.New()
w := a.NewWindow("计算器")
w.SetFixedSize(true)
// 设置深色主题
a.Settings().SetTheme(theme.DarkTheme())
// 声明计算器状态变量
var (
firstNumber float64
operation string
newNumber = true
showingResult = false // 添加这个变量
)
// 使用新的显示组件替代原来的Entry
display := newCalculatorDisplay()
// 定义颜色
grayColor := &color.NRGBA{R: 51, G: 51, B: 51, A: 255}
orangeColor := &color.NRGBA{R: 255, G: 159, B: 10, A: 255}
lightGrayColor := &color.NRGBA{R: 165, G: 165, B: 165, A: 255}
darkerGrayColor := &color.NRGBA{R: 40, G: 40, B: 40, A: 255}
// 数字按钮
numbers := map[string]*calculatorButton{}
for i := 0; i <= 9; i++ {
number := i
numbers[strconv.Itoa(i)] = newCalculatorButton(strconv.Itoa(i), grayColor, func() {
if newNumber || showingResult {
display.text = ""
if showingResult {
display.process = "" // 清除之前的计算过程
}
newNumber = false
showingResult = false
}
display.text += strconv.Itoa(number)
display.Refresh()
})
}
// 运算符按钮
add := newCalculatorButton("+", orangeColor, func() {
if showingResult {
firstNumber, _ = strconv.ParseFloat(display.text, 64)
display.process = display.text + " +" // 开始新的计算过程
showingResult = false
} else {
if !newNumber {
secondNumber, _ := strconv.ParseFloat(display.text, 64)
firstNumber = calculateResult(firstNumber, secondNumber, operation)
display.text = strconv.FormatFloat(firstNumber, 'f', -1, 64)
display.process = display.text + " +" // 更新计算过程
} else {
firstNumber, _ = strconv.ParseFloat(display.text, 64)
display.process = display.text + " +" // 显示第一个数字和运算符
}
}
operation = "+"
newNumber = true
display.Refresh()
})
subtract := newCalculatorButton("-", orangeColor, func() {
if showingResult {
firstNumber, _ = strconv.ParseFloat(display.text, 64)
showingResult = false
} else {
if !newNumber {
secondNumber, _ := strconv.ParseFloat(display.text, 64)
firstNumber = calculateResult(firstNumber, secondNumber, operation)
display.text = strconv.FormatFloat(firstNumber, 'f', -1, 64)
} else {
firstNumber, _ = strconv.ParseFloat(display.text, 64)
}
}
operation = "-"
newNumber = true
display.Refresh()
})
multiply := newCalculatorButton("×", orangeColor, func() {
if showingResult {
firstNumber, _ = strconv.ParseFloat(display.text, 64)
showingResult = false
} else {
if !newNumber {
secondNumber, _ := strconv.ParseFloat(display.text, 64)
firstNumber = calculateResult(firstNumber, secondNumber, operation)
display.text = strconv.FormatFloat(firstNumber, 'f', -1, 64)
} else {
firstNumber, _ = strconv.ParseFloat(display.text, 64)
}
}
operation = "*"
newNumber = true
display.Refresh()
})
divide := newCalculatorButton("÷", orangeColor, func() {
if showingResult {
firstNumber, _ = strconv.ParseFloat(display.text, 64)
showingResult = false
} else {
if !newNumber {
secondNumber, _ := strconv.ParseFloat(display.text, 64)
firstNumber = calculateResult(firstNumber, secondNumber, operation)
display.text = strconv.FormatFloat(firstNumber, 'f', -1, 64)
} else {
firstNumber, _ = strconv.ParseFloat(display.text, 64)
}
}
operation = "/"
newNumber = true
display.Refresh()
})
equals := newCalculatorButton("=", orangeColor, func() {
if !newNumber {
secondNumber, _ := strconv.ParseFloat(display.text, 64)
display.process += " " + display.text + " =" // 完成计算过程
result := calculateResult(firstNumber, secondNumber, operation)
display.text = strconv.FormatFloat(result, 'f', -1, 64)
firstNumber = result
showingResult = true
display.Refresh()
}
})
// 额外的功能按钮
ac := newCalculatorButton("AC", lightGrayColor, func() {
display.text = "0"
display.process = "" // 清除计算过程
firstNumber = 0
operation = ""
newNumber = true
showingResult = false
display.Refresh()
})
plusMinus := newCalculatorButton("+/-", lightGrayColor, func() {
if current, err := strconv.ParseFloat(display.text, 64); err == nil {
display.text = strconv.FormatFloat(-current, 'f', -1, 64)
display.Refresh()
}
})
percent := newCalculatorButton("%", lightGrayColor, func() {
if current, err := strconv.ParseFloat(display.text, 64); err == nil {
display.text = strconv.FormatFloat(current/100, 'f', -1, 64)
display.Refresh()
}
})
decimal := newCalculatorButton(".", grayColor, func() {
if !strings.Contains(display.text, ".") {
display.text += "."
display.Refresh()
}
})
// 科学计算器的额外功能按钮
mc := newCalculatorButton("mc", darkerGrayColor, func() {
// 内存清除功能
})
mPlus := newCalculatorButton("m+", darkerGrayColor, func() {
// 内存加功能
})
mMinus := newCalculatorButton("m-", darkerGrayColor, func() {
// 内存减功能
})
mr := newCalculatorButton("mr", darkerGrayColor, func() {
// 内存调用功能
})
// 科学运算按钮
sin := newCalculatorButton("sin", darkerGrayColor, func() {
if val, err := strconv.ParseFloat(display.text, 64); err == nil {
result := math.Sin(val * math.Pi / 180) // 转换为弧度
display.text = strconv.FormatFloat(result, 'f', -1, 64)
display.Refresh()
}
})
cos := newCalculatorButton("cos", darkerGrayColor, func() {
if val, err := strconv.ParseFloat(display.text, 64); err == nil {
result := math.Cos(val * math.Pi / 180)
display.text = strconv.FormatFloat(result, 'f', -1, 64)
display.Refresh()
}
})
tan := newCalculatorButton("tan", darkerGrayColor, func() {
if val, err := strconv.ParseFloat(display.text, 64); err == nil {
result := math.Tan(val * math.Pi / 180)
display.text = strconv.FormatFloat(result, 'f', -1, 64)
display.Refresh()
}
})
ln := newCalculatorButton("ln", darkerGrayColor, func() {
if val, err := strconv.ParseFloat(display.text, 64); err == nil {
result := math.Log(val)
display.text = strconv.FormatFloat(result, 'f', -1, 64)
display.Refresh()
}
})
log := newCalculatorButton("log₁₀", darkerGrayColor, func() {
if val, err := strconv.ParseFloat(display.text, 64); err == nil {
result := math.Log10(val)
display.text = strconv.FormatFloat(result, 'f', -1, 64)
display.Refresh()
}
})
sqrt := newCalculatorButton("√x", darkerGrayColor, func() {
if val, err := strconv.ParseFloat(display.text, 64); err == nil {
result := math.Sqrt(val)
display.text = strconv.FormatFloat(result, 'f', -1, 64)
display.Refresh()
}
})
square := newCalculatorButton("x²", darkerGrayColor, func() {
if val, err := strconv.ParseFloat(display.text, 64); err == nil {
result := math.Pow(val, 2)
display.text = strconv.FormatFloat(result, 'f', -1, 64)
display.Refresh()
}
})
pi := newCalculatorButton("π", darkerGrayColor, func() {
display.text = strconv.FormatFloat(math.Pi, 'f', -1, 64)
display.Refresh()
})
e := newCalculatorButton("e", darkerGrayColor, func() {
display.text = strconv.FormatFloat(math.E, 'f', -1, 64)
display.Refresh()
})
factorial := newCalculatorButton("x!", darkerGrayColor, func() {
if val, err := strconv.ParseFloat(display.text, 64); err == nil {
n := int(val)
result := 1.0
for i := 2; i <= n; i++ {
result *= float64(i)
}
display.text = strconv.FormatFloat(result, 'f', -1, 64)
display.Refresh()
}
})
// 添加模式切换状态
var isScientificMode = false
// 声明updateLayout变量(在最前面)
var updateLayout func()
// 创建主容器
mainContainer := container.NewVBox()
// 创建显示区域容器
displayContainer := container.NewPadded(display)
// 创建基本计算器布局
createBasicLayout := func() *fyne.Container {
return container.NewGridWithColumns(4,
ac, plusMinus, percent, divide,
numbers["7"], numbers["8"], numbers["9"], multiply,
numbers["4"], numbers["5"], numbers["6"], subtract,
numbers["1"], numbers["2"], numbers["3"], add,
numbers["0"], decimal, equals,
)
}
// 创建科学计算器布局
createScientificLayout := func() *fyne.Container {
return container.NewGridWithColumns(10,
// 第一行
newCalculatorButton("(", darkerGrayColor, nil),
newCalculatorButton(")", darkerGrayColor, nil),
mc, mPlus, mMinus, mr,
ac, plusMinus, percent, divide,
// 第二行
newCalculatorButton("2ⁿᵈ", darkerGrayColor, nil),
square,
newCalculatorButton("x³", darkerGrayColor, nil),
newCalculatorButton("xʸ", darkerGrayColor, nil),
e, // 使用 e 按钮
newCalculatorButton("10ˣ", darkerGrayColor, nil),
numbers["7"], numbers["8"], numbers["9"], multiply,
// 第三行
factorial, // 使用阶乘按钮
sqrt, // 使用平方根按钮
pi, // 使用 π 按钮
ln, // 使用自然对数按钮
log, // 使用常用对数按钮
newCalculatorButton("EE", darkerGrayColor, nil),
numbers["4"], numbers["5"], numbers["6"], subtract,
// 第四行
sin, // 使用正弦按钮
cos, // 使用余弦按钮
tan, // 使用正切按钮
newCalculatorButton("sinh", darkerGrayColor, nil),
newCalculatorButton("cosh", darkerGrayColor, nil),
newCalculatorButton("tanh", darkerGrayColor, nil),
numbers["1"], numbers["2"], numbers["3"], add,
// 第五行
newCalculatorButton("Rad", darkerGrayColor, nil),
newCalculatorButton("sinh⁻¹", darkerGrayColor, nil),
newCalculatorButton("cosh⁻¹", darkerGrayColor, nil),
newCalculatorButton("tanh⁻¹", darkerGrayColor, nil),
newCalculatorButton("Rand", darkerGrayColor, nil),
newCalculatorButton("EE", darkerGrayColor, nil),
numbers["0"], decimal, equals,
)
}
// 创建顶部工具栏(不包含modeSwitch)
toolbar := container.NewHBox(
widget.NewSeparator(),
)
// 定义updateLayout函数
updateLayout = func() {
var layout fyne.CanvasObject
if isScientificMode {
layout = createScientificLayout()
} else {
layout = createBasicLayout()
}
mainContainer.Objects = []fyne.CanvasObject{
toolbar,
displayContainer,
layout,
}
mainContainer.Refresh()
}
// 现在定义modeSwitch(在updateLayout之后)
modeSwitch := newCalculatorButton("⚙", lightGrayColor, func() {
isScientificMode = !isScientificMode
startSize := w.Canvas().Size()
var targetWidth float32
if isScientificMode {
targetWidth = 600
} else {
targetWidth = 300
}
targetSize := fyne.NewSize(targetWidth, 450)
animation := fyne.NewAnimation(
time.Millisecond*300,
func(progress float32) {
width := startSize.Width + (targetSize.Width-startSize.Width)*progress
height := startSize.Height + (targetSize.Height-startSize.Height)*progress
w.Resize(fyne.NewSize(width, height))
},
)
animation.Start()
updateLayout()
})
// 更新toolbar,添加modeSwitch
toolbar.Objects = append([]fyne.CanvasObject{modeSwitch}, toolbar.Objects...)
// 初始化布局
updateLayout()
// 直接使用mainContainer作为窗口内容
w.SetContent(mainContainer)
w.Resize(fyne.NewSize(300, 450))
w.ShowAndRun()
}
// 添加一个辅助函数来计算结果
func calculateResult(first, second float64, op string) float64 {
var result float64
switch op {
case "+":
result = first + second
case "-":
result = first - second
case "*":
result = first * second
case "/":
if second != 0 {
result = first / second
}
}
return result
}
go.mod文件
module calc
go 1.23.1
require fyne.io/fyne/v2 v2.5.2
require (
fyne.io/systray v1.11.0 // indirect
github.com/BurntSushi/toml v1.4.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fredbi/uri v1.1.0 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/fyne-io/gl-js v0.0.0-20220119005834-d2da28d9ccfe // indirect
github.com/fyne-io/glfw-js v0.0.0-20240101223322-6e1efdc71b7a // indirect
github.com/fyne-io/image v0.0.0-20220602074514-4956b0afb3d2 // indirect
github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6 // indirect
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a // indirect
github.com/go-text/render v0.2.0 // indirect
github.com/go-text/typesetting v0.2.0 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/gopherjs/gopherjs v1.17.2 // indirect
github.com/jeandeaual/go-locale v0.0.0-20240223122105-ce5225dcaa49 // indirect
github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e // indirect
github.com/nicksnyder/go-i18n/v2 v2.4.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rymdport/portal v0.2.6 // indirect
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c // indirect
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef // indirect
github.com/stretchr/testify v1.8.4 // indirect
github.com/yuin/goldmark v1.7.1 // indirect
golang.org/x/image v0.18.0 // indirect
golang.org/x/mobile v0.0.0-20231127183840-76ac6878050a // indirect
golang.org/x/net v0.25.0 // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/text v0.16.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)