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左右,是不是非常的簡單而且易懂~如果需要一些調整,可以自行增加

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章