gui-gio:定时器的demo3

使用go-gio库示例的定时器详解

请添加图片描述

包含的控件和功能
1、进度条的显示和控制
2、滑块的显示和控制
3、垂直布局的设计
4、ui界面的强制刷新

package main

import (
	"context"
	"fmt"
	"log"
	"os"
	"sync"
	"time"

	"gioui.org/app"             // app contains Window handling.
	"gioui.org/font/gofont"     // gofont is used for loading the default font.
	"gioui.org/io/key"          // key is used for keyboard events.
	"gioui.org/io/system"       // system is used for system events (e.g. closing the window).
	"gioui.org/layout"          // layout is used for layouting widgets.
	"gioui.org/op"              // op is used for recording different operations.
	"gioui.org/unit"            // unit is used to define pixel-independent sizes
	"gioui.org/widget"          // widget contains state handling for widgets.
	"gioui.org/widget/material" // material contains material design widgets.
)

// Timer 计时器实现
type Timer struct {
	// 更改通知通道
	Updated  chan struct{}
	mu       sync.Mutex
	start    time.Time     // 计时器的启动时间
	now      time.Time     // 计时器的现在时间
	duration time.Duration // 时间差
}

// NewTimer 创建一个计时器
func NewTimer(initialDuration time.Duration) *Timer {
	return &Timer{
		Updated:  make(chan struct{}),
		duration: initialDuration,
	}
}

// Start 开启一个计时器、并返回他的取消方法
func (t *Timer) Start() context.CancelFunc {
	// 初始化定时器状态
	now := time.Now()
	t.now = now
	t.start = now

	// 结束通道
	done := make(chan struct{})
	go t.run(done)
	return func() { close(done) }
}

// 计时器主循环
func (t *Timer) run(done chan struct{}) {
	// 50毫秒的计时器
	tick := time.NewTicker(50 * time.Millisecond)
	defer tick.Stop()

	for {
		select {
		case now := <-tick.C: // 每五十毫秒更新当前计时
			t.update(now)
		case <-done:
			return
		}
	}
}

// 向ui发送更新ui界面的通知
func (t *Timer) invalidate() {
	select {
	case t.Updated <- struct{}{}:
	default:
	}
}

// 更新时间
func (t *Timer) update(now time.Time) {
	t.mu.Lock()
	defer t.mu.Unlock()
	// 上一次刷新的时间
	previousNow := t.now
	// 现在刷新的时间
	t.now = now

	// 当现在的时间与开始时间的时间差小于设置差值时,更新界面
	progressAfter := t.now.Sub(t.start)
	if progressAfter <= t.duration {
		// 通知更新ui界面
		t.invalidate()
		return
	}

	// 当时间差等于差值时更新ui界面、使进度条走到头
	progressBefore := previousNow.Sub(t.start)
	if progressBefore <= t.duration {
		t.invalidate()
		return
	}
}

// Reset 将开始时间设置为上次的时间
func (t *Timer) Reset() {
	t.mu.Lock()
	defer t.mu.Unlock()

	t.start = t.now
	t.invalidate()
}

// SetDuration 更改计时器的持续时间
func (t *Timer) SetDuration(duration time.Duration) {
	t.mu.Lock()
	defer t.mu.Unlock()

	if t.duration == duration {
		return
	}
	t.duration = duration
	t.invalidate()
}

// Info 获取当前计时器的信息
func (t *Timer) Info() (info Info) {
	t.mu.Lock()
	defer t.mu.Unlock()

	info.Progress = t.now.Sub(t.start)
	info.Duration = t.duration
	if info.Progress > info.Duration {
		info.Progress = info.Duration
	}
	return info
}

// Info 计时器信息
type Info struct {
	Progress time.Duration
	Duration time.Duration
}

// ProgressString 返回进度条的信息
func (info *Info) ProgressString() string {
	return fmt.Sprintf("%.1fs", info.Progress.Seconds())
}

func main() {
	ui := NewUI()

	go func() {
		w := app.NewWindow(
			app.Title("Timer"),
			app.Size(unit.Dp(360), unit.Dp(360)),
		)
		if err := ui.Run(w); err != nil {
			log.Println(err)
			os.Exit(1)
		}
		os.Exit(0)
	}()

	app.Main()
}

var defaultMargin = unit.Dp(10)

// UI ui界面
type UI struct {
	Theme    *material.Theme
	Timer    *Timer           // 计时器
	duration widget.Float     // 滑动框
	reset    widget.Clickable // 可点击对象
}

// NewUI 创建计时器实例
func NewUI() *UI {
	ui := &UI{}
	ui.Theme = material.NewTheme(gofont.Collection())

	// 设置五秒的初始值
	ui.Timer = NewTimer(5 * time.Second)
	ui.duration.Value = 5

	return ui
}

// Run 运行界面
func (ui *UI) Run(w *app.Window) error {

	// 开启一个计时器
	closeTimer := ui.Timer.Start()
	// 退出时关闭
	defer closeTimer()

	var ops op.Ops
	for {
		select {
		// 主动刷新ui窗口
		case <-ui.Timer.Updated:
			// 是窗口无效、刷新frameEvent
			w.Invalidate()

		case e := <-w.Events():

			switch e := e.(type) {

			case system.FrameEvent:

				gtx := layout.NewContext(&ops, e)

				ui.Layout(gtx)

				e.Frame(gtx.Ops)

			case key.Event:
				switch e.Name {

				case key.NameEscape:
					return nil
				}
			case system.DestroyEvent:
				return e.Err
			}
		}
	}
}

// Layout ui界面显示
func (ui *UI) Layout(gtx layout.Context) layout.Dimensions {

	// 检查重置按钮是否点击了
	if ui.reset.Clicked() {
		// 重置时间
		ui.Timer.Reset()
	}
	// 检查滑块值是否已更改
	if ui.duration.Changed() {
		// 重新设置时间差
		ui.Timer.SetDuration(secondsToDuration(float64(ui.duration.Value)))
	}

	// 获取当前计时器的信息
	info := ui.Timer.Info()
	progress := float32(0)
	// 当时间差为0时,进度条显示完成
	if info.Duration == 0 {
		progress = 1
	} else {
		// 百分比显示
		progress = float32(info.Progress.Seconds() / info.Duration.Seconds())
	}

	// inset is used to add padding around the window border.
	inset := layout.UniformInset(defaultMargin)
	return inset.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
		// 创建一个垂直布局
		return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
			layout.Rigid(material.Body1(ui.Theme, "Elapsed Time").Layout),
			// 进度条
			layout.Rigid(material.ProgressBar(ui.Theme, progress).Layout),
			// 进度信息
			layout.Rigid(material.Body1(ui.Theme, info.ProgressString()).Layout),
			// 占位组件
			layout.Rigid(layout.Spacer{Height: ui.Theme.TextSize}.Layout),
			// 文本显示区
			layout.Rigid(material.Body1(ui.Theme, "Duration").Layout),
			// 滑块
			layout.Rigid(material.Slider(ui.Theme, &ui.duration, 0, 15).Layout),
			// 占位组件
			layout.Rigid(layout.Spacer{Height: ui.Theme.TextSize}.Layout),
			// 重置按钮
			layout.Rigid(material.Button(ui.Theme, &ui.reset, "Reset").Layout),
		)
	})
}

// 将秒数转换成时间间隔
func secondsToDuration(s float64) time.Duration {
	return time.Duration(s * float64(time.Second))
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值