Flutter 仿英雄联盟客户端详解

最近在用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...

相关推荐
集成显卡15 小时前
windows 下使用 bat 批处理运行 Chrome 无头模式刷一波访问量
windows·程序员
孤鸿玉16 小时前
Fluter InteractiveViewer 与ScrollView滑动冲突问题解决
flutter
叽哥1 天前
Flutter Riverpod上手指南
android·flutter·ios
BG2 天前
Flutter 简仿Excel表格组件介绍
flutter
zhangmeng2 天前
FlutterBoost在iOS26真机运行崩溃问题
flutter·app·swift
恋猫de小郭2 天前
对于普通程序员来说 AI 是什么?AI 究竟用的是什么?
前端·flutter·ai编程
卡尔特斯2 天前
Flutter A GlobalKey was used multipletimes inside one widget'schild list.The ...
flutter
w_y_fan2 天前
Flutter 滚动组件总结
前端·flutter
醉过才知酒浓2 天前
Flutter Getx 的页面传参
flutter
火柴就是我3 天前
flutter 之真手势冲突处理
android·flutter