Flutter 解決系統BottomNavigationBar的水波紋問題

吉哈達發表於2020-02-27

起因

Flutter 系統自帶的BottomNavigationBar,在點選時item會有一個水波紋效果,產品並不想要這個(實際上這個水波紋有的時候還會卡住無法消失)。
網上暫時沒有找到現成的,所以就自己擼一個。

PS:通過繼承InteractiveInkFeature,也可以去除一些widget自帶的水波紋(使用方法可以參考demo裡的NoInkWellFactory類檔案),不過bottom nav這裡沒法使用。
複製程式碼

Step.1

首先整體結構,我們參照系統的,子Item的狀態我們通過Provider進行管理,先建立一個BottomNavBarNoInk.

程式碼如下(說明我儘量寫在註釋裡方便閱讀):
複製程式碼
class BottomNavBarNoInk extends StatefulWidget{

  IndexModel indexModel;

  final width;
  final height;
  List<BottomNavigationBarItem> items;

  int currentIndex;
  ValueChanged<int> onTap;

  BottomNavBarNoInk({@required this.width
    ,@required this.
    height,@required this.items,
    this.currentIndex,this.onTap}) : indexModel = IndexModel(currentIndex);


  @override
  State<StatefulWidget> createState() {

    return BottomNavBarNoInkState();
  }
}

class BottomNavBarNoInkState extends State<BottomNavBarNoInk> {

  List<Widget> barItems = [];

  @override
  void initState() {
    // TODO: implement initState
    super.initState();

    transfer2Widget();
  }
    //根據items建立對應的widget
  transfer2Widget(){
    for(int i=0; i< widget.items.length;i++){
      barItems.add(GestureDetector(
        onTap: (){
          widget.onTap(i);
          //更新model的值
          model?.setIndex(i);
        },
        child: BottomNoInkBarItem(item: widget.items[i],index: i,),
      ));
    }
  }
  //用於儲存當前第幾個item被點選
  IndexModel model ;

  @override
  Widget build(BuildContext context) {
    //通過provider 儲存IndexModel,
    //子widget可以共享這個model並根據內部資料的變更自動重新整理
    return ChangeNotifierProvider(
      create: (ctx){
        model = IndexModel(widget.currentIndex);
        return model;
      },
      child:Container(
        width: widget.width,
        height: widget.height,
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceAround,
          children: barItems,
        ),
      ) ,
    ) ;
  }



複製程式碼

這裡我們依然使用系統的BottomNavigationBarItem對item進行封裝.

Step.2

建立一個IndexModel對狀態進行儲存

程式碼如下

class IndexModel extends ChangeNotifier{
  int selectIndex;

  IndexModel(@required this.selectIndex);

  get index => selectIndex;

  setIndex(int index){
    selectIndex = index;
    notifyListeners();
  }

}
複製程式碼

Step.3

在子Widget(BottomNoInkBarItem)中我們通過Consumer來獲取到Provider管理的物件,並且根據這個物件的值來構造子widget,如果Provider的值變動,子widget也會同步重新整理。

程式碼如下:

class BottomNoInkBarItem extends StatefulWidget{

  int index;
  BottomNavigationBarItem item;


  BottomNoInkBarItem({this.item,this.index});

  @override
  State<StatefulWidget> createState() {
    // TODO: implement createState
    return BottomNoInkBarItemState();
  }

}

class BottomNoInkBarItemState extends State<BottomNoInkBarItem> {
  @override
  Widget build(BuildContext context) {

    return Consumer<IndexModel>(
      builder: (ctx,model,child){
        
        return Container(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.spaceAround,
            children: <Widget>[
              Stack(
                children: <Widget>[
                  //未啟用狀態
                  Offstage(
                    offstage: model.index == widget.index,
                    child: widget.item.icon,
                  ),
                  Offstage(
                    offstage: model.index != widget.index,
                    child: widget.item.activeIcon,
                  ),
                ],
              ),
              ///title
              widget.item.title
            ],
          ),
        );
      },
    );
  }
}
複製程式碼

結束

至此功能就完成了,因為我的專案用到了Provider,所以這裡便直接使用了。如果不想用Provider,也可以使用stream來實現。 有其他騷操作的,請評論區告訴我,大家一起交流。

Demo地址

相關文章