Flutter svga 播放及图片替换

前言

SVGA是一种动画格式,可以兼容安卓、ios和web,可以实现很多复杂的动画,这样开发就不用头疼canvas来实现动画时的卡顿优化问题了,那么接下来简单介绍一下再Flutter中怎么使用svga

引入

首先目前的官方SVGA插件不支持Dart 3x 版本,想要在最新版本使用SVGA需要自己去封装插件实现或者降低Dart版本。

导入地址:pub.dev/packages/sv...

csharp 复制代码
flutter pub add svgaplayer_flutter
// 或者在pubspec.yaml文件中添加
svgaplayer_flutter: ^2.2.0

实现效果展示

如上所示需要播放炸弹动画并且改变动画内部的值,如果按照一般的方案去实现的话,在动图上方放一个文本区域写文本,那么炸弹的膨胀和伸缩过程并不会同步到文字上,需要文字也有效果的话,那么就需要使用svga的更换图层的功能

代码实现

1. 引入svga

arduino 复制代码
import 'package:svgaplayer_flutter/svgaplayer_flutter.dart';

2. 初始化svga controller

less 复制代码
// 使用svga需要添加控制器并且需要再当前的类中注入mixin
class _MyHomePageState extends State<MyHomePage>
    with SingleTickerProviderStateMixin {
  // 引入svga controller
  late SVGAAnimationController controller;
  int num = 20;
  @override
  void initState() {
    controller = SVGAAnimationController(vsync: this);
    // 加载svga
    loadAnimation();
    super.initState();
  }
  ......
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        // svga区域
        child: SizedBox(
          width: 300,
          height: 300,
          child: SVGAImage(controller),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

3. 引入svga资源

ini 复制代码
void loadAnimation() async {
  final videoItem = await SVGAParser.shared.decodeFromAssets("assets/1.svga");
  controller.videoItem = videoItem;
  controller.repeat();
}

到此,svga已经可以展示在页面上了,如下图所示,整个svga的接入已经完成了,想要实现动态替换上面的文字,

需要使用官方提供的方法controller.videoItem?.dynamicItem.setImage去实现

4. 分析svga资源

需要动态替换文字,实际上是不停的进行切图,但是我们需要切换的是百分比数字,我们不可能切100张图片然后去检索替换,这样的方案需要UI小姐姐去切100张图,如果真这么做了,那么UI小姐姐应该已经开始磨刀霍霍了。因此,我们需要自己去生成对应的svga替换图片

4.1. 确认图片规格

首先我们需要和制作svga素材的同学预定好需要替换的图层,需要帮我们留好替换的位置图层,及相应的名称,这里推荐我们使用官方推出的预览网站,去查看需要替换的图层名称和规格:
svga.dev/svga-previe...

我们可以看到我们的小姐姐已经帮我们留好了名为zhadan的图片,并且这个预览网站也告诉了我们图片的大小规格,当然这里也需要和UI小姐姐确认一下最终规格。

4.2. 将文字生成图片流

图片的生成我们使用ui.PictureRecorder()来借助canvas绘制来完成,具体代码如下:

less 复制代码
Future<ui.Image> randerImageByText(
    {required String content,
    required double fontSize,
    Color? color = Colors.white,
    required double width,
    required double height,
    TextAlign textAlign = TextAlign.justify}) async {
  var recorder = ui.PictureRecorder();
  Canvas canvas = Canvas(recorder);
  Paint paint = Paint()..color = Colors.transparent;
  TextPainter textPainter = TextPainter(
    textAlign: textAlign,
    text: TextSpan(
        text: content,
        style: TextStyle(
          color: color,
          fontSize: fontSize,
          // fontFamily: 'DingTalk',
        )),
    textDirection: TextDirection.rtl,
    textWidthBasis: TextWidthBasis.longestLine,
    ellipsis: '...',
    maxLines: 1,
  )..layout(maxWidth: width, minWidth: width);

  double top = (height - textPainter.height) / 2;
  canvas.drawRect(
      Rect.fromLTRB(0, top, 0 + width, textPainter.height), paint);
  // 可以传入minWidth,maxWidth来限制它的宽度,如不传,文字会绘制在一行
  textPainter.paint(canvas, Offset(0, top));
  Picture picture = recorder.endRecording();
  return await picture.toImage(width.toInt(), height.toInt());
}

4.3. 获取到文件流替换图片

我们将获取到的图片流使用setImage方法更换,这样就形成了我们上面看到的效果

php 复制代码
ui.Image? image = await randerImageByText(
    content: "$num%",
    color: Colors.white,
    width: double.parse("90"),
    height: double.parse("38"),
    fontSize: double.parse("30"),
    textAlign: TextAlign.center);
controller.videoItem?.dynamicItem.setImage(image, "zhadan");

全代码展示:

arduino 复制代码
import 'dart:ui';

import 'package:flutter/material.dart';
import 'package:svgaplayer_flutter/svgaplayer_flutter.dart';
import 'dart:ui' as ui;

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        // This is the theme of your application.
        //
        // Try running your application with "flutter run". You'll see the
        // application has a blue toolbar. Then, without quitting the app, try
        // changing the primarySwatch below to Colors.green and then invoke
        // "hot reload" (press "r" in the console where you ran "flutter run",
        // or simply save your changes to "hot reload" in a Flutter IDE).
        // Notice that the counter didn't reset back to zero; the application
        // is not restarted.
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});
  final String title;
  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage>
    with SingleTickerProviderStateMixin {
  late SVGAAnimationController controller;
  int num = 20;
  @override
  void initState() {
    controller = SVGAAnimationController(vsync: this);
    // myLog("===========${widget.giftUrl}");
    loadAnimation();
    super.initState();
  }

  @override
  void dispose() {
    controller.dispose();
    super.dispose();
  }

  void loadAnimation() async {
    final videoItem = await SVGAParser.shared.decodeFromAssets("assets/1.svga");
    controller.videoItem = videoItem;
    ui.Image? image = await randerImageByText(
        content: "$num%",
        color: Colors.white,
        width: double.parse("90"),
        height: double.parse("38"),
        fontSize: double.parse("30"),
        textAlign: TextAlign.center);
    controller.videoItem?.dynamicItem.setImage(image, "zhadan");
    controller.repeat();
  }

  void _incrementCounter() async {
    setState(() {
      num++;
    });
    ui.Image? image = await randerImageByText(
        content: "$num%",
        color: Colors.white,
        width: double.parse("90"),
        height: double.parse("38"),
        fontSize: double.parse("30"),
        textAlign: TextAlign.center);
    controller.videoItem?.dynamicItem.setImage(image, "zhadan");
  }

  // 根据文字生成图片流
  Future<ui.Image> randerImageByText(
      {required String content,
      required double fontSize,
      Color? color = Colors.white,
      required double width,
      required double height,
      TextAlign textAlign = TextAlign.justify}) async {
    var recorder = ui.PictureRecorder();
    Canvas canvas = Canvas(recorder);
    Paint paint = Paint()..color = Colors.transparent;
    TextPainter textPainter = TextPainter(
      textAlign: textAlign,
      text: TextSpan(
          text: content,
          style: TextStyle(
            color: color,
            fontSize: fontSize,
            // fontFamily: 'DingTalk',
          )),
      textDirection: TextDirection.rtl,
      textWidthBasis: TextWidthBasis.longestLine,
      ellipsis: '...',
      maxLines: 1,
    )..layout(maxWidth: width, minWidth: width);

    double top = (height - textPainter.height) / 2;
    canvas.drawRect(
        Rect.fromLTRB(0, top, 0 + width, textPainter.height), paint);
// 可以传入minWidth,maxWidth来限制它的宽度,如不传,文字会绘制在一行
    textPainter.paint(canvas, Offset(0, top));
    Picture picture = recorder.endRecording();
    return await picture.toImage(width.toInt(), height.toInt());
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: SizedBox(
          width: 300,
          height: 300,
          child: SVGAImage(controller),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

以上便是我在进行项目开发中的一些svga播放的问题处理,希望能够帮到大家。如果各位有更好的方案或者更高级的写法欢迎多多指教

相关推荐
傅里叶4 小时前
Flutter项目使用 buf.build
flutter
恋猫de小郭5 小时前
iOS 26 开始强制 UIScene ,你的 Flutter 插件准备好迁移支持了吗?
android·前端·flutter
yuanlaile6 小时前
Flutter开发HarmonyOS鸿蒙App商业项目实战已出炉
flutter·华为·harmonyos
CodeCaptain6 小时前
可直接落地的「Flutter 桥接鸿蒙 WebSocket」端到端实施方案
websocket·flutter·harmonyos
stringwu7 小时前
Flutter 中的 MVVM 架构实现指南
前端·flutter
消失的旧时光-194319 小时前
Flutter 异步体系终章:FutureBuilder 与 StreamBuilder 架构优化指南
flutter·架构
消失的旧时光-19431 天前
Flutter 异步 + 状态管理融合实践:Riverpod 与 Bloc 双方案解析
flutter
程序员老刘1 天前
Flutter版本选择指南:避坑3.27,3.35基本稳定 | 2025年10月
flutter·客户端
—Qeyser1 天前
Flutter网络请求Dio封装实战
网络·flutter·php·xcode·android-studio