一般來說,寫 BottomNavigationBar 會使用這個方法,官方文件,這個是官方的範例。
今天我想要介紹,如何從簡單的變成有動畫的感覺。
今日的程式碼 => GITHUB
製作一個 Widget
,然後裡面放一個 Row
,Row
裡面放一個可以點擊的 AnimatedContainer
,然後設定 icon 的圖示和動畫。這個 Widget
會有一個初始的 pageIndex
,BottomNavigator
也會有一個 pageIndex
,這兩個的 index
要設為一樣。在這個自定義的 Widget
裡面,會有一個 callBack
可以讓 BottomNavigator
拿到這個自定義 Widget
裡面被點擊的 index
。
這邊我是把它建立成 2 個物件,有興趣的話(可以自行嘗試變成一個物件)
class BarStyle {
final double fontSize, iconSize;
final FontWeight fontWeight;
BarStyle(
{this.fontSize = 18.0,
this.iconSize = 32.0,
this.fontWeight = FontWeight.w600});
}
class BarItemData {
final String text;
final IconData iconData;
final Color color;
BarItemData(this.text, this.iconData, this.color);
}
控制 bottomNavigationBar
的頁面
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
/// the index of pageItem
/// 畫面的 index
int pageIndex = 0;
/// 宣告一個 List,等等要放對應的 Page 進去
List<Widget> pageItem = [];
@override
void initState() {
super.initState();
/// 每一頁對應的 Page 是什麼
pageItem = [HomePage(), NotifyScreen(), UserProfile(), SettingApp()];
}
/// Object<BarItem> of List
/// List 裡面包一個 自定義的物件 BarItem
final List<BarItemData> barItems = [
BarItemData("Home", Icons.home, Color(0xFF498AEF)),
BarItemData("Notify", Icons.notifications_active, Colors.red),
BarItemData("Profile", Icons.person_outline, Colors.teal),
BarItemData("Setting", Icons.menu, Colors.purple)
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('BottomNavigationBar Sample'),
),
body: IndexedStack(
index: pageIndex,
children: pageItem,
),
bottomNavigationBar: AnimationBottomBar(
barItemsData: barItems,
// animationDuration: const Duration(milliseconds: 500), 非必填
// curves: Curves.easeInOut, 非必填
barStyle: BarStyle(
fointSize: 20.0, fontWeight: FontWeight.w400, iconSize: 30.0),
changePageIndex: (int index) {
/// when bottomBar on Tap will return the bottomBar index
/// 會回傳被點擊到的 bottomBar index
setState(() {
pageIndex = index;
});
},
),
);
}
}
自定義的客製化 BottomBar 的 Item
class AnimationBottomBar extends StatefulWidget {
/// get data of List<BarItem>
/// 取得 BarItem 的資料
final List<BarItemData> _barItemsData;
/// get animation duration time
/// 取得 animation 的延遲時間
final Duration _animationDuration;
/// get data of List<BarItem>
/// 取得 BarItem 的資料
final BarStyle _barStyle;
/// callBack will return the index of the bottombar that was clicked
/// callBack 回傳被點擊到的 index
final void Function(int index) _changePageIndex;
/// animation
/// 動畫的效果
final Curve _curves;
const AnimationBottomBar(
{Key? key,
required List<BarItemData> barItemsData,
Duration animationDuration = const Duration(milliseconds: 500),
Curve curves = Curves.easeInOut,
required BarStyle barStyle,
required Function(int index) changePageIndex})
: _barItemsData = barItemsData,
_animationDuration = animationDuration,
_barStyle = barStyle,
_curves = curves,
_changePageIndex = changePageIndex,
super(key: key);
@override
_AnimationBottomBarState createState() => _AnimationBottomBarState();
}
class _AnimationBottomBarState extends State<AnimationBottomBar>
with TickerProviderStateMixin {
/// init selectedBarIndex = 0;
/// 初始化 selectedBarIndex = 0;
int selectedBarIndex = 0;
@override
Widget build(BuildContext context) {
return Material(
elevation: 10.0,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 16),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildBarItems()),
),
);
}
/// save the single data of barItemsData into the List
/// 將 barItemsData 的單筆資料存入 List 裡面
List<Widget> _buildBarItems() {
/// init _barItem List
List<Widget> _barItems = [];
for (int i = 0; i < widget._barItemsData.length; i++) {
BarItemData item = widget._barItemsData[i];
bool isSelected = selectedBarIndex == i;
_barItems.add(_customBarItem(i, isSelected, item));
}
return _barItems;
}
/// build CustomBarItem in BottomNavigatorBar
/// 建立客製化的 BarItem 樣式
InkWell _customBarItem(int i, bool isSelected, BarItemData item) {
return InkWell(
splashColor: Colors.transparent,
onTap: () {
setState(() {
selectedBarIndex = i;
widget._changePageIndex(selectedBarIndex);
});
},
child: AnimatedContainer(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
duration: widget._animationDuration,
decoration: BoxDecoration(
color:
isSelected ? item.color.withOpacity(0.15) : Colors.transparent,
borderRadius: BorderRadius.all(Radius.circular(30.0))),
child: Row(
children: <Widget>[
Icon(
item.iconData,
color: isSelected ? item.color : Colors.black,
size: widget._barStyle.iconSize,
),
SizedBox(width: 10.0),
AnimatedSize(
duration: widget._animationDuration,
curve: widget._curves,
vsync: this,
child: Text(
isSelected ? item.text : "",
style: TextStyle(
color: item.color,
fontWeight: widget._barStyle.fontWeight,
fontSize: widget._barStyle.fontSize),
),
),
],
),
),
);
}
}