Flutter Widget粒子/沙化效果

这是一个让widget粒子化的效果,文章借鉴自掘金的博客。

首先,大家一定要思考,如何去做才能实现这样一个效果,如何去实现最为简单。因为任何一个功能的实现方式一定是多样的,举一反三也是一件很有趣的事儿。

首先考虑,一个widget里面肯定有多个child,那么最优的做法,一定是把widget的画面直接截下来,这样就无需管理它里面子child的事儿了。有了截图之后,要让它粒子化,那么我是需要去获取它每个pixel的颜色,之后让它们进行无规则运动。那怎么样才能拆分他们呢?

答案是把他们放到不同的layer上,然后让这些layer载体,向各个方向进行运动,就可以模拟出四散的效果了。

所以步骤是这样的:

1.获取widget的截图

2.将截图上的像素点分配到不同的layer上

3.让layer进行无规则运动。

看上去是不是很简单!没错就是非常简单。

首先第一步,要拿到widget的截图。flutter的显示就是一个大型绘制现场,所有widget都会变成render。

class ParticleBody extends StatefulWidget {
  Widget child;

  //动画时间
  var duration;

  //图层数量
  var numberLayers;

  ParticleBody({this.duration = const Duration(seconds: 2),
    this.child,
    this.numberLayers = 10});

  @override
  _ParticleBodyState createState() => _ParticleBodyState();
}

class _ParticleBodyState extends State<ParticleBody>
    with TickerProviderStateMixin {
  GlobalKey _globalKey = GlobalKey();

  List<Widget> layers = [];

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: <Widget>[
        ...layers,
        GestureDetector(
          onTap: () {
            _grain();
          },
          child: Opacity(
            opacity: (1 - _controller.value),
            child: RepaintBoundary(
              key: _globalKey,
              child: widget.child,
            ),
          ),
        )
      ],
    );
  }

}

使用GestureDetector监听点击手势,用RepaintBoundary去截取图像。

第一步要去获取相应的图片信息

//获取图片信息
  Future<image.Image> _getImgInfo() async {
    RenderRepaintBoundary repaintBoundary =
    _globalKey.currentContext.findRenderObject();
    var img = await repaintBoundary.toImage();
    var byteData = await img.toByteData(format: ImageByteFormat.png);
    var uint8list = byteData.buffer.asUint8List();
    return image.decodeImage(uint8list);
  }

这里使用Image类的解码类,获得相应的图片信息。

获得图片信息后我们就需要将图片上的像素点,分配到一张张的layer上,然后再让它进行随机运动,模拟无规则。

//分配像素点
  _separate(List<image.Image> blankImage, width, height, image.Image imgInfo) {
    for (int x = 0; x < width; x++) {
      for (int y = 0; y < height; y++) {
        var pixel = imgInfo.getPixel(x, y);
        if (pixel == 0) continue;
        int index = Random().nextInt(widget.numberLayers);
        blankImage[index].setPixel(x, y, pixel);
      }
    }
  }

大家可以想一下,让它展示出粒子效果需要几个动画?又要几种方式?

这了我选择两种动画,移动+消失,如果要爆炸效果,可以选用移动+缩放


  //把图片画到图层上
  Widget _getLayer(image.Image imgInfo) {
    Uint8List uint8list = Uint8List.fromList(image.encodePng(imgInfo));

    CurvedAnimation curvedAnimation =
    CurvedAnimation(parent: _controller, curve: Curves.easeIn);

    Animation<Offset> animation = Tween(
        begin: Offset.zero,
        end: Offset(30, -50) +
            Offset(30, 30).scale((Random().nextDouble() - 0.5) * 2,
                (Random().nextDouble() - 0.5) * 2))
        .animate(_controller)
      ..addListener(() {
        setState(() {});
      });

    return AnimatedBuilder(
      child: Image.memory(uint8list),
      builder: (context, child) {
        return Transform.translate(
          offset: animation.value,
          child: animation.isCompleted ? null : Opacity(
            opacity: cos(curvedAnimation.value * pi / 2), //1->0
            child: child,
          ),
        );
      },
      animation: _controller,
    );
  }

使用动画记得要混入动画,然后使用AnimationController。

 AnimationController _controller;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(vsync: this, duration: widget.duration)
      ..addListener(() {
        setState(() {});
      });
  }

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

最后增加相应的点击事件,就OK了

//点击事件
  _grain() async {
    var imgInfo = await _getImgInfo();
    var width = imgInfo.width;
    var height = imgInfo.height;
    List<image.Image> blankImage = List.generate(
        widget.numberLayers, (index) => image.Image(width, height));
    _separate(blankImage, width, height, imgInfo);

    layers = blankImage.map((e) => _getLayer(e)).toList();

    setState(() {});

    _controller.forward(from: 0);
  }

代码行数应该在130左右,是不是非常的简单而且易懂~如果需要一些调整,可以自行增加

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值