最近在用Flutter 模仿英雄联盟客户端,
制作顶部菜单的时候,发现了之前没有注意的细节效果,写个文章分享和记录一下。
分析:鼠标移动到菜单上,会产生一个光晕,从底部向上扇形扩散,光晕的中心点就是鼠标的X轴坐标。
现在尝试来复现这个效果。
我们需要用到的第一个组件是:CustomPaint
它是一个自己处理绘制的组件。
一定要用RepaintBoundary包住,不然它会被其他组件的notifyListeners()影响导致CustomPaint重绘,如果没有用RepaintBoundary包裹,它自身的notifyListeners()行为也会影响其他组件导致重绘。
less
RepaintBoundary(
//底层光圈绘制层
child: CustomPaint(
painter: _painter,
// 上层元素
child: widget.child,
),
)
scala
class TopMenuShadowPainter extends ChangeNotifier implements CustomPainter {
TopMenuShadowPainter();
@override
void paint(Canvas canvas, Size size) {
// 圆的尺寸
double radius = 80;
// 绘制一个 圆形
canvas.drawOval(
Rect.fromLTWH(0, 0, radius, radius),
paint,
);
// 或者绘制一个 扇形 pi是系统定义的 3.1415926535897932 , pi + pi 就是从左侧水平 0°到 右侧 180°
canvas.drawArc(
Rect.fromLTWH(0, 0, radius, radius),
pi,
pi,
true,
paint,
);
}
绘制出大小80直径的圆
绘制出80直径半圆
接下来就是让这个圆在鼠标的位置中心点绘制,所以需要把鼠标传进去,一开始我使用的是AnimatedBuilder+CustomPaint, 这种方案每次绘制都会重新创建新的painter对象,不是特别的好。
我参考了Flutter 绘制探索 1 | CustomPainter 正确刷新姿势 | 七日打卡这篇文章,里面有讲到CustomPaint的几种刷新机制,当前这个Menu 比较适合采用 ChangeNotifier的方式进行刷新,让当前TopMenuShadowPainter继承ChangeNotifier,并且实现CustomPainter,这样它就带有通知刷新和绘制能力。
我们需要使用MouseRegion捕获鼠标的位置和移出移入事件。
csharp
/// 绘制器
late TopMenuShadowPainter _painter;
MouseRegion(
/// 显示为手指的效果
cursor: SystemMouseCursors.click,
onEnter: (event) {
// 鼠标进入
_painter.updateEnter(true);
},
onExit: (event) {
// 鼠标离开
_painter.updateEnter(false);
},
onHover: (event) {
// 鼠标坐标移动
_painter.updatePoint(event.localPosition.dx);
},
child: RepaintBoundary(
//底层光圈绘制层
child: CustomPaint(
painter: _painter,
child: widget.child,
),
),
);
内部更新鼠标的坐标和进出状态。
ini
TopMenuShadowPainter extends ChangeNotifier implements CustomPainter
// 使用方法刷新鼠标的进入离开状态 以及 鼠标的 坐标 =-=
// notifyListeners 触发的时候,没有用RepaintBoundary,会导致其他部分组件重绘。
/// 鼠标进入
bool mouseEnter = false;
// 鼠标的坐标点
double mouseX = 0;
/// 更新
void updateEnter(bool newMouseEnter) {
mouseEnter = newMouseEnter;
//debugPrint("鼠标进入退出:$mouseEnter");
notifyListeners();
}
/// 更新
void updatePoint(double newMouseX) {
mouseX = newMouseX;
notifyListeners();
}
重新计算绘制的圆位置。
arduino
// 计算鼠标的坐标 产生圆的位置
// 圆起点的X坐标 计算方式 = 鼠标的X轴坐标(鼠标是居中位置) 减去 圆的一半
double x = mouseX - radius / 2,
// 圆起点的Y坐标 计算方式 = 绘图Canvs的高度的一半
// 向下偏移 10px(使光晕更贴底,这个值也可以不加或者更大)
double y = size.height / 2 + 10,
// 绘制圆
canvas.drawOval(
Rect.fromLTWH(
mouseX - radius / 2,
size.height / 2 + 10,
radius,
radius,
),
paint,
);
好的,我们现在来看看效果。
ops?咋回事,溢出了。。
这时候我们设置一下裁剪区域,
arduino
// 从0,0坐标 扩展到画板尺寸size的Rect区域
canvas.clipRect(Offset.zero & size);
Y轴溢出问题解决
Y轴不溢出了,但是X轴上面的被裁剪掉了
调整一下裁剪区域
arduino
// 裁剪区域 向左移动 圆的大小 向右也移动圆的的大小,radius
Rect clipRect = Rect.fromLTRB(- radius, 0, size.width + radius, size.height);
canvas.clipRect(clipRect);
接下来,再给光晕增加一个高斯模糊的效果。
ini
var paint = Paint()
// 背景渐变刷子
..shader = gradient
// 模糊效果
..maskFilter = const MaskFilter.blur(BlurStyle.normal, 20);
// 绘制底部的金线
canvas.drawLine(
Offset(mouseX - 50, size.height),
Offset(mouseX + 50, size.height),
Paint()
..strokeCap = StrokeCap.round
// 从 中间向两边 渐变的背景刷
..shader = lineColor
..strokeWidth = 1.5,
);
PS:最开始我是给每个菜单包一个这个渲染,因为我是模仿英雄联盟做的,后面封装了一个完整的容器,这样多个菜单共享一个背景刷,效果会好很多。
另外一个 PLAY按钮的具体做法,目前有点LOW,大概讲一下思路,看看后面有没有更好的方案
默认效果
随机圆点
开启模糊
鼠标移入的时候,开启一个循环动画。绘制随机的光斑(模糊),每一帧向左侧偏移0~1px,最终效果就还能接受,等优化了。
最后附上源码地址,可以下载编译 或者 下载 编译好的包 体验。 github.com/944095635/l...