Flutter利用Canvas模拟实现微信红包领取效果

目录
  • 前言
  • 效果
  • 红包弹出
  • 红包布局
    • 上半部分
    • 下半部分
    • 金币绘制
    • 金币文字绘制
    • 头像和文字
  • 金币旋转
  • 红包开启
  • 结果弹出

前言

前面写了一篇Flutter利用Canvas绘制精美表盘效果详解的文章,对 Flutter 中的 Canvas 使用有了进一步的理解,就想着再用 Canvas 实现一个什么样的效果来加深一下对 Canvas 使用的理解,这个时候正好看到群里有人发红包,于是就想着能不能在 Flutter 中使用 Canvas 实现微信领取红包的效果?想到就做,知行合一,经过几天空余时间的研究,最终实现了微信领取红包效果,于是有了这篇文章。

效果

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

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

  • 点击弹出红包
  • 红包整体布局
  • 金币点击旋转
  • 红包开启动画
  • 结果页面弹出

拆分后如下图所示:

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

红包弹出

红包弹出主要分为两部分:从小到大缩放动画、半透明遮罩。很自然的想到了使用 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 倍。

关于 Flutter 屏幕适配,请参阅:Flutter应用框架搭建之屏幕适配详解

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

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

效果如下:

红包是弹出来了,但因为没有缩放动画,很突兀。为了实现缩放动画,在 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 并为其添加半透明颜色实现半透明遮罩,最终实现效果:

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

红包布局

标题说了是使用 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 里,首先绘制红包的背景,背景分为上下两部分,上部分又由一个矩形和一个圆弧组成,下半部分同样是由一个矩形和一个圆弧组成,上半部分的圆弧是凸出来的,而下半部分的是凹进去的,示意图如下:

初始化:

/// 画笔
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);
}

这里使用 Path 来进行绘制,首先向路径中添加一个圆角矩形,也就是示意图中的第①部分,然后通过 getTopBezierPath 获取一个贝塞尔曲线的 bezierPath 并将其添加到 path 路径中,getTopBezierPath 源码如下:

Path getTopBezierPath() {
  Path bezierPath = Path();
  bezierPath.moveTo(left, topBezierStart);
  bezierPath.quadraticBezierTo(centerWidth, topBezierEnd, right , topBezierStart);

  var pms = bezierPath.computeMetrics();
  var pm = pms.first;
  goldCenter = pm.getTangentForOffset(pm.length / 2)?.position ?? Offset.zero;
  return bezierPath;
}

getTopBezierPath 源码分为两部分,第一部分是创建贝塞尔曲线的 path ,使用的是最开始初始化的数据创建,实现示意图中的第②部分内容;然后根据创建好的贝塞尔曲线的 path 计算出路径中中间点的坐标,作为金币中心点坐标。示意图如下:

图中红点就是贝塞尔曲线的点,中间实线就是贝塞尔曲线,也就是上面代码中创建的贝塞尔曲线路径,实线中间的点就是金币位置的中心点。

贝塞尔曲线绘制完成后调用 drawShadow 绘制阴影,作用是突出上下两部分连接处的效果,最后通过 path 绘制出整个上半部分的效果,如下:

下半部分

代码实现如下:

void drawBottom(ui.Canvas canvas) {
  path.reset();
  path.moveTo(left, bottomBezierStart );
  path.quadraticBezierTo(centerWidth, topBezierEnd, right , bottomBezierStart);

  path.lineTo(right, topBezierEnd);
  path.lineTo(left, topBezierEnd);

  path.addRRect(RRect.fromLTRBAndCorners(left, topBezierEnd, right, bottom, bottomLeft: const Radius.circular(5), bottomRight: const Radius.circular(5)));
  path.close();

  canvas.drawShadow(path, Colors.redAccent, 2, true);

  canvas.drawPath(path, _paint);
}

下半部分实现同样分为两部分,首先绘制出贝塞尔曲线,即示意图第③部分,然后再添加一个圆角矩形,即示意图第④部分;然后绘制下半部分的阴影和图形,单独展示下半部分效果如下:

将上下两部分结合起来,就实现了红包背景的效果,如下:

金币绘制

背景绘制完成后接下来进行金币的绘制,前面已计算出金币的中心点坐标,静态金币的绘制就相对来说比较简单了,就是一个圆形,代码如下:

void drawGold(ui.Canvas canvas){
  Path path = Path();

  canvas.save();
  canvas.translate(0.5.sw, goldCenter.dy);

  _paint.style = PaintingStyle.fill;
  path.addOval(Rect.fromLTRB(-40.w , -40.w, 40.w , 40.w));

  _paint.color = const Color(0xFFFCE5BF);
  canvas.drawPath(path, _paint);

  canvas.restore();
}

这里将画布移动到到金币的中心点,然后向 Path 中添加添加一个半径为 40.w 的圆,最后将 path 绘制出来即可。效果如下:

金币文字绘制

金币绘制出来后,还需在金币上绘制一个繁体的 "開" 字,代码如下:

void drawOpenText(ui.Canvas canvas) {
  if(controller.showOpenText){
    TextPainter textPainter = TextPainter(
      text: TextSpan(
        text: "開",
        style: TextStyle(fontSize: 34.sp, color: Colors.black87, height: 1.0, fontWeight: FontWeight.w400)
      ),
      textDirection: TextDirection.ltr,
      maxLines: 1,
      textWidthBasis: TextWidthBasis.longestLine,
      textHeightBehavior: const TextHeightBehavior(applyHeightToFirstAscent: false, applyHeightToLastDescent: false)
    )..layout();

    canvas.save();
    canvas.translate(0.5.sw, goldCenter.dy);
    textPainter.paint(canvas, Offset(- textPainter.width / 2, -textPainter.height/2));
    canvas.restore();
  }
}

使用 TextPainter 进行文字的绘制,同样是将画布移动到金币的中心,然后绘制文字,效果如下:

头像和文字

经过上面的绘制,效果已经出来了,但是还差红包封面上的用户头像相关文字,使用 Canvas 同样能实现,但这里并没有使用 Canvas 来实现,而是使用 CoustomPaint 的 child 来实现:

CustomPaint(
  size: Size(1.sw, 1.sh),
  painter: RedPacketPainter(controller: controller),
  child: buildChild(),
)

Container buildChild() {
  return Container(
    padding: EdgeInsets.only(top: 0.3.sh),
    child: Column(
      crossAxisAlignment: CrossAxisAlignment.center,
      children: [
        Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            ClipRRect(
              borderRadius: BorderRadius.circular(3.w),
              child: Image.network("https://p26-passport.byteacctimg.com/img/user-avatar/32f1f514b874554f69fe265644ca84e4~300x300.image", width: 24.w,)),
            SizedBox(width: 5.w,),
            Text("loongwind发出的红包", style: TextStyle(fontSize: 16.sp, color: Color(
              0xFFF8E7CB), fontWeight: FontWeight.w500),)
          ],
        ),
        SizedBox(height: 15.w,),
        Text("恭喜发财", style: TextStyle(fontSize: 18.sp, color: Color(
          0xFFF8E7CB)),)
      ],
    ),
  );
}

CoustomPaint 的 child 允许传入一个 Widget,Widget 的实现就是要显示的头像和文字,代码如上,最终效果如下:

至此红包的整个布局的实现就完成了。

金币旋转

前面完成了红包的静态显示,接下来就看看怎么让红包动起来,首先看看怎么让金币旋转起来。

说到旋转首先想到的就是以金币的中心旋转,可以通过旋转画布的旋转或者 path 的 transform 旋转来实现,但是经过实验使用这种方式能让金币旋转起来,但是做到旋转的立体效果却很复杂。所以最终采用的是使用两个圆在 x 轴上进行一定的偏移,然后压缩圆的宽度来模拟实现旋转效果,示意图如下:

如图所示,绘制两个相同的圆,开始时将两个圆重叠在一起,然后同时压缩圆的宽度并将下层的圆向左偏移一定单位,就形成了旋转的立体效果。

代码:

void drawGold(ui.Canvas canvas){
  Path path = Path();

  canvas.save();
  canvas.translate(0.5.sw, goldCenter.dy);

  _paint.style = PaintingStyle.fill;
  path.addOval(Rect.fromLTRB(-40.w , -40.w, 40.w , 40.w));

  _paint.color = const Color(0xFFE5CDA8);
  canvas.drawPath(path, _paint);

  _paint.color = const Color(0xFFFCE5BF);
  canvas.drawPath(path, _paint);

  canvas.restore();
}

修改上面绘制金币的代码,设置不同的颜色再绘制一个圆,这样就在同一个位置绘制了两个不同颜色的圆。那么怎么让它动起来呢?可以使用动画,通过动画执行宽度的缩放,是宽度系数从 1 缩放到 0 再从 0 回到 1。因为 CustomPainter 是继承自 Listenable ,而动画也是 Listenable 所以直接将动画与 CustomPainter 结合起来使用更方便。

为了方便统一控制红包的动画,创建一个 RedPacketController,并在里面创建一个控制金币旋转的动画及控制器:

class RedPacketController{

  final SingleTickerProviderStateMixin tickerProvider;
  late AnimationController angleController;
  late Animation<double> angleCtrl;

  RedPacketController({required this.tickerProvider}){
    initAnimation();
  }

  void initAnimation() {
    angleController = AnimationController(
        duration: const Duration(seconds: 3),
        vsync: tickerProvider
    )..repeat(reverse: true);
    angleCtrl = angleController.drive(Tween(begin: 1.0, end: 0.0));
  }

  void dispose(){
    angleController.dispose();
    timer?.cancel();
  }
}

为了看到旋转的效果,将动画执行时间设置为 3 秒,并且让其重复执行,重复执行时设置 reverse 为 true,即反向执行,然后改造 _RedPacketState 混入 SingleTickerProviderStateMixin 并创建 RedPacketController :

class _RedPacketState extends State<RedPacket> with SingleTickerProviderStateMixin{

  late RedPacketController controller = RedPacketController(tickerProvider: this);

  Widget buildRedPacket() {
    return GestureDetector(
      onTapUp: controller.clickGold,
      child: CustomPaint(
        size: Size(1.sw, 1.sh),
        painter: RedPacketPainter(controller: controller),
        child: buildChild(),
      ),
    );
  }
  /// ...
}

class RedPacketPainter extends CustomPainter{

  RedPacketController controller;

  RedPacketPainter({required this.controller}) : super(repaint:controller.angleController);

  /// ...
}

RedPacketPainter 的构造方法调用了 super 并传入了 repaint 参数,即创建的动画控制器。

这样就能在绘制金币的时候使用动画的值了:

void drawGold(ui.Canvas canvas){
  Path path = Path();

  double angle = controller.angleCtrl.value;

  canvas.save();
  canvas.translate(0.5.sw, goldCenter.dy);

  _paint.style = PaintingStyle.fill;
  path.addOval(Rect.fromLTRB(-40.w * angle , -40.w, 40.w * angle, 40.w));

  _paint.color = const Color(0xFFE5CDA8);
  canvas.drawPath(path, _paint);

  _paint.color = const Color(0xFFFCE5BF);
  canvas.drawPath(path, _paint);

  canvas.restore();
}

通过 controller.angleCtrl.value 获取当前动画的值,然后在圆的 left 和 right 参数上乘以这个值,看一下效果:

效果已经有了,但是发现在旋转到最小的时候中间是空的,这不符合我们的预期,那怎么办呢?将两个圆的边一一连接起来是不是中间就不空了,如图所示:

代码实现:

void drawGoldCenterRect(ui.Path path, ui.Path path2, ui.Canvas canvas) {

  var pms1 = path.computeMetrics();
  var pms2 = path2.computeMetrics();

  var pathMetric1 = pms1.first;
  var pathMetric2 = pms2.first;
  var length = pathMetric1.length;
  Path centerPath = Path();
  for(int i = 0; i <length; i++){
    var position1 = pathMetric1.getTangentForOffset(i.toDouble())?.position;
    var position2 = pathMetric2.getTangentForOffset(i.toDouble())?.position;
    if(position1 == null || position2 == null){
      continue;
    }
    centerPath.moveTo(position1.dx, position1.dy);
    centerPath.lineTo(position2.dx, position2.dy);
  }

  Paint centerPaint = Paint()..color = const Color(0xFFE5CDA8)
    ..style = PaintingStyle.stroke..strokeWidth=1;
  canvas.drawPath(centerPath, centerPaint);
}

使用 Path.computeMetrics 计算两个 path 的路径点,循环路径每一个点,将两个 path 的每一个点连接起来然后绘制出来,再来看一下效果:

效果好多了,但是仔细观察发现还是有一个问题,金币看着不是旋转的而是左右摇摆的,这是因为实现的立体的效果一直在一边导致的,需要根据旋转的时机将立体效果的方向切换,从 1 到 0 时在右边,从 0 到 1 时在左边,通过动画的状态进行判断,修改代码如下:

var frontOffset = 0.0;
var backOffset = 0.0;
if(controller.angleCtrl.status == AnimationStatus.reverse){
  frontOffset = 4.w;
  backOffset = -4.w;
}else if(controller.angleCtrl.status == AnimationStatus.forward){
  frontOffset = -4.w;
  backOffset = 4.w;
}

var path2 = path.shift(Offset(backOffset * (1 - angle),  0));
path = path.shift(Offset(frontOffset * (1 - angle), 0));

再来看一下效果:

这样旋转效果就很完美了,金币中间还缺一个空心的矩形,实现很简单,在圆的 path 路径中叠加一个矩形即可:

path.addRect(Rect.fromLTRB(-10.w * angle , -10.w, 10.w * angle , 10.w));
path.fillType = PathFillType.evenOdd;

设置fillTypeevenOdd ,这样就形成了中心空心的效果,并且由于上面连接两个圆的路径点,这个空心也自带了立体效果,如图:

最后为金币添加点击事件,点击时开启旋转,并隐藏金币上的文字。点击事件可以直接给 CustomPaint 包裹一个 GestureDetector ,点击时判断点击坐标是否在金币的绘制范围内,可以使用 Path.contains 进行判断,所以需要保存金币的 path 用于点击判断,这里将其保存到 controller 里:

controller.goldPath = path.shift(Offset(0.5.sw, goldCenter.dy));

然后在 RedPacketController 里定义对应的变量和方法:

class RedPacketController{
  Path? goldPath;
  bool showOpenText = true;

  bool checkClickGold(Offset point){
    return  goldPath?.contains(point) == true;
  }

  void clickGold(TapUpDetails details) {
    if(checkClickGold(details.globalPosition)){
      angleController.repeat(reverse: true);
      tickerProvider.setState(() {
        showOpenText = false;
      });
    }
  }

  /// ...
}

goldPath 用于保存金币的 path,showOpenText 用于是否显示金币上的文字,点击时判断事件触发点是否在金币范围内,在金币范围内则触发动画启动,并设置金币上的文字不显示。

为 CustomPaint 添加点击事件:

Widget buildRedPacket() {
  return GestureDetector(
    onTapUp: controller.clickGold,
    child: CustomPaint(
      size: Size(1.sw, 1.sh),
      painter: RedPacketPainter(controller: controller),
      child: buildChild(),
    ),
  );
}

看一下效果 :

红包开启

红包开启其实就是将红包上下两部分分别进行向上和向下的平移,再加上背景颜色的渐变实现,示意图如下:

加上之前金币的动画,存在多个动画控制器,所以需要将 _RedPacketState 修改为混入 TickerProviderStateMixin:

class _RedPacketState extends State<RedPacket> with TickerProviderStateMixin{
  /// ...
}

然后在 RedPacketController 添加平移动画控制器,并添加平移和颜色渐变动画,平移系数从 0 到 1, 颜色渐变从不透明到完全透明。并将平移动画与之前的金币动画合并为 repaint

class RedPacketController{

  final TickerProviderStateMixin tickerProvider;

  late AnimationController angleController;
  late AnimationController translateController;
  late Animation<double> translateCtrl;
  late Animation<Color?> colorCtrl;

  void initAnimation() {

    /// ...

    translateController = AnimationController(
        duration: const Duration(milliseconds: 800),
        vsync: tickerProvider
    );
    translateCtrl = translateController.drive(Tween(begin: 0.0, end: 1.0));
    colorCtrl = translateController.drive(ColorTween(
        begin: Colors.redAccent,
        end: const Color(0x00FF5252))
    );

    repaint = Listenable.merge([angleController, translateController]);
  }
}

再在 RedPacketPainter 的 super 里传入 repaint:

RedPacketPainter({required this.controller}) : super(repaint:controller.repaint);

改造绘制红包上半部分代码:

void drawTop(ui.Canvas canvas) {
  canvas.save();
  canvas.translate(0, topBezierEnd  * ( - controller.translateCtrl.value));

  /// ...

  canvas.restore();
}

添加画布平移操作,平移的 Y 值为上半部分高度乘以动画值,即从 0 向上移动上半部分高度。

下半部分添加同样的处理,平移方向向下:

void drawBottom(ui.Canvas canvas) {
  canvas.save();
  canvas.translate(0, topBezierStart * (controller.translateCtrl.value));

	/// ...

  canvas.restore();
}

效果如下:

背景的平移效果实现了,但是上面的头像和文字没动,接下来给头像和文字的 Widget 添加 AnimatedBuilder 使用相同的动画让其跟着移动:

Widget buildChild() {
    return AnimatedBuilder(
      animation: controller.translateController,
      builder: (context, child) => Container(
        padding: EdgeInsets.only(top: 0.3.sh * (1 - controller.translateCtrl.value)),
        child: Column(...),
      ),
    );
  }

通过动态修改 paddingTop 的值,让头像与文字也向上平移。效果如下:

最后在金币点击事件上添加一个定时器,金币旋转 2 秒后执行红包开启动画:

void clickGold(TapUpDetails details) {
  if(checkClickGold(details.globalPosition)){
    if(angleController.isAnimating){
      stop();
    }else{
      angleController.repeat(reverse: true);
      tickerProvider.setState(() {
        showOpenText = false;
      });
      timer = Timer(const Duration(seconds: 2), (){
        stop();
      });
    }
  }
}
void stop() async{
  if(angleController.isAnimating){
    ///停止金币动画,让动画看起来更自然
    if(angleController.status == AnimationStatus.forward){
      await angleController.forward();
      angleController.reverse();
    }else if(angleController.status == AnimationStatus.reverse){
      angleController.reverse();
    }

    tickerProvider.setState(() {
      showOpenBtn = false;
    });
    translateController.forward();
  }
}

这样就实现了点击金币后,金币旋转 2 秒后开启红包。

结果弹出

结果页是一个新的界面,在红包开启时同步执行,并且拥有一个渐变动画,路由跳转时添加动画实现,代码如下:

void onOpen(){
  Navigator.push(
    context,
    PageRouteBuilder(
      transitionDuration: const Duration(seconds: 1),
      pageBuilder: (context, animation, secondaryAnimation) =>
      FadeTransition(
        opacity: animation,
        child: const ResultPage(),
      )
    )
  );
}

RedPacket 添加一个红包开启时的回调和红包动画完成时的回调,前者用于跳转结果页,后者用于移除 Overlay

OverlayEntry? entry;
void showRedPacket(BuildContext context, Function? onOpen){
  entry = OverlayEntry(builder: (context) => RedPacket(onFinish: _removeRedPacket, onOpen: onOpen,));
  Overlay.of(context)?.insert(entry!);
}

void _removeRedPacket(){
  entry?.remove();
  entry = null;
}

在金币旋转动画停止时调用:

void stop() async{
  if(angleController.isAnimating){
   /// ...
    translateController.forward();
    onOpen?.call();
  }
}

红包动画完成时调用 onFinish 回调, 给红包最后的平移动画添加监听来实现:

translateController.addStatusListener((status) {
  if(status == AnimationStatus.completed){
    onFinish?.call();
  }
});

OK,大功告成,再看看最终的效果:

以上就是Flutter利用Canvas模拟实现微信红包领取效果的详细内容,更多关于Flutter Canvas微信领红包的资料请关注我们其它相关文章!

(0)

相关推荐

  • flutter 实现点击下拉栏微信右上角弹出窗功能

    先看效果实现 需求分析 这个是使用 PopupRoute这个路由类进行实现 大概原理就是利用PopupRpute这个类进行改造,然后自定义一个页面,页面内镶嵌一个动画类,用来实现缩放动画 大概分为三部分,PopupRoute改造,弹出页面设置,动画类设置. 为什么选择PopupRoute? 可以镶嵌在flutter本身的路由管理之中 也就是逻辑操作都是正常的页面管理,可以手动管理,也可以用路由返回直接关掉,不会影响原有页面和布局 第一步,改造PopupRoute类 import 'package

  • Flutter实现仿微信底部菜单栏功能

    import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget{ @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( body: MyHomePage(), ), ); } } class MyHomePage extends Sta

  • 如何利用Flutter仿写微信搜索页效果

    目录 效果图 顶部搜索栏 SearchBar 实现细节 左边搜索框实现 右边取消按钮实现 内容的检索 内容的传递 内容的检索 搜索列表实现 总结 效果图 如上图所示,我们用 Flutter 来仿写搜索页面,这里聊天首页点击搜索栏会跳转到搜索页,搜索页面包含顶部搜索框跟底部 ListView,在搜索框内我们输入搜索词会检索聊天列表模型中 name 属性中包含搜索词的模型,并在底部列表中展示,且搜索词高亮显示.下面我们分别来介绍下这些功能的实现. 顶部搜索栏 class SearchBar exte

  • flutter仿微信底部图标渐变功能的实现代码

    先给大家展示下效果图,感觉不错请参考实例代码. 实现思路 在flutter中,如果想实现上面的页面切换效果,必然会想到pageView.pageView的controller可以监听到pageView的滚动事件,也可以获取pageView滚动的位置,所以我们在滚动事件中根据位置去改变对应的图标颜色就可以实现了. 改变图标颜色 图标是从微信中提取出来的,都是webp格式的图片.要改变图片颜色可以使用ImageIcon这个组件. ImageIcon会把一张图片变成单色图片,所以只要图片没有多色的要求

  • Flutter 仿微信支付界面

    左侧是微信支付的界面,右侧是开发完成后的效果,图标是从 iconfont 上下载的.首先介绍一下本篇涉及到的组件. 带装饰效果的 Container 实际过程中我们经常会遇到一个容器需要额外的样式,例如圆角,背景色等.在 Flutter 中,对于各种容器都有一个 decoration 的属性,可以用于装饰容器.典型的用法有设置背景色.圆角.边框和阴影等,其中背景色可以使用渐变色.decoration 是一个 Decoration 对象,最常用的是 BoxDecoration,BoxDecorat

  • Flutter实现微信朋友圈功能

    本文实例为大家分享了Flutter实现微信朋友圈功能的具体代码,供大家参考,具体内容如下 今天给大家实现一下微信朋友圈的效果,下面是效果图 下面还是老样子,还是以代码的方式进行讲解 import 'package:dio/dio.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:nursery_school_gardener/view/ma

  • Flutter利用Canvas模拟实现微信红包领取效果

    目录 前言 效果 红包弹出 红包布局 上半部分 下半部分 金币绘制 金币文字绘制 头像和文字 金币旋转 红包开启 结果弹出 前言 前面写了一篇Flutter利用Canvas绘制精美表盘效果详解的文章,对 Flutter 中的 Canvas 使用有了进一步的理解,就想着再用 Canvas 实现一个什么样的效果来加深一下对 Canvas 使用的理解,这个时候正好看到群里有人发红包,于是就想着能不能在 Flutter 中使用 Canvas 实现微信领取红包的效果?想到就做,知行合一,经过几天空余时间的

  • Flutter利用Canvas绘制精美表盘效果详解

    目录 前言 初始化 面板 刻度 刻度线 刻度值 指针 时针 分针 秒针 动起来 前言 趁着周末空闲时间使用 Flutter 的 Canvas制作了一个精美表盘. 最终实现的效果还不错,如下: 前面说到使用 Canvas 实现该表盘效果,而在 Flutter 中使用 Canvas 更多的则是继承 CustomPainter 类实现 paint 方法,然后在 CustomPaint 中使用自定义实现的 CustomPainter. 比如这里创建的 DialPainter 使用如下: @overrid

  • Android仿硬币转动微信红包动画效果

    项目需要研究了一下微信红包动画,即硬币转动的效果,原理其实就是三张不同角度的图片利用AnimationDrawable帧动画进行播放,在参考了案例之后,给自己记录一下完成的过程. 1,在XML文件中定义动画: 步骤如下: ①新建 Android 项目 ②在drawable目录中新建一个anim.xml(注意文件名小写) <?xml version="1.0" encoding="utf-8"?> <animation-list xmlns:andr

  • Android仿打开微信红包动画效果实现代码

    首先看下效果: 实现原理: 准备3张不同角度的图片,通过AnimationDrawable帧动画进行播放即可 代码实现: 1.编写动画xml文件: <?xml version="1.0" encoding="utf-8"?> <animation-list xmlns:android="http://schemas.android.com/apk/res/android" android:oneshot="false&

  • 利用Vue模拟实现element-ui的分页器效果

    目录 1. 思路 1.1客户端 1.2服务器 2.服务器 2.1创建数据 2.2创建接口 3.客户端 3.1创建静态页面 3.2请求数据 3.3解析逻辑 4.总结 1. 思路 1.1客户端 利用vue相关的知识搭建基本页面, 上面四张图片,下面是分页器基本 效果静态显示.点击分页器实现 不同数据请求,显示不同图片 1.2服务器 根据客户端发送的数据进行数据 分段传输,比如,点击的是那一页 分页器每次需要展示几个数据, 2.服务器 创建数据(存放图片的网址,以及id) 创建接口,发送数据. 2.1

  • Canvas实现微信红包照片效果

    本篇文章来源慕课网课程<canvas玩转红包照片>,用canvas及css3结合,实现红包照片的效果,并不做支付过程,代替的是使用2个按钮,显示清晰图片和重置圆圈可见区域.未做移动的屏幕适应 微信红包照片效果图一张模糊图片,只有鱼哥小圆圈的区域是清晰可见 原理分析: 1.先在页面放置一张原图片image,用css3的filter做模糊处理 2.在图片区域上方放置一个与图片image大小一样的canvas,放置整张清晰图片 3.通过canvas的图片剪辑方法,剪辑出一个圆圈区域,就达到了只显示一

  • java模拟实现微信红包算法

    本文实例为大家分享了java实现微信红包算法的具体代码,供大家参考,具体内容如下 实现过程 1.代码如下(示例): import java.math.BigDecimal; import java.util.ArrayList; public class GenRedPacket { static ArrayList<BigDecimal> list = new ArrayList<BigDecimal>();; public static BigDecimal bianLiJia

  • 利用canvas实现的加载动画效果实例代码

    前言 以前在浏览文章时,看到过一个Android的加载效果,觉得挺好看的,于是自己就模仿了一个.下面话不多说,我们直接来看看详细的介绍吧. 运行效果图 分析下这个效果: 1.可以把这四个方块标号 2.这个运动效果其实只用分解为两段动画,一段是上下移动,一段是左右移动. 示例代码: /*核心代码*/ /*分析动画,其实动画只有两次的执行*/ var for_index = 1;//记录当前执行动画的序列 var interval = setInterval(function(){ if(for_i

  • 微信小程序利用canvas 绘制幸运大转盘功能

    小程序对 canvas api 跟h5的不太一致 ,所以这个搞的比较久,不多说,先贴代码 Page({ /** * 页面的初始数据 */ data: { awardsConfig: {}, restaraunts: [], //大转盘奖品信息 }, /** * 生命周期函数--监听页面加载 */ onLoad: function (options) { var self = this; wx.getSystemInfo({ //获取系统信息成功,将系统窗口的宽高赋给页面的宽高 success:

随机推荐