简述
今天给大家分享一个Flutter的表情雨的实现,具体的效果见下图,这个效果其实是工作中的一个小小的需求,这里面单独拿出来讲讲下这个效果的实现思路。
在以前特别是新手,遇到一些动画效果都是拿到需求后,在网络上去找寻类似的效果,然后修改修改就可以了,不知道你们是不是呢?其实从chatgpt出来以后,很多时候对于这种效果我们其实直接就可以问下gpt,gpt会给我们一些思路,只要我们表达的清晰,配合gpt我们会很快实现这个效果。
效果图
分析
我们如何去问gpt,才能让gpt给出我们想要的结果呢?当然在以前我们都是自己先了解这个效果属于什么效果,比如说 “跑马灯”、“画廊”等等,然后在网络搜索这个关键字查询。但是现在我们不是单纯的搜关键字了,而是用自己的语言把效果用话语表达出来,让gpt知道到底怎么写代码,总结来说你就是产品,gpt是开发人员,你要把产品需求告诉gpt去开发。
ChatGpt问答
用户问:请使用Flutter实现一个widget,这个widget支持设置宽高,其内容是由n个表情从上往下落下,这些表情下落的时机是间隔落下,下落过程中透明度在高度一半的时候透明度逐渐隐藏。
注意: 这里我使用的gpt是3.5版本。
gpt回答代码如下:
import 'dart:async'; import 'dart:math'; import 'package:flutter/material.dart'; class FallingEmojisWidget extends StatefulWidget { final double width; final double height; final List<String> emojis; final Duration interval; const FallingEmojisWidget({ Key? key, required this.width, required this.height, required this.emojis, this.interval = const Duration(milliseconds: 500), }) : super(key: key); @override _FallingEmojisWidgetState createState() => _FallingEmojisWidgetState(); } class _FallingEmojisWidgetState extends State<FallingEmojisWidget> with TickerProviderStateMixin { late AnimationController _animationController; late Timer _timer; List<Widget> _emojiWidgets = []; @override void initState() { super.initState(); _animationController = AnimationController( vsync: this, duration: Duration(milliseconds: 1000), ); _timer = Timer.periodic(widget.interval, (_) => _addEmoji()); } @override void dispose() { _animationController.dispose(); _timer.cancel(); super.dispose(); } void _addEmoji() { setState(() { final emoji = widget.emojis[Random().nextInt(widget.emojis.length)]; _emojiWidgets.add(_buildEmojiWidget(emoji)); }); } Widget _buildEmojiWidget(String emoji) { final initialOffset = Offset( Random().nextDouble() * widget.width, -50, ); final targetOffset = Offset( initialOffset.dx, widget.height / 2, ); final animation = Tween<Offset>( begin: initialOffset, end: targetOffset, ).animate(CurvedAnimation( parent: _animationController, curve: Curves.easeInOut, )); return AnimatedBuilder( animation: _animationController, builder: (context, child) { final opacity = _animationController.value < 0.5 ? (_animationController.value * 2) : (1 - (_animationController.value - 0.5) * 2); return Positioned( left: animation.value.dx, top: animation.value.dy, child: Opacity( opacity: opacity, child: Text( emoji, style: TextStyle(fontSize: 24), ), ), ); }, ); } @override Widget build(BuildContext context) { return Container( width: widget.width, height: widget.height, child: Stack( children: _emojiWidgets, ), ); } }`
gpt第一次回答你的代码其实已经告诉你实现思路了,比如上面的代码,通过gpt的回答我们知道可以使用Stack控件,然后在Stack中添加我们想要的数量的表情widget,然后通过AnimatedBuilder+Positioned实现表情下落的效果。是不是很6,从开始没有思路立马就知道如何实现了,你其实可以直接copy gpt的回答然后运行看看,在此基础上修改修改就ok了。下面是我修改后的完整代码。
import 'dart:math'; import 'package:flutter/material.dart'; /// 表情下落动画 class EmojiAnimWidget extends StatefulWidget { const EmojiAnimWidget( {Key? key, required this.width, required this.height, this.emojiNum = 6, this.duration = const Duration(seconds: 5)}) : super(key: key); final double width; final double height; final int emojiNum; final Duration duration; @override State<EmojiAnimWidget> createState() => EmojiAnimWidgetState(); } class EmojiAnimWidgetState extends State<EmojiAnimWidget> with SingleTickerProviderStateMixin { late AnimationController _animationController; List<Widget>? _emojis; @override void initState() { super.initState(); _animationController = AnimationController( vsync: this, duration: widget.duration, ); } @override void dispose() { _animationController.dispose(); super.dispose(); } /// 开始表情动画 void startEmojiAnim(String emoji) { _emojis = List.generate(widget.emojiNum, (index) { var delay = Random().nextInt(150).toDouble() + 50; var fontSize = Random().nextInt(9).toDouble() + 18.0; var animation = Tween<double>( begin: 0.0, end: 1.0, ).animate(CurvedAnimation( parent: _animationController, curve: Interval(delay / 500, (delay + 300) / 500, curve: Curves.linear), )); return AnimatedBuilder( animation: _animationController, builder: (context, child) { var opacity = 1 - animation.value; return Positioned( left: (widget.width - 32) / widget.emojiNum * index, top: -delay + animation.value * (widget.height + delay), child: Opacity( opacity: (opacity > 0.5) ? 1 : opacity, child: Container( height: 50, color: Colors.transparent, alignment: Alignment.center, child: Text( emoji, style: TextStyle(fontSize: fontSize, height: 1.2), textAlign: TextAlign.center, ), ), ), ); }, ); }); setState(() { _animationController.stop(); _animationController.repeat(); _animationController.forward(); }); } @override Widget build(BuildContext context) { if (_emojis == null) return Container(); return Container( alignment: Alignment.center, padding: const EdgeInsets.only(left: 16, right: 16), width: widget.width, height: widget.height, child: Stack( clipBehavior: Clip.none, children: _emojis!, ), ); } }