Flutter更新BottomNavigationBar

时间:2018-04-11 17:31:04

标签: dart flutter bottomnavigationview

我正在使用BottomNavigationBar和TabController。 通过单击BottomNavigationBar的不同选项卡,TabView正在更改内容。 但是,如果我在TabView上滑动以切换到另一个视图/选项卡,则BottomNavigationBar不会更新到我刷到的选项卡。 我已经在TabController中添加了一个监听器来检测更改。 但是,如何以编程方式更新BottomNavigationBar以反映更改?

5 个答案:

答案 0 :(得分:3)

我认为在您的情况下使用PageView代替TabBarView会更优雅。

enter image description here

class BottomBarExample extends StatefulWidget {
  @override
  _BottomBarExampleState createState() => new _BottomBarExampleState();
}

class _BottomBarExampleState extends State<BottomBarExample> {
  int _page = 0;
  PageController _c;
  @override
  void initState(){
    _c =  new PageController(
      initialPage: _page,
    );
    super.initState();
  }
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      bottomNavigationBar: new BottomNavigationBar(
        currentIndex: _page,
        onTap: (index){
          this._c.animateToPage(index,duration: const Duration(milliseconds: 500),curve: Curves.easeInOut);
        },
        items: <BottomNavigationBarItem>[
        new BottomNavigationBarItem(icon: new Icon(Icons.supervised_user_circle), title: new Text("Users")),
        new BottomNavigationBarItem(icon: new Icon(Icons.notifications), title: new Text("Alerts")),
        new BottomNavigationBarItem(icon: new Icon(Icons.email), title: new Text("Inbox")),

      ],

      ),
      body: new PageView(
        controller: _c,
        onPageChanged: (newPage){
          setState((){
            this._page=newPage;
          });
        },
        children: <Widget>[
          new Center(
            child: new Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                new Icon(Icons.supervised_user_circle),
                new Text("Users")
              ],
            ),
          ),
          new Center(
            child: new Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                new Icon(Icons.notifications),
                new Text("Alerts")
              ],
            ),
          ),
          new Center(
            child: new Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                new Icon(Icons.mail),
                new Text("Inbox")
              ],
            ),
          ),
        ],
      ),
    );
  }
}

答案 1 :(得分:1)

完整答案在这里:https://stackoverflow.com/a/63258130/10563627

首先创建一个课程MyBottomBarDemo

class MyBottomBarDemo extends StatefulWidget {
  @override
  _MyBottomBarDemoState createState() => new _MyBottomBarDemoState();
}

class _MyBottomBarDemoState extends State<MyBottomBarDemo> {
  int _pageIndex = 0;
  PageController _pageController;

  List<Widget> tabPages = [
    Screen1(),
    Screen2(),
    Screen3(),
  ];

  @override
  void initState(){
    super.initState();
    _pageController = PageController(initialPage: _pageIndex);
  }

  @override
  void dispose() {
    _pageController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("BottomNavigationBar", style: TextStyle(color: Colors.white)),
        backgroundColor: Colors.deepPurple,
      ),
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _pageIndex,
        onTap: onTabTapped,
        backgroundColor: Colors.white,
        items: <BottomNavigationBarItem>[
          BottomNavigationBarItem( icon: Icon(Icons.home), title: Text("Home")),
          BottomNavigationBarItem(icon: Icon(Icons.mail), title: Text("Messages")),
          BottomNavigationBarItem(icon: Icon(Icons.person), title: Text("Profile")),
        ],

      ),
      body: PageView(
        children: tabPages,
        onPageChanged: onPageChanged,
        controller: _pageController,
      ),
    );
  }
  void onPageChanged(int page) {
    setState(() {
      this._pageIndex = page;
    });
  }

  void onTabTapped(int index) {
    this._pageController.animateToPage(index,duration: const Duration(milliseconds: 500),curve: Curves.easeInOut);
  }
}

然后创建一个屏幕

class Screen1 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
        color: Colors.green,
            child: Center(child: Text("Screen 1")),
    );
  }
}

 screen 2... Screen 3...

enter image description here

答案 2 :(得分:0)

在TabView上滑动时,您应该更新bottomNavigationBar的当前索引以匹配tabview的新索引

data a;
array a(5) a5 - a1;
a1=1 ;a3=2;
want = whichn(coalesce(of a[*]), of a[*]);
run;

答案 3 :(得分:0)

关于BottomNavigationBar的另一种观点

许多人甚至官方文档面临额外导航的方式与 Web Navigation 不同。它们不会呈现具有可在单击后执行的可用链接的通用和/或部分通用模板。相反,他们正在实现的是在附加的“tabs_screen”中预加载不同的屏幕(基本上只是一个额外的 Scaffold 包装和渲染一个屏幕),然后,根据 onTap 上的给定索引,甚至是 BottomNavigationBar,您将使用该索引来确定将在该脚手架内实际呈现哪个屏幕。仅此而已。

相反,我建议以这种方式处理额外导航:

我像往常一样添加了底部导航栏,但我没有在列表页面上预加载/实例化屏幕,我所做的只是为收藏夹屏幕添加另一个命名路由,

我设计了我的底部导航栏:

bottomNavigationBar: BottomNavigationBar(
  onTap: (index) => onTapSelectNavigation(index, context),
  items: [
    BottomNavigationBarItem(
      icon: Icon(
        Icons.category,
      ),
      label: '',
      activeIcon: Icon(
        Icons.category,
        color: Colors.red,
      ),
      tooltip: 'Categories',
    ),
    BottomNavigationBarItem(
      icon: Icon(
        Icons.star,
      ),
      label: '',
      activeIcon: Icon(
        Icons.star,
        color: Colors.red,
      ),
      tooltip: 'Favorites',
    ),
  ],
  currentIndex: _activeIndex,
),

然后在 onTap 上甚至我调用了 onTapSelectNavigation 方法。

在该方法中,根据给定的点击标签索引,我将决定我将调用哪个路由。此BottomNavigationBar 可用于整个应用程序。为什么?因为我在我的 FeeddyScaffold 上实现了这个,这对于所有的屏幕都是通用的,因为 FeeddyScaffold 将所有这些屏幕上的所有内部小部件都包装起来。在每个屏幕上,我都会实例化 FeeddyScaffold,并传递一个小部件列表作为命名参数。这样我保证脚手架对于每个屏幕都是通用的,如果我实现了那个公共脚手架的额外导航,那么它就可以用于屏幕。这是我的 FeeddyScaffold 组件:

// Packages:
import 'package:feeddy_flutter/_inner_packages.dart';
import 'package:feeddy_flutter/_external_packages.dart';

// Screens:
import 'package:feeddy_flutter/screens/_screens.dart';

// Models:
import 'package:feeddy_flutter/models/_models.dart';

// Components:
import 'package:feeddy_flutter/components/_components.dart';

// Helpers:
import 'package:feeddy_flutter/helpers/_helpers.dart';

// Utilities:
import 'package:feeddy_flutter/utilities/_utilities.dart';

class FeeddyScaffold extends StatefulWidget {
  // Properties:
  final bool _showPortraitOnly = false;
  final String appTitle;
  final Function onPressedAdd;
  final String objectName;
  final int objectsLength;
  final List<Widget> innerWidgets;
  final int activeIndex;

  // Constructor:
  const FeeddyScaffold({
    Key key,
    this.appTitle,
    this.onPressedAdd,
    this.objectName,
    this.objectsLength,
    this.innerWidgets,
    this.activeIndex,
  }) : super(key: key);

  @override
  _FeeddyScaffoldState createState() => _FeeddyScaffoldState();
}

class _FeeddyScaffoldState extends State<FeeddyScaffold> {
  final bool _showPortraitOnly = false;
  int _activeIndex;

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    _activeIndex = widget.activeIndex;
  }

  void onTapSelectNavigation(int index, BuildContext context) {
    switch (index) {
      case 0:
        Navigator.pushNamed(context, FoodCategoryIndexScreen.screenId);
        break;
      case 1:
        Navigator.pushNamed(context, FavoritesScreen.screenId);
        break;
    }
  }

  @override
  Widget build(BuildContext context) {
    AppData appData = Provider.of<AppData>(context, listen: true);
    Function closeAllThePanels = appData.closeAllThePanels; // Drawer related:
    bool deviceIsIOS = DeviceHelper.deviceIsIOS(context);

    // WidgetsFlutterBinding.ensureInitialized(); // Without this it might not work in some devices:
    SystemChrome.setPreferredOrientations([
      DeviceOrientation.portraitUp,
      // DeviceOrientation.portraitDown,
      if (!_showPortraitOnly) ...[
        DeviceOrientation.landscapeLeft,
        DeviceOrientation.landscapeRight,
      ],
    ]);

    FeeddyAppBar appBar = FeeddyAppBar(
      appTitle: widget.appTitle,
      onPressedAdd: widget.onPressedAdd,
      objectName: widget.objectName,
    );

    return Scaffold(
      appBar: appBar,
      onDrawerChanged: (isOpened) {
        if (!isOpened) {
          closeAllThePanels();
        }
      },

      drawer: FeeddyDrawer(),

      body: NativeDeviceOrientationReader(
        builder: (context) {
          final orientation = NativeDeviceOrientationReader.orientation(context);
          bool safeAreaLeft = DeviceHelper.isLandscapeLeft(orientation);
          bool safeAreaRight = DeviceHelper.isLandscapeRight(orientation);
          bool isLandscape = DeviceHelper.isLandscape(orientation);

          return SafeArea(
            left: safeAreaLeft,
            right: safeAreaRight,
            child: Column(
              mainAxisAlignment: MainAxisAlignment.spaceAround,
              crossAxisAlignment: CrossAxisAlignment.stretch,
              children: widget.innerWidgets,
            ),
          );
        },
      ),

      bottomNavigationBar: BottomNavigationBar(
        onTap: (index) => onTapSelectNavigation(index, context),
        items: [
          BottomNavigationBarItem(
            icon: Icon(
              Icons.category,
            ),
            label: '',
            activeIcon: Icon(
              Icons.category,
              color: Colors.red,
            ),
            tooltip: 'Categories',
          ),
          BottomNavigationBarItem(
            icon: Icon(
              Icons.star,
            ),
            label: '',
            activeIcon: Icon(
              Icons.star,
              color: Colors.red,
            ),
            tooltip: 'Favorites',
          ),
        ],
        currentIndex: _activeIndex,
      ),

      // FAB
      floatingActionButton: deviceIsIOS
          ? null
          : FloatingActionButton(
              tooltip: 'Add ${widget.objectName.inCaps}',
              child: Icon(Icons.add),
              onPressed: () => widget.onPressedAdd,
            ),
      // floatingActionButtonLocation: FloatingActionButtonLocation.endDocked,
      floatingActionButtonLocation: deviceIsIOS ? null : FloatingActionButtonLocation.endDocked,
    );
  }
}

因此,在 onTapSelectNavigation 方法上,根据给定的选项卡索引,并使用简单的 Switch case 语句,我决定调用哪个命名路由。就这么简单。

void onTapSelectNavigation(int index, BuildContext context) {
  switch (index) {
    case 0:
      Navigator.pushNamed(context, FoodCategoryIndexScreen.screenId);
      break;
    case 1:
      Navigator.pushNamed(context, FavoritesScreen.screenId);
      break;
  }
}

现在,这还不够。为什么?因为当我为整个应用程序提供额外导航时,就会发生一些奇怪的事情。

如果我们只使用一个简单的 setState 在本地状态中设置和存储这个公共脚手架上的 activeTab,那么当我们在两个选项卡之间移动时,它会起到很好的作用。但是,当我们从 FoodCategoryIndexScreen 单击特定的 FoodCategory 时,访问保存 FoodRecipesList(餐食)和/或特定餐食的 FoodCategoryShowScreen,然后我们在 IOS 上向后滑动或在两个平台上都点击后,活动选项卡功能将获得疯狂的。它将不再正确显示当前活动标签。

为什么?

因为您是通过弹出路线到达那里的,而不是通过单击然后执行 onTab 事件,因此不会执行 setState 函数,因此不会更新 activeTab。

解决办法:

使用 RouteObserver 功能。这可以手动完成,但最简单的方法是安装 route_observer_mixin 包。这就是我们接下来要做的:

  1. 在您的主文件中,您将使用 RouteObserverProvider 包装整个应用程序,包含在提到的 mixin 中。 (我现在才加到上面的[RouteObserverProvider],其他的我之前已经加了):
  void main() {
    runApp(MultiProvider(
      providers: [
        RouteObserverProvider(),    

          // Config about the app:
          ChangeNotifierProvider<AppData>(
            create: (context) => AppData(),
          ),
    
          // Data related to the FoodCategoriesData objects: (sqlite)
          ChangeNotifierProvider<FoodCategoriesData>(
            create: (context) => FoodCategoriesData(),
          ),
    
          // Data related to the FoodCategoriesFoodRecipesData objects: (sqlite)
          ChangeNotifierProvider<FoodCategoriesFoodRecipesData>(
            create: (context) => FoodCategoriesFoodRecipesData(),
          ),
    
          // Data related to the FoodRecipesData objects: (sqlite)
          ChangeNotifierProvider<FoodRecipesData>(
            create: (context) => FoodRecipesData(),
          ),
    
          // Data related to the FoodIngredientsData objects: (sqlite)
          ChangeNotifierProvider<FoodIngredientsData>(
            create: (context) => FoodIngredientsData(),
          ),
    
          // Data related to the RecipeStepsData objects: (sqlite)
          ChangeNotifierProvider<RecipeStepsData>(
            create: (context) => RecipeStepsData(),
          ),
        ],
        // child: MyApp(),
        child: InitialSplashScreen(),
      ));
    }
  1. 在包含额外导航(显示 BottomNavigationBar) 您将添加 RouteAware 和 RouteObserverMixin 到您的州。

示例:

.
.
.
class _FoodCategoryIndexScreenState extends State<FoodCategoryIndexScreen> with RouteAware, RouteObserverMixin {
.
.
.
  1. 在每个有状态屏幕上(FavoritesScreen 除外),我们都会将此属性添加到本地状态:

    int _activeTab = 0;

注意:在收藏夹屏幕上,这将默认为 1,而不是 0,当然,因为收藏夹是第二个选项卡(索引 = 1)。所以在 FavoritesScreen 中是这样的:

int _activeTab = 1;
  1. 然后您将覆盖每个有状态屏幕上的 RouteAware 方法(我们只需要 didPopNext 和 didPush):
    /// Called when the top route has been popped off, and the current route
    /// shows up.
    @override
    void didPopNext() {
      print('didPopNext => Emerges: $_screenId');
      setState(() {
        _activeTab = 0;
      });
    }
    
    /// Called when the current route has been pushed.
    @override
    void didPush() {
      print('didPush => Arriving to: $_screenId');
      setState(() {
        _activeTab = 0;
      });
    }
    
    /// Called when the current route has been popped off.
    @override
    void didPop() {
      print('didPop => Popping of: $_screenId');
    }
    
    /// Called when a new route has been pushed, and the current route is no
    /// longer visible.
    @override
    void didPushNext() {
      print('didPushNext => Covering: $_screenId');
    }

当然,在FavoritesScreen里是这样的:

    /// Called when the top route has been popped off, and the current route
    /// shows up.
    @override
    void didPopNext() {
      print('didPopNext => Emerges: $_screenId');
      setState(() {
        _activeTab = 1;
      });
    }
    
    /// Called when the current route has been pushed.
    @override
    void didPush() {
      print('didPush => Arriving to: $_screenId');
      setState(() {
        _activeTab = 1;
      });
    }
    
    /// Called when the current route has been popped off.
    @override
    void didPop() {
      print('didPop => Popping of: $_screenId');
    }
    
    /// Called when a new route has been pushed, and the current route is no
    /// longer visible.
    @override
    void didPushNext() {
      print('didPushNext => Covering: $_screenId');
    }

RouterObserver 包将跟踪所有推送和弹出,并根据用户点击的每次后退或后退链接执行相应的正确方法,因此相应地更新每个有状态屏幕上的 _activeTab 属性。

  1. 然后我们将简单地将 int _activeTab 属性作为命名参数传递给每个有状态屏幕内的每个 FeeddyScaffold。像这样:

lib/screens/food_category_index_screen.dart

    .
    .
    .  
    return FeeddyScaffold(
        activeIndex: _activeTab,
        appTitle: widget.appTitle,
        innerWidgets: [
          // Food Categories Grid:
          Expanded(
            flex: 5,
            child: FoodCategoriesGrid(),
          ),
        ],
        objectsLength: amountTotalFoodCategories,
        objectName: 'category',
        onPressedAdd: () => _showModalNewFoodCategory(context),
      );
    }
    .
    .
    .

基于 setState 执行的 _activeTab 属性的每次更新,将始终引发 UI 的重新渲染,这将允许始终在 BottomNavigationBar 上相应地显示正确的选项卡索引,基于何时,始终匹配带有活动选项卡的当前路线。在这种情况下,我们希望始终显示第一个选项卡处于活动状态,但查看收藏夹屏幕时除外。

所以,无论我们是推送还是弹出路由,它都会以一种非常一致的方式看起来总是这样:

FoodCategoriesIndexScreen: enter image description here

FoodCategoryShowScreen: enter image description here

FoodRecipeShowScreen: enter image description here

收藏夹显示: enter image description here

有关更多详细信息,您可以在 GitHub 上从 here 克隆我的应用程序的源代码。

结束。

答案 4 :(得分:-1)

对于正在寻找简短解决方案的人来说,这是我的,也许对某人有用:

#include <iostream>

namespace Eigen {
    template<typename T, int W, int H>
    class Matrix {
    public:
        static Matrix<T,W,H> Zero() {
            return Matrix<T, W, H>{};
        }

        std::ostream &print_on(std::ostream &strm) const {
            return strm;
        }
    };
}

template <typename T, int W, int H>
std::ostream &operator<<(std::ostream &strm, Eigen::Matrix<T,W,H> const &matrix) {
    return matrix.print_on(strm);
}

template<typename T, int S>
class Base {
public:
    Eigen::Matrix<int, S, S> Mat = Eigen::Matrix<int, S, S>::Zero();

    virtual void print() { std::cout << Mat << std::endl; };
};

class Derived1 : public Base<Derived1,3>{
public:
};

class Derived2 : public Base<Derived2,4>{
public:
};

template <int Size>
class AdvertisingDerived : public Base<AdvertisingDerived<Size>,Size> {
public:
    constexpr static int matrixSize = Size;
};

int main(){
    Derived1 d1;
    d1.print();   // print a 3x3 zero matrix

    Derived2 d2;
    d2.print();   // print a 4x4 zero matrix

    AdvertisingDerived<3> ad1;

    AdvertisingDerived<4> ad2;

    std::cin.get();
    return 0;
}

我们不会忘记主要内容,应该像这样:

    class App extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => AppState();
}

class AppState extends State<App> {

  int currentTab = 0;

  void _selectTab(int index) {
    debugPrint (" index = $index ");
    setState(() {
      currentTab = index;
    });

    switch (currentTab) {
    case 0:

      debugPrint (" my index 0 ");
      break;

      case 1:
       debugPrint (" my index 1 ");
      break;

      case 2:
       debugPrint (" my index 2 ");
      break;

  }

  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: _buildBody(),
      bottomNavigationBar: BottomNavigationBar(
       currentIndex: currentTab,
       onTap: _selectTab,

      items: [
        BottomNavigationBarItem(
          icon: Icon(Icons.home), title: Text("Home"),
        ),

        BottomNavigationBarItem(
          icon: Icon(Icons.message), title: Text("Message"),
        ),

        BottomNavigationBarItem(
          icon: Icon(Icons.settings), title: Text("Settings"),
        ),

      ],

      ),
    );
  }

  Widget _buildBody() {
    // return a widget representing a page
  }
}