Shake/jitter TabBar Flutter 动画
Shake/jitter animation for TabBar Flutter
正在尝试获得上述动画的所需输出。尝试使用 AnimatedBuilder
和 Transform
或 Matrix of z-axis
制作此动画。但未能如愿。这是我的代码。角度、收缩、晃动的部分没弄对
我的主页
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin {
late TabController _tabController;
late void Function() _memberCaller;
late void Function() _voucherCaller;
late void Function() _rewardCaller;
late void Function() _drinksCaller;
double degree = 0;
@override
void initState() {
super.initState();
_tabController = TabController(length: 4, vsync: this);
}
@override
void dispose() {
_tabController.dispose();
super.dispose();
}
void _callMethoCaller(int index) {
switch (index) {
case 0:
_memberCaller.call();
break;
case 1:
_voucherCaller.call();
break;
case 2:
_rewardCaller.call();
break;
case 3:
_drinksCaller.call();
break;
}
}
@override
Widget build(BuildContext context) {
List<Widget> _tabs(int index) => [
AnimationTab(
iconName: Icons.card_giftcard,
label: 'Membership',
functionCaller: (void Function() method) {
_memberCaller = method;
}),
AnimationTab(
iconName: Icons.confirmation_num_outlined,
label: 'Voucher',
functionCaller: (void Function() method) {
_voucherCaller = method;
}),
AnimationTab(
iconName: Icons.emoji_events_outlined,
label: 'Rewards',
functionCaller: (void Function() method) {
_rewardCaller = method;
}),
AnimationTab(
iconName: Icons.wine_bar_outlined,
label: 'Drinks',
functionCaller: (void Function() method) {
_drinksCaller = method;
}),
];
return Scaffold(
appBar: AppBar(
centerTitle: true,
bottom: PreferredSize(
child: Container(
child: TabBar(
onTap: _callMethoCaller,
controller: _tabController,
labelPadding: EdgeInsets.only(top: 5.0, bottom: 2.0),
indicatorColor: Colors.black,
tabs: List.generate(4, (index) => _tabs(index)[index]),
),
decoration: BoxDecoration(
color: Colors.white,
boxShadow: const [
BoxShadow(
color: Colors.white,
spreadRadius: 5.0,
offset: Offset(0, 3))
],
),
),
preferredSize: Size.fromHeight(30),
),
),
body: TabBarView(
controller: _tabController,
children: const [
Center(child: Text('1')),
Center(child: Text('2')),
Center(child: Text('3')),
Center(child: Text('4')),
],
),
);
}
}
动画选项卡
class AnimationTab extends StatefulWidget {
final String label;
final IconData iconName;
final FunctionCaller functionCaller;
const AnimationTab({
Key? key,
required this.iconName,
required this.label,
required this.functionCaller,
}) : super(key: key);
@override
_AnimationTabState createState() => _AnimationTabState();
}
class _AnimationTabState extends State<AnimationTab>
with TickerProviderStateMixin {
late AnimationController _animationController;
late Animation<double> _animation;
@override
void initState() {
super.initState();
_animationController = AnimationController(
vsync: this,
duration: Duration(milliseconds: 500),
);
final _curvedAnimation = CurvedAnimation(
parent: _animationController,
curve: Curves.bounceIn,
reverseCurve: Curves.bounceOut);
_animation = TweenSequence<double>([
TweenSequenceItem<double>(tween: Tween(begin: 0, end: 12.5), weight: 1),
TweenSequenceItem<double>(tween: Tween(begin: 12.5, end: 0), weight: 1),
TweenSequenceItem<double>(tween: Tween(begin: 0, end: -12.5), weight: 1),
]).animate(_curvedAnimation)
..addListener(() {
setState(() {});
})
..addStatusListener((status) {
if (status == AnimationStatus.completed) {
_animationController.reverse();
}
});
}
void _animationExecution() {
_animationController.forward();
}
@override
Widget build(BuildContext context) {
const _tabTextStyle = TextStyle(
fontWeight: FontWeight.w300, fontSize: 12, color: Colors.black);
widget.functionCaller.call(_animationExecution);
return AnimatedBuilder(
animation: _animationController,
builder: (ctx, _) {
return Transform(
transform: Matrix4.rotationZ(math.pi * _animation.value / 180),
alignment: Alignment.center,
child: Tab(
icon: Icon(widget.iconName, color: Colors.black),
child: Text(widget.label, style: _tabTextStyle),
),
);
},
);
}
}
你不需要那些 TweenSequence
s,“状态侦听器”等,也可以使用普通的 Transform.rotate
而不是 Matrix4.rotationZ
,检查这个:
class TabTest extends StatefulWidget {
@override
_TabTestState createState() => _TabTestState();
}
class _TabTestState extends State<TabTest> with TickerProviderStateMixin {
late TabController tabController;
late List<AnimationController> animationControllers;
@override
void initState() {
super.initState();
tabController = TabController(length: 4, vsync: this)
..addListener(_listener);
animationControllers = List.generate(4, (i) => AnimationController(
vsync: this,
duration: Duration(milliseconds: 750),
reverseDuration: Duration(milliseconds: 350),
));
}
@override
Widget build(BuildContext context) {
// timeDilation = 5;
return Scaffold(
appBar: AppBar(
bottom: TabBar(
tabs: List.generate(4, (i) => AnimatedBuilder(
animation: animationControllers[i],
builder: (context, child) {
final child = Tab(
icon: Icon(Icons.cast),
child: Text('tab $i'),
);
final value = animationControllers[i].value;
if (animationControllers[i].status == AnimationStatus.forward) {
final angle = sin(4 * pi * value) * pi * 0.3;
return Transform.rotate(angle: angle, child: child);
} else {
final dy = sin(2 * pi * value) * 0.2;
return FractionalTranslation(translation: Offset(0, dy), child: child);
}
},
),
),
controller: tabController,
),
),
body: TabBarView(
children: List.generate(4, (i) =>
FittedBox(
child: Text('tab $i'),
),
),
controller: tabController,
),
);
}
void _listener() {
if (tabController.indexIsChanging) {
animationControllers[tabController.previousIndex].reverse();
} else {
animationControllers[tabController.index].forward();
}
}
@override
void dispose() {
super.dispose();
tabController
..removeListener(_listener)
..dispose();
animationControllers.forEach((ac) => ac.dispose());
}
}
正在尝试获得上述动画的所需输出。尝试使用 AnimatedBuilder
和 Transform
或 Matrix of z-axis
制作此动画。但未能如愿。这是我的代码。角度、收缩、晃动的部分没弄对
我的主页
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin {
late TabController _tabController;
late void Function() _memberCaller;
late void Function() _voucherCaller;
late void Function() _rewardCaller;
late void Function() _drinksCaller;
double degree = 0;
@override
void initState() {
super.initState();
_tabController = TabController(length: 4, vsync: this);
}
@override
void dispose() {
_tabController.dispose();
super.dispose();
}
void _callMethoCaller(int index) {
switch (index) {
case 0:
_memberCaller.call();
break;
case 1:
_voucherCaller.call();
break;
case 2:
_rewardCaller.call();
break;
case 3:
_drinksCaller.call();
break;
}
}
@override
Widget build(BuildContext context) {
List<Widget> _tabs(int index) => [
AnimationTab(
iconName: Icons.card_giftcard,
label: 'Membership',
functionCaller: (void Function() method) {
_memberCaller = method;
}),
AnimationTab(
iconName: Icons.confirmation_num_outlined,
label: 'Voucher',
functionCaller: (void Function() method) {
_voucherCaller = method;
}),
AnimationTab(
iconName: Icons.emoji_events_outlined,
label: 'Rewards',
functionCaller: (void Function() method) {
_rewardCaller = method;
}),
AnimationTab(
iconName: Icons.wine_bar_outlined,
label: 'Drinks',
functionCaller: (void Function() method) {
_drinksCaller = method;
}),
];
return Scaffold(
appBar: AppBar(
centerTitle: true,
bottom: PreferredSize(
child: Container(
child: TabBar(
onTap: _callMethoCaller,
controller: _tabController,
labelPadding: EdgeInsets.only(top: 5.0, bottom: 2.0),
indicatorColor: Colors.black,
tabs: List.generate(4, (index) => _tabs(index)[index]),
),
decoration: BoxDecoration(
color: Colors.white,
boxShadow: const [
BoxShadow(
color: Colors.white,
spreadRadius: 5.0,
offset: Offset(0, 3))
],
),
),
preferredSize: Size.fromHeight(30),
),
),
body: TabBarView(
controller: _tabController,
children: const [
Center(child: Text('1')),
Center(child: Text('2')),
Center(child: Text('3')),
Center(child: Text('4')),
],
),
);
}
}
动画选项卡
class AnimationTab extends StatefulWidget {
final String label;
final IconData iconName;
final FunctionCaller functionCaller;
const AnimationTab({
Key? key,
required this.iconName,
required this.label,
required this.functionCaller,
}) : super(key: key);
@override
_AnimationTabState createState() => _AnimationTabState();
}
class _AnimationTabState extends State<AnimationTab>
with TickerProviderStateMixin {
late AnimationController _animationController;
late Animation<double> _animation;
@override
void initState() {
super.initState();
_animationController = AnimationController(
vsync: this,
duration: Duration(milliseconds: 500),
);
final _curvedAnimation = CurvedAnimation(
parent: _animationController,
curve: Curves.bounceIn,
reverseCurve: Curves.bounceOut);
_animation = TweenSequence<double>([
TweenSequenceItem<double>(tween: Tween(begin: 0, end: 12.5), weight: 1),
TweenSequenceItem<double>(tween: Tween(begin: 12.5, end: 0), weight: 1),
TweenSequenceItem<double>(tween: Tween(begin: 0, end: -12.5), weight: 1),
]).animate(_curvedAnimation)
..addListener(() {
setState(() {});
})
..addStatusListener((status) {
if (status == AnimationStatus.completed) {
_animationController.reverse();
}
});
}
void _animationExecution() {
_animationController.forward();
}
@override
Widget build(BuildContext context) {
const _tabTextStyle = TextStyle(
fontWeight: FontWeight.w300, fontSize: 12, color: Colors.black);
widget.functionCaller.call(_animationExecution);
return AnimatedBuilder(
animation: _animationController,
builder: (ctx, _) {
return Transform(
transform: Matrix4.rotationZ(math.pi * _animation.value / 180),
alignment: Alignment.center,
child: Tab(
icon: Icon(widget.iconName, color: Colors.black),
child: Text(widget.label, style: _tabTextStyle),
),
);
},
);
}
}
你不需要那些 TweenSequence
s,“状态侦听器”等,也可以使用普通的 Transform.rotate
而不是 Matrix4.rotationZ
,检查这个:
class TabTest extends StatefulWidget {
@override
_TabTestState createState() => _TabTestState();
}
class _TabTestState extends State<TabTest> with TickerProviderStateMixin {
late TabController tabController;
late List<AnimationController> animationControllers;
@override
void initState() {
super.initState();
tabController = TabController(length: 4, vsync: this)
..addListener(_listener);
animationControllers = List.generate(4, (i) => AnimationController(
vsync: this,
duration: Duration(milliseconds: 750),
reverseDuration: Duration(milliseconds: 350),
));
}
@override
Widget build(BuildContext context) {
// timeDilation = 5;
return Scaffold(
appBar: AppBar(
bottom: TabBar(
tabs: List.generate(4, (i) => AnimatedBuilder(
animation: animationControllers[i],
builder: (context, child) {
final child = Tab(
icon: Icon(Icons.cast),
child: Text('tab $i'),
);
final value = animationControllers[i].value;
if (animationControllers[i].status == AnimationStatus.forward) {
final angle = sin(4 * pi * value) * pi * 0.3;
return Transform.rotate(angle: angle, child: child);
} else {
final dy = sin(2 * pi * value) * 0.2;
return FractionalTranslation(translation: Offset(0, dy), child: child);
}
},
),
),
controller: tabController,
),
),
body: TabBarView(
children: List.generate(4, (i) =>
FittedBox(
child: Text('tab $i'),
),
),
controller: tabController,
),
);
}
void _listener() {
if (tabController.indexIsChanging) {
animationControllers[tabController.previousIndex].reverse();
} else {
animationControllers[tabController.index].forward();
}
}
@override
void dispose() {
super.dispose();
tabController
..removeListener(_listener)
..dispose();
animationControllers.forEach((ac) => ac.dispose());
}
}