Flutter使用Canvas实现微信红包领取效果

效果

最终实现的整体效果如下:

red-packet-demo.gif

实现

看完效果以后,接下来就带领大家来看看是怎样一步一步实现最终效果的,在正式动手写代码之前,先对整个效果做一个简单的拆分,将其分为五个部分:

  1. 点击弹出红包
  2. 红包整体布局
  3. 金币点击旋转
  4. 红包开启动画
  5. 结果页面弹出

拆分后如下图所示:

red-packet-00.gif

接下来就一步一步来实现。

红包弹出

红包弹出主要分为两部分:从小到大缩放动画、半透明遮罩。很自然的想到了使用 Dialog 来实现,最终也确实使用 Dialog 实现了对应的效果,但是在最后展示结果页的时候出现问题了,因为红包开启与结果展示是同时进行的,结果页在红包下面,使用 Dialog 的话会存在结果页在 Dialog 上面遮住红包的效果,最后使用了 Overlay 在顶层添加一个 Widget 来实现。

创建一个 RedPacket 的 Widget:

class RedPacket extends StatelessWidget {
   
  const RedPacket({
   Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
   
    return Center(
        child: Container(
          width: 0.8.sw,
          height: 1.2.sw,
          color: Colors.redAccent,
        )
    );
  }
} 

内容很简单,就是一个居中的宽高分别为 0.8.sw1.2.sw 的 Container,颜色为红色。这里 sw 是代表屏幕宽度,即红包宽度为屏幕宽度的 0.8 倍,高度为屏幕宽度的 1.2 倍。

然后点击按钮时通过 Overlay 展示出来, 创建一个 showRedPacket 的方法:

void showRedPacket(BuildContext context){
   
  OverlayEntry entry = OverlayEntry(builder: (context) => RedPacket());
  Overlay.of(context)?.insert(entry);
} 

效果如下:

red-packet-3.1.1.gif

红包是弹出来了,但因为没有缩放动画,很突兀。为了实现缩放动画,在 Container 上包裹 ScaleTransition 用于缩放动画,同时将 RedPacket 改为 StatefulWidget ,因为使用动画需要用到 AnimationController 传入 SingleTickerProviderStateMixin ,实现如下:

class RedPacket extends StatefulWidget {
   

  const RedPacket({
   Key? key}) : super(key: key);

  @override
  State<RedPacket> createState() => _RedPacketState();
}

class _RedPacketState extends State<RedPacket> with SingleTickerProviderStateMixin {
   
  late AnimationController scaleController = AnimationController(vsync: this)
    ..duration = const Duration(milliseconds: 500)
    ..forward();

  @override
  Widget build(BuildContext context) {
   
    return Container(
      color: Color(0x88000000), /// 半透明遮罩
      child: Center(
          child: ScaleTransition(
            scale: Tween<double>(begin: 0.0, end: 1.0).animate(CurvedAnimation(parent: scaleController, curve: Curves.fastOutSlowIn)),
            child: Container(
              width: 0.8.sw,
              height: 1.2.sw,
              color: Colors.redAccent,
            ),
          )
      ),
    );
  }
} 

ScaleTransition 设置动画从 0.0 到 1.0 即从无到原本大小,动画时间为 500 毫秒;同时在外层再包裹一层 Container 并为其添加半透明颜色实现半透明遮罩,最终实现效果:

red-packet-3.1.2.gif

这样就实现了第一部分的功能。

红包布局

标题说了是使用 Canvas 来实现,所以红包布局主要是使用 Canvas 来实现,将前面红包的 Container 换成 CustomPaint, 然后创建 RedPacketPainter 继承自 CustomPainter :

ScaleTransition(
  scale: Tween<double>(begin: 0.0, end: 1.0).animate(CurvedAnimation(parent: scaleController, curve: Curves.fastOutSlowIn)),
  child: CustomPaint(
    size: Size(1.sw, 1.sh),
    painter: RedPacketPainter(),
  ),
) 

考虑到后续动画,这里将画布的大小设置为全屏。红包布局的核心代码就在 RedPacketPainter 里,首先绘制红包的背景,背景分为上下两部分,上部分又由一个矩形和一个圆弧组成,下半部分同样是由一个矩形和一个圆弧组成,上半部分的圆弧是凸出来的,而下半部分的是凹进去的,示意图如下:

red-packet-3.2.1.gif

初始化:

/// 画笔 
late final Paint _paint = Paint()..isAntiAlias = true;
/// 路径
final Path path = Path();

/// 红包的高度:1.2倍的屏幕宽度
late double height = 1.2.sw;

/// 上半部分贝塞尔曲线的结束点
late double topBezierEnd = (1.sh - height)/2 + height/8*7;
/// 上半部分贝塞尔曲线的起点
late double topBezierStart= topBezierEnd - 0.2.sw;
/// 下半部分贝塞尔曲线的起点
late double bottomBezierStart = topBezierEnd - 0.4.sw;

/// 金币中心点,后续通过path计算
Offset goldCenter = Offset.zero;

/// 横向的中心点
final double centerWidth = 0.5.sw;

/// 红包在整个界面的left
late double left = 0.1.sw;
/// 红包在整个界面的right
late double right = 0.9.sw;
/// 红包在整个界面的top
late double top = (1.sh - height)/2;
/// 红包在整个界面的bottom
late double bottom = (1.sh - height)/2 + height; 
上半部分

代码实现如下:

void drawTop(ui.Canvas canvas) {
     
  path.reset();
  path.addRRect(RRect.fromLTRBAndCorners(left, top, right, topBezierStart, topLeft: const Radius.circular(5), topRight: const Radius.circular(5)));
  var bezierPath = getTopBezierPath();
  path.addPath(bezierPath, Offset.zero);
  path.close();

  canvas.drawShadow(path, Colors.redAccent, 2, true);
  canvas.drawPath(path, _paint
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值