import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:vnm_version/config/app_colors.dart';
typedef CallBack = void Function(int);
class GoCodeNavigationBarWidget extends StatefulWidget {
///选中了第几个导航按钮,总计五个,依次是:Home,Casino,Live,More,Me
CallBack callBack;
GoCodeNavigationBarWidget({required this.callBack, super.key});
@override
State<StatefulWidget> createState() {
return _GoCodeNavigationBarState();
}
}
class _GoCodeNavigationBarState extends State<GoCodeNavigationBarWidget>
with TickerProviderStateMixin {
///动画控制器
late AnimationController controller;
///组件移动的动画
late Animation<Offset> animationHorizontal;
Offset? lastOffsetEndHorizontal;
///组件向上移动的动画
late Animation<Offset> animationUp;
///组件向下移动的动画()
late Animation<Offset> animationDown;
// ///组件向上移动时候的颜色渐变动画
// late Animation<Offset> animationColorUp;
//
// ///组件向下移动时候的颜色渐变动画()
// late Animation<Offset> animationColorDown;
///动画每移动一个区间耗费的时间
int timeConsumedForEachInterval = 2;
// ///用户初始化选中的图标,默认是中间的LIVE
// int defaultSelectedIndex = 2;
///用户上次点击选中的图标,默认是中间的LIVE,即2
int lastSelectedIndex = 2;
///用户本次选中的图标
int thisSelectedIndex = -1;
///动画是否初始化
bool _initializedAnimation = false;
///图片大小的变化动画
var pictureSizeChange;
var pictureSizeChangeController;
///是否在播放动画,播放动画的时候禁止点击
bool isPlayingAnimation = false;
String coverIconPath = "assets/images/navigation_bar_move_bg_normal.png";
///启动动画的效果,参数为当前tab的索引
void _animation(int locationIndex) {
///移动区间的个数
int intervals = locationIndex - lastSelectedIndex;
if (intervals == 0) {
return;
}
_initializedAnimation = true;
///注意,千万不要添加addStatusListener监听,更不能在status == AnimationStatus.completed的时候赋值selectedIndex = locationIndex,我就被这句代码坑惨了
controller = AnimationController(
duration:
Duration(seconds: intervals.abs() * timeConsumedForEachInterval),
vsync: this)
..addListener(() {
///计算距离左右边缘多少距离的时候改变图片
bool isNearEdge = ScreenUtil().screenWidth / 5 * intervals.abs() -
ScreenUtil().screenWidth /
5 *
intervals.abs() *
controller.value <
30.w;
bool isFarEdge =
ScreenUtil().screenWidth / 5 * intervals.abs() * controller.value >
30.w;
if (locationIndex == 0 && isNearEdge) {
coverIconPath = "assets/images/navigation_bar_move_bg_left.png";
} else if (locationIndex == 4 && isNearEdge) {
coverIconPath = "assets/images/navigation_bar_move_bg_right.png";
} else {
if (lastSelectedIndex == 0 && !isFarEdge) {
coverIconPath = "assets/images/navigation_bar_move_bg_left.png";
} else if (lastSelectedIndex == 4 && !isFarEdge) {
coverIconPath = "assets/images/navigation_bar_move_bg_right.png";
} else {
coverIconPath = "assets/images/navigation_bar_move_bg_normal.png";
}
}
setState(() {});
})
..addStatusListener((status) {
if (status == AnimationStatus.forward) {
isPlayingAnimation = true;
}
if (status == AnimationStatus.completed) {
isPlayingAnimation = false;
setState(() {
lastSelectedIndex = locationIndex;
animationUp = Tween(begin: Offset(0.w, 0.h), end: Offset(0.w, 0.h))
.animate(controller);
animationDown =
Tween(begin: Offset(0.w, 0.h), end: Offset(0.w, 0.h))
.animate(controller);
});
}
});
Offset nowOffsetHorizontal = Offset(
(locationIndex - 2) * (ScreenUtil().screenWidth / 5 / 104.w), 0.h);
animationHorizontal = Tween(
begin: lastOffsetEndHorizontal ?? Offset(0.w, 0.h),
end: nowOffsetHorizontal)
.animate(controller);
lastOffsetEndHorizontal = nowOffsetHorizontal;
animationUp = Tween(begin: Offset(0.w, 0.h), end: Offset(0.w, -0.44.h))
.animate(controller);
animationDown = Tween(begin: Offset(0.w, 0.h), end: Offset(0.w, 0.3548.h))
.animate(controller);
pictureSizeChange = Tween(begin: 0.0, end: 1.0);
controller.forward();
}
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
// height: 486.h,
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage("assets/images/main_navigation_bg.png"),
fit: BoxFit.fitWidth,
alignment: Alignment.bottomCenter)),
child: Stack(
children: [
///左右移动的图片要和上下移动的图片在不同的层级,这样可以保证在动画过程中左右移动的图片肯定会被5种item图片覆盖
backgroundWidget(),
Positioned.fill(
child: Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: List.generate(5, (index) => tab(index)),
))
],
));
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
Expanded tab(int locationIndex) {
return Expanded(
child: InkWell(
onTap: isPlayingAnimation
? null
: () {
// coverIconPath =
// "assets/images/navigation_bar_move_bg_normal.png";
_animation(locationIndex);
widget.callBack(locationIndex);
thisSelectedIndex = locationIndex;
},
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.end,
children: [
showNavigationBarIcon(locationIndex),
SizedBox(
height: lastSelectedIndex == locationIndex ? 5.h : 11.h),
_showText(locationIndex),
SizedBox(height: 3.h),
],
),
)));
}
SizedBox _showText(int locationIndex) {
String text = "";
switch (locationIndex) {
case 0:
text = AppLocalizations.of(context)?.homeTab ?? "Home";
break;
case 1:
text = AppLocalizations.of(context)?.casinoTab ?? "Casino";
break;
case 2:
text = AppLocalizations.of(context)?.liveTab ?? "LIVE";
break;
case 3:
text = AppLocalizations.of(context)?.moreTab ?? "More";
break;
case 4:
text = AppLocalizations.of(context)?.meTab ?? "Me";
break;
default:
text = AppLocalizations.of(context)?.liveTab ?? "LIVE";
break;
}
return SizedBox(
height: 15.h,
child: Center(child: Text(text, style: _getTextStyle(locationIndex))),
);
}
TextStyle _getTextStyle(int nowIndex) {
return lastSelectedIndex == nowIndex
? TextStyle(
fontSize: 11.sp,
color: AppColors.termsText0,
fontWeight: FontWeight.bold)
: TextStyle(fontSize: 12.sp, color: AppColors.passwordUnselected);
}
Widget showNavigationBarIcon(int locationIndex) {
if (lastSelectedIndex == locationIndex) {
return SlideTransition(
position: _initializedAnimation
? animationDown
: const AlwaysStoppedAnimation<Offset>(Offset.zero),
child: Container(
margin: EdgeInsets.only(bottom: 20.h),
width: !_initializedAnimation
? 31.w
: 31.w -
(31.w - 25.w) * pictureSizeChange.evaluate(controller),
height: !_initializedAnimation
? 31.h
: 31.h -
(31.h - 25.h) * pictureSizeChange.evaluate(controller),
child: Image.asset(
getIconPath(locationIndex),
)));
} else if (thisSelectedIndex == locationIndex) {
return SlideTransition(
position: _initializedAnimation
? animationUp
: const AlwaysStoppedAnimation<Offset>(Offset.zero),
child: getUpImage(getIconPath(locationIndex)),
);
} else {
return getNormalImage(getIconPath(locationIndex));
}
}
Image getUpImage(String iconPath) {
return Image.asset(iconPath,
width: !_initializedAnimation
? 25.w
: 25.w + (31.w - 25.w) * pictureSizeChange.evaluate(controller),
height: _initializedAnimation
? 25.h
: 25.h + (31.h - 25.h) * pictureSizeChange.evaluate(controller));
}
Image getNormalImage(String iconPath) {
return Image.asset(iconPath, width: 25.w, height: 25.h);
}
Row backgroundWidget() {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [tabBg()],
);
}
Stack tabBg() {
return Stack(children: [
SlideTransition(
position: _initializedAnimation
? animationHorizontal
: const AlwaysStoppedAnimation<Offset>(Offset.zero),
child: Container(
width: 104.w,
margin: EdgeInsets.only(bottom: 22.h),
///去除切换图片时候的闪烁效果
child: FadeInImage(
placeholder: AssetImage(coverIconPath),
image: AssetImage(coverIconPath),
)),
)
]);
}
String getIconPath(int locationIndex) {
String iconPath = "";
switch (locationIndex) {
case 0:
iconPath = locationIndex == lastSelectedIndex
? "assets/images/home_selected.png"
: "assets/images/home_unselected.png";
break;
case 1:
iconPath = locationIndex == lastSelectedIndex
? "assets/images/casino_selected.png"
: "assets/images/casino_unselected.png";
break;
case 2:
iconPath = locationIndex == lastSelectedIndex
? "assets/images/live_selected.png"
: "assets/images/live_unselected.png";
break;
case 3:
iconPath = locationIndex == lastSelectedIndex
? "assets/images/more_selected.png"
: "assets/images/more_unselected.png";
break;
case 4:
iconPath = locationIndex == lastSelectedIndex
? "assets/images/me_selected.png"
: "assets/images/me_unselected.png";
break;
default:
iconPath = locationIndex == lastSelectedIndex
? "assets/images/home_selected.png"
: "assets/images/home_unselected.png";
break;
}
return iconPath;
}
}
看下效果(视频效果和图片资源已上传)
flutter底部导航栏的动画