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