[go] 360安全卫士七 --带动画效果的登陆框

永远的GitHub地址: https://github.com/JianBiHua/go_360_safe

如图效果(显示跟隐藏,都是慢慢显示的带尖角的窗体):

这里有几个小问题我还没有解决

  1. 动画时长设置太短(比如0.3秒),左右会多出一部分,
  2. 显示时,会闪现显示一个子控件,然后才正常动画
  3. ticker有时会崩溃.

显示: 当鼠标滑动到"简笔画/登陆/360图标"这块时
隐藏: 当鼠标厉害显示区域以及登陆框区域时。

[go] 360安全卫士七 --带动画效果的登陆框_第1张图片# 实现原理说明

1. 如上节的波浪球绘制一样,得画一个带尖角的矩形。

2. 使用QT库自带的Mask功能(SetMask2)

核心代码如下:
A. 绘制代码:

  1. 我只画了尖角为上的情况,当然,也可以尖角为左为右。
  2. 为绘制了阴影
  3. 绘制的尖角矩形位置是动态的,效果才好看些
  4. 代码注释还算详细,自己理解吧。
// 画图
// 绘制较简单,就不将painter作为全局变量了
func (p *PopupWidget) onPaint (event *gui.QPaintEvent)  {
	//// 创建画笔
	var device = p.BackingStore().PaintDevice() //window.Painters[0]
	var painter = gui.NewQPainter2(device)
	// 反走样
	painter.SetRenderHint(gui.QPainter__Antialiasing, true)

	var shadowPath = gui.NewQPainterPath()
	var path = gui.NewQPainterPath()

	// 我这里只画一个向上的,其它基本一样,就是坐标不一样罢了
	// 我最讨厌go里面的不同类型的运算,转来转去,麻烦....
	switch p.arrowDirection {
	case ArrowDirectionUp:
		// 办法1:这个是通过遮罩实现的,但是看不到左右的边,如果有阴影,效果就不是很好了。
		//// 这些代码是显示一个完整的带尖角的代码。
		//path.MoveTo2(0, ArrowWidth)
		//path.LineTo2(p.luDis-ArrowWidth/2.0, ArrowWidth)
		//// 下面两句是画箭头的
		//path.LineTo2(p.luDis, 0)
		//path.LineTo2(p.luDis+ArrowWidth/2.0, ArrowWidth)
		//path.LineTo2(float64(p.Width()), ArrowWidth)
		//path.LineTo2(float64(p.Width()), float64(p.Height()))
		//path.LineTo2(0, float64(p.Height()))
		//path.LineTo2(0, ArrowWidth)
		//
		//// 添加一个遮罩层,我们的遮罩应该以箭头为中心
		//// 这样就能算出实际应该遮罩的位置,改变currentProgress, 就出现了从箭头位置向两边延伸的效果
		//path2.AddRect2(p.luDis*(1-p.currentProgress), 0, p.currentProgress*float64(p.Width()), float64(p.Height()))
		//painter.DrawPath(path2)

		// 办法2,直接根据currentProgress,动态画这个带尖角的图形,可以稍微往里留一两个像素,那么就能看到阴影了.
		// 注意,这个图形最小宽度应为 ArrowWidth+(ShadowWidth*2), 左右留出ShadowWidth大小的像素画阴影。

		// 这里要考虑两种情况,
		// 第一: currentProgress*p.width() < ArrowWidth+(ShadowWidth*2) 时
		// 第二: currentProgress*p.width() >= ArrowWidth+(ShadowWidth*2) 时
		// 设置颜色。

		//=======================================================================
		// 阴影的原理,就是将图形, 向左或者向右,向上或者向下移动一定像素,图形透明度降低
		// 画阴影
		painter.SetPen3(core.Qt__NoPen)
		painter.SetBrush(gui.NewQBrush10(p.getShadowColor()))

		var rw = p.currentProgress*float64(p.Width()-ShadowWidth*2)
		var rx = 0.0
		if rw < ArrowWidth+ShadowWidth*2 {
			rx = p.luDis-ArrowWidth/2+2
		} else {
			rx = p.luDis*(1-p.currentProgress)+2
		}

		// 这样就画出了不同的图形,最小宽度ArrowWidth+(ShadowWidth*2)
		shadowPath.MoveTo2(rx+p.pX, ArrowWidth+2+p.pY)
		shadowPath.LineTo2(p.luDis-ArrowWidth/2+p.pX, ArrowWidth+2+p.pY)
		// 画角
		shadowPath.LineTo2(p.luDis+p.pX, 0+2+p.pY)
		shadowPath.LineTo2(p.luDis+ArrowWidth/2+p.pX, ArrowWidth+2+p.pY)
		shadowPath.LineTo2(rx+rw+p.pX, ArrowWidth+2+p.pY)
		// 留出底部的阴影区域
		shadowPath.LineTo2(rx+rw+p.pX, float64(p.Height())-ShadowWidth+2+p.pY)
		shadowPath.LineTo2(rx+p.pX, float64(p.Height())-ShadowWidth+2+p.pY)
		shadowPath.LineTo2(rx+p.pX, ArrowWidth+2+p.pY)
		// 显示图形。
		painter.DrawPath(shadowPath)

		//=======================================================================
		// 画正常的图形
		// 设置颜色。
		painter.SetPen2(gui.NewQColor3(0x7F, 0x7F, 0x7F, 0x7F))
		painter.SetBrush(gui.NewQBrush3(gui.NewQColor3(0xF5, 0xF5, 0xF5, 0xFF),1))

		rw = p.currentProgress*float64(p.Width()-ShadowWidth*2)
		rx = 0.0
		if rw < ArrowWidth+ShadowWidth*2 {
			rx = p.luDis-ArrowWidth/2
		} else {
			rx = p.luDis*(1-p.currentProgress)
		}

		// 这样就画出了不同的图形,最小宽度ArrowWidth+(ShadowWidth*2)
		path.MoveTo2(rx+p.pX, ArrowWidth+p.pY)
		path.LineTo2(p.luDis-ArrowWidth/2+p.pX, ArrowWidth+p.pY)
		// 画角
		path.LineTo2(p.luDis+p.pX, 0+p.pY)
		path.LineTo2(p.luDis+ArrowWidth/2+p.pX, ArrowWidth+p.pY)
		path.LineTo2(rx+rw+p.pX, ArrowWidth+p.pY)
		// 留出底部的阴影区域
		path.LineTo2(rx+rw+p.pX, float64(p.Height())-ShadowWidth+p.pY)
		path.LineTo2(rx+p.pX, float64(p.Height())-ShadowWidth+p.pY)
		path.LineTo2(rx+p.pX, ArrowWidth+p.pY)

		// 显示图形。
		painter.DrawPath(path)
	case ArrowDirectionDown:
	}

	// 结束绘制
	painter.End()
	painter.DestroyQPainter()
}

B. 动画效果.

  1. 实际上就是动态修改currentProgress, 以及动态设置SetMask2
// 显示
// interval:显示的持续时间, 1000 = 1s
// f: 动画结束时相应,可能某些人喜欢在动画结束后执行一些操作,就可以这么写
func (p *PopupWidget) Show2 (interval int, f func(widget *PopupWidget))  {
	if !p.isShow {
		// 只有隐藏状态才显示
		p.ticker = time.NewTicker(20 * time.Millisecond)

		// 默认遮罩,什么也不显示
		p.SetMask2(gui.NewQRegion2(int(p.luDis),
			0,
			0,
			p.Height(),
			gui.QRegion__Rectangle))

		p.loginWidget.ShowDefault()

		go func() {
			defer p.ticker.Stop()

			var ticker = 0
			for {
				select {
				case <-p.ticker.C:
					// 1.0 / (interval/20)
					p.currentProgress += 20.0 / float64(interval)
					if p.currentProgress > 1 {
						p.currentProgress = 1
						// 都跑到了,就直接让它结束吧,
						ticker = interval
					}

					// 修改遮罩位置
					// 添加一个遮罩层,我们的遮罩应该以箭头为中心
					// 这样就能算出实际应该遮罩的位置,改变currentProgress, 就出现了从箭头位置向两边延伸的效果
					p.SetMask2(gui.NewQRegion2(int(p.luDis*(1-p.currentProgress)),
						0,
						int(p.currentProgress*float64(p.Width())),
						p.Height(),
						gui.QRegion__Rectangle))

					// 计数
					ticker += 20
					if ticker >= interval {

						//
						p.isShow = true

						// 告诉上层,动画完成了.
						if f != nil {
							f (p)
						}

						return
					}

					// 刷新
					p.Update()

				}
			}
		} ()
	}
}

C. 控件显示代码

  1. 这里捕获window的事件消息,我们只关心鼠标滑动的消息。
  2. 根据鼠标滑动的坐标,来判断应该是显示,还是隐藏loginPopupWidget
  3. 注意这个SetGeometry3,是自定义的,不是QT系统库函数,我想重载SetGeometry2的,不过失败了
  4. 代码注释也比较详细,自己理解哈。
	mw.window.ConnectEvent(func(event *core.QEvent) bool {

		if event.Type() == core.QEvent__HoverMove {
			// 获取当前鼠标在屏幕上的绝对坐标
			var pos = core.NewQPoint2(gui.QCursor_Pos().X(), gui.QCursor_Pos().Y())
			// 将全局左边转换成window上的坐标
			var pos2 = mw.window.MapFromGlobal(pos)
			var x = pos2.X()
			var y = pos2.Y()

			// 如果是鼠标滑动
			// 处理鼠标的滑动
			// 实现的逻辑是,
			// 当鼠标移动到loginPopupWidget上或者移动到登陆区域,才显示登陆框,否则,如果显示了则隐藏。
			if mw.loginPopupWidget == nil {
				// 如果loginPopupWidget不存在,移动到登陆区域,才显示登陆框
				if mw.loginWidget.Widget().Geometry().Contains3(x, y) {
					mw.loginPopupWidget = NewPopupWidget (mw.window, ArrowDirectionUp, 90, 700, 90)
					mw.loginPopupWidget.SetGeometry3(700, 90, 180, 100)
					mw.loginPopupWidget.Show(1*1000)
					mw.loginPopupWidget.SetWindowFlags(core.Qt__WindowStaysOnTopHint);
					mw.loginPopupWidget.ShowNormal()
				}
			} else {
				// 如果显示了。鼠标移动到除登录区以及loginPopupWidget区域外时,隐藏loginPopupWidget
				if !mw.loginWidget.Widget().Geometry().Contains3(x, y) &&
					!mw.loginPopupWidget.Geometry().Contains3(x, y) {

					mw.loginPopupWidget.Hide2(1*1000, func(widget *PopupWidget) {
						if mw.loginPopupWidget != nil {
							mw.loginPopupWidget.HideDefault()
							//当隐藏后销毁
							mw.loginPopupWidget.DestroyQWidget()
							mw.loginPopupWidget = nil
						}
					})
				}
			}
		}

		// 如果没有这句,你会发现波浪不动了, 因为你把所有的事件都处理了,下面收不到消息了
		return mw.window.EventDefault(event)
	})

你可能感兴趣的:(go)