效果图
preview.gif案例
import 'package:flutter/material.dart';
///语音识别
class SpeakPage extends StatefulWidget {
@override
_SpeakPageState createState() => _SpeakPageState();
}
///需要继承TickerProvider,如果有多个AnimationController,则应该使用TickerProviderStateMixin。
class _SpeakPageState extends State<SpeakPage> with SingleTickerProviderStateMixin {
String speakTips = '长按说话';
String speakResult = '';
Animation<double> animation;
AnimationController controller;
@override
void initState() {
//创建 AnimationController 对象 AnimationController 用于控制动画,它包含动画的启动forward()、停止stop() 、反向播放 reverse()等方法。
controller = AnimationController(
vsync: this, duration: Duration(milliseconds: 1000));
//CurvedAnimation来指定动画的曲线;Curves.easeIn 开始慢,后面快
animation = CurvedAnimation(parent: controller, curve: Curves.easeIn)
..addStatusListener((status) {
if (status == AnimationStatus.completed) {
// 动画结束时,反转从尾到头播放,结束的状态是 dismissed
controller.reverse();
} else if (status == AnimationStatus.dismissed) {
// 重新从头到尾播放
controller.forward();
}
});
super.initState();
}
@override
void dispose() {
//路由销毁时需要释放动画资源
controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
padding: EdgeInsets.all(30),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
_topItem(),
_bottomItem(),
],
),
),
);
}
///顶部视图
_topItem() {
return Column(
children: <Widget>[
Padding(
padding: EdgeInsets.fromLTRB(0, 30, 0, 30),
child: Text(
'你可以这样说',
style: TextStyle(fontSize: 16, color: Colors.black54),
),
),
Text(
'故宫门票\n北京一日游\n迪士尼乐园',
textAlign: TextAlign.center,
style: TextStyle(fontSize: 15, color: Colors.grey),
),
Padding(
padding: EdgeInsets.all(20),
child: Text(
speakResult,
style: TextStyle(color: Colors.blue),
),
),
],
);
}
///底部视图
_bottomItem() {
//根据父容器宽高的百分比来设置子组件宽高
return FractionallySizedBox(
widthFactor: 1,
child: Stack(
children: <Widget>[
GestureDetector(
onTapDown: (e) {
_speakStart();
},
onTapUp: (e) {
_speakStop();
},
child: Column(
children: <Widget>[
Padding(
padding: EdgeInsets.all(10),
child: Text(
speakTips,
style: TextStyle(color: Colors.blue, fontSize: 12),
),
),
Stack(
children: <Widget>[
Container(
//占坑,避免动画执行过程中导致父布局大小变化
width: MIC_SIZE,
height: MIC_SIZE,
),
Center(
child: AnimatedMic(
animation: animation,
),
),
],
),
],
),
),
//右边关闭按钮
Positioned(
right: 0,
bottom: 20,
child: GestureDetector(
onTap: () {
Navigator.pop(context);
},
child: Icon(
Icons.close,
size: 30,
color: Colors.grey,
),
),
),
],
),
);
}
void _speakStart() {
controller.forward();
}
void _speakStop() {
controller.reset();
controller.stop();
}
}
const double MIC_SIZE = 80;
///AnimatedWidget会自动调用addlistener和setState
class AnimatedMic extends AnimatedWidget {
//透明度改变动画
static final _opacityTween = Tween<double>(begin: 1, end: 0.5);
//大小改变动画
static final _sizeTween = Tween<double>(begin: MIC_SIZE, end: MIC_SIZE - 20);
AnimatedMic({Key key, Animation<double> animation})
: super(key: key, listenable: animation);
@override
Widget build(BuildContext context) {
// 外部传递过来的 Animation 对象
final Animation<double> animation = listenable;
return Opacity(
opacity: _opacityTween.evaluate(animation),
child: Container(
//evaluate(Animation<double> animation) 获取动画当前映射值
height: _sizeTween.evaluate(animation),
width: _sizeTween.evaluate(animation),
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(MIC_SIZE / 2),
),
child: Icon(
Icons.mic,
color: Colors.white,
size: 30,
),
),
);
}
}
上面代码可以看出动画示例步骤:
- 初始化一个
AnimationController
对象
AnimationController controller = AnimationController(
vsync: this, duration: Duration(milliseconds: 1000));
- 初始化一个
Animation
对象, 并将AnimationController
作为参数传递进去。
//CurvedAnimation来指定动画的曲线;Curves.easeIn 开始慢,后面快
animation = CurvedAnimation(parent: controller, curve: Curves.easeIn)
..addStatusListener((status) {
if (status == AnimationStatus.completed) {
// 动画结束时,反转从尾到头播放,结束的状态是 dismissed
controller.reverse();
} else if (status == AnimationStatus.dismissed) {
// 重新从头到尾播放
controller.forward();
}
});
- 调用
AnimationController
的forward()
方法执行动画
controller.forward();
- 在
Widget
的dispose()
方法中调用释放资源
controller.dispose();
AnimationStatus.completed
表示动画在结束时停止的状态,这个时候我们让动画反向执行(从后往前);AnimationStatus.dismissed
表示动画在开始时就停止的状态,这个时候我们让动画正常执行(从前往后)。这样就可以让动画无限执行了。
AnimatedWidget
使用 AnimatedWidget
来实现动画,就不需要给动画 addListener(...) 和 setState((){}) 了,AnimatedWidget 自己会使用当前 Animation 的 value 来绘制自己。当然,这里 Animation 我们是以构造参数的方式传递进去的。
代码如下:
const double MIC_SIZE = 80;
///AnimatedWidget会自动调用addlistener和setState
class AnimatedMic extends AnimatedWidget {
//透明度改变动画
static final _opacityTween = Tween<double>(begin: 1, end: 0.5);
//大小改变动画
static final _sizeTween = Tween<double>(begin: MIC_SIZE, end: MIC_SIZE - 20);
AnimatedMic({Key key, Animation<double> animation})
: super(key: key, listenable: animation);
@override
Widget build(BuildContext context) {
// 外部传递过来的 Animation 对象
final Animation<double> animation = listenable;
return Opacity(
opacity: _opacityTween.evaluate(animation),
child: Container(
//evaluate(Animation<double> animation) 获取动画当前映射值
height: _sizeTween.evaluate(animation),
width: _sizeTween.evaluate(animation),
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(MIC_SIZE / 2),
),
child: Icon(
Icons.mic,
color: Colors.white,
size: 30,
),
),
);
}
}
上述代码中,我们定义了一个 AnimatedMic
继承了 AnimatedWidget
,然后定义了一个构造方法,注意,构造方法中我们定义了一个 Animation
然后把这个 Animation
传到父类(super)中去了,我们可以看看listenable: animation
这个参数,是一个 Listenable
类型,如下:
/// The [Listenable] to which this widget is listening.
///
/// Commonly an [Animation] or a [ChangeNotifier].
final Listenable listenable;
然后再看看 Animation 类:
abstract class Animation<T> extends Listenable implements ValueListenable<T> {
...
}
可以看到 Animation
是 Listenable
的子类,所以在我们自定义的 AnimatedMic
类中可以传一个 Animation
类型的的参数作为父类中 listenable
的值。
使用我们上面定义的 AnimatedMic
也很简单,直接作为 widget
使用就好,部分代码如下:
Stack(
children: <Widget>[
Container(
//占坑,避免动画执行过程中导致父布局大小变化
width: MIC_SIZE,
height: MIC_SIZE,
),
Center(
child: AnimatedMic(
animation: animation,
),
),
],
)
可以看出我们在实例化 AnimatedMic
的时候传入了一个 Animation
对象。
网友评论