一般來說,寫 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),
              ),
            ),
          ],
        ),
      ),
    );
  }
}