简述
今天给大家分享一个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!,
),
);
}
}