Flutter svga 播放及图片替换

前言

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

引入

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

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

flutter pub add svgaplayer_flutter
// 或者在pubspec.yaml文件中添加
svgaplayer_flutter: ^2.2.0

实现效果展示

Flutter svga 播放及图片替换

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

代码实现

1. 引入svga

import 'package:svgaplayer_flutter/svgaplayer_flutter.dart';

2. 初始化svga controller

// 使用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资源

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

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

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

 Flutter svga 播放及图片替换

4. 分析svga资源

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

4.1. 确认图片规格

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

Flutter svga 播放及图片替换

Flutter svga 播放及图片替换

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

4.2. 将文字生成图片流

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

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方法更换,这样就形成了我们上面看到的效果

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");

全代码展示:

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播放的问题处理,希望能够帮到大家。如果各位有更好的方案或者更高级的写法欢迎多多指教

原文链接:https://juejin.cn/post/7355826327356850202 作者:前端李逍

(0)
上一篇 2024年4月10日 下午5:08
下一篇 2024年4月11日 上午10:00

相关推荐

发表回复

登录后才能评论