Flutter 高亮当前选中的导航项

Posted

技术标签:

【中文标题】Flutter 高亮当前选中的导航项【英文标题】:Flutter highlight current selected nav item 【发布时间】:2020-07-03 07:11:39 【问题描述】:

我为个人项目创建了一个 Flutter 入门工具包。

一切正常,但我有一个问题,我无法突出显示抽屉中当前选定的项目。

我不知道应该把确定当前选定项目的代码放在哪里。

下面是我的代码!

class _MdDrawerState extends State<MdDrawer>
    with SingleTickerProviderStateMixin<MdDrawer> 
  final _animationDuration = const Duration(milliseconds: 350);

  AnimationController _animationController;
  Stream<bool> isDrawerOpenStream;
  StreamController<bool> isDrawerOpenStreamController;
  StreamSink<bool> isDrawerOpenSink;

  .....

  @override
  void dispose() 
    _animationController.dispose();
    isDrawerOpenStreamController.close();
    isDrawerOpenSink.close();
    super.dispose();
  

  void onIconPressed() 
    final animationStatus = _animationController.status;
    final isAnimationCompleted = animationStatus == AnimationStatus.completed;

    .....
  

  @override
  Widget build(BuildContext context) 
    final screenWidth = MediaQuery.of(context).size.width;

    return StreamBuilder<bool>(
      initialData: false,
      stream: isDrawerOpenStream,
      builder: (context, isLeftDrawerOpenedAsync) 
        return AnimatedPositioned(
          duration: _animationDuration,
          top: 0,
          bottom: 0,
          left: isLeftDrawerOpenedAsync.data ? 0 : -screenWidth,
          right: isLeftDrawerOpenedAsync.data ? 0 : screenWidth - 45,
          child: Row(
            children: <Widget>[
              Expanded(
                child: Container(
                  padding: const EdgeInsets.symmetric(horizontal: 20),
                  color: Theme.of(context).backgroundColor,
                  child: ListView(
                    children: <Widget>[
                      Column(
                        children: <Widget>[
                          SizedBox(
                            height: 30,
                          ),
                          ListTile(
                            title: Text('First - Last',
                                style: Theme.of(context).textTheme.headline),
                            subtitle: Text('something@gmail.com',
                                style: Theme.of(context).textTheme.subhead),
                            leading: CircleAvatar(
                              child: Icon(
                                Icons.perm_identity,
                                color: Theme.of(context).iconTheme.color,
                              ),
                              radius: 40,
                            ),
                          ),
                          Divider(
                            height: 30,
                          ),
                          MdNavItem(
                            icon: Icons.home,
                            title: 'Home',
                            onTap: () 
                              onIconPressed();
                              BlocProvider.of<MdNavBloc>(context)
                                  .add(NavigationEvents.HomeClickedEvent);
                            ,
                          ),
                          MdNavItem(
                            icon: Icons.account_box,
                            title: 'Account',
                            onTap: () 
                              onIconPressed();
                              BlocProvider.of<MdNavBloc>(context)
                                  .add(NavigationEvents.AccountClickedEvent);
                            ,
                          ),
                          MdNavItem(
                            icon: Icons.shopping_basket,
                            title: 'Orders',
                            onTap: () 
                              onIconPressed();
                              BlocProvider.of<MdNavBloc>(context)
                                  .add(NavigationEvents.OrderClickedEvent);
                            ,
                          ),
                          MdNavItem(
                            icon: Icons.card_giftcard,
                            title: 'Wishlist',
                            onTap: () 
                              onIconPressed();
                              BlocProvider.of<MdNavBloc>(context)
                                  .add(NavigationEvents.WishlistClickedEvent);
                            ,
                          ),
                          Divider(
                            height: 30,
                          ),
                          MdNavItem(
                            icon: Icons.settings,
                            title: 'Settings',
                            onTap: () 
                              onIconPressed();
                              BlocProvider.of<MdNavBloc>(context)
                                  .add(NavigationEvents.SettingsClickedEvent);
                            ,
                          ),
                          MdNavItem(
                            icon: Icons.exit_to_app,
                            title: 'Logout',
                          ),
                          Divider(
                            height: 45,
                          ),
                        ],
                      ),
                    ],
                  ),
                ),
              ),
              ......
            ],
          ),
        );
      ,
    );
  

还有 MdNavItem 类

class MdNavItem extends StatelessWidget 
  final IconData icon;
  final String title;
  final Function onTap;

  const MdNavItem(this.icon, this.title, this.onTap);

  @override
  Widget build(BuildContext context) 
    return GestureDetector(
      onTap: onTap,
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Container(
          color: Theme.of(context).backgroundColor,
          child: Row(
            children: <Widget>[
              Icon(
                icon,
                size: 25,
                color: Theme.of(context).iconTheme.color,
              ),
              SizedBox(
                width: 20,
              ),
              Text(
                title,
                style: Theme.of(context).textTheme.headline,
              ),
            ],
          ),
        ),
      ),
    );
  

【问题讨论】:

【参考方案1】:

编辑:

第一种方法:

将此代码添加到您的抽屉中:

class _MdDrawerState extends State<MdDrawer>
    with SingleTickerProviderStateMixin<MdDrawer> 
  final _animationDuration = const Duration(milliseconds: 350);

  AnimationController _animationController;
  Stream<bool> isDrawerOpenStream;
  StreamController<bool> isDrawerOpenStreamController;
  StreamSink<bool> isDrawerOpenSink;


  final List<bool> isTaped = [true, false, false, false, false];  // the first is true because when the app
                                                                  //launch the home needs to be in red(or the
                                                                  //color you choose)

  void changeHighlight(int index)
    for(int indexTap = 0; indexTap < isTaped.length; indexTap++) 
      if (indexTap == index) 
        isTaped[index] = true;                                    //used to change the value of the bool list
       else 
        isTaped[indexTap] = false;
      
    
  

  .....

  @override
  void dispose() 
    _animationController.dispose();
    isDrawerOpenStreamController.close();
    isDrawerOpenSink.close();
    super.dispose();
  

  void onIconPressed() 
    final animationStatus = _animationController.status;
    final isAnimationCompleted = animationStatus == AnimationStatus.completed;

    .....
  

  @override
  Widget build(BuildContext context) 
    final screenWidth = MediaQuery.of(context).size.width;

    return StreamBuilder<bool>(
      initialData: false,
      stream: isDrawerOpenStream,
      builder: (context, isLeftDrawerOpenedAsync) 
        return AnimatedPositioned(
          duration: _animationDuration,
          top: 0,
          bottom: 0,
          left: isLeftDrawerOpenedAsync.data ? 0 : -screenWidth,
          right: isLeftDrawerOpenedAsync.data ? 0 : screenWidth - 45,
          child: Row(
            children: <Widget>[
              Expanded(
                child: Container(
                  padding: const EdgeInsets.symmetric(horizontal: 20),
                  color: Theme.of(context).backgroundColor,
                  child: ListView(
                    children: <Widget>[
                      Column(
                        children: <Widget>[
                          SizedBox(
                            height: 30,
                          ),
                          ListTile(
                            title: Text('First - Last',
                                style: Theme.of(context).textTheme.headline),
                            subtitle: Text('something@gmail.com',
                                style: Theme.of(context).textTheme.subhead),
                            leading: CircleAvatar(
                              child: Icon(
                                Icons.perm_identity,
                                color: Theme.of(context).iconTheme.color,
                              ),
                              radius: 40,
                            ),
                          ),
                          Divider(
                            height: 30,
                          ),
                          MdNavItem(
                            wasTaped: isTaped[0],
                            icon: Icons.home,
                            title: 'Home',
                            onTap: () 
                              onIconPressed();
                              BlocProvider.of<MdNavBloc>(context)
                                  .add(NavigationEvents.HomeClickedEvent);
                              changeHighlight(0);
                            ,
                          ),
                          MdNavItem(
                            wasTaped: isTaped[1],
                            icon: Icons.account_box,
                            title: 'Account',
                            onTap: () 
                              onIconPressed();
                              BlocProvider.of<MdNavBloc>(context)
                                  .add(NavigationEvents.AccountClickedEvent);
                              changeHighlight(1);
                            ,
                          ),
                          MdNavItem(
                            wasTaped: isTaped[2],
                            icon: Icons.shopping_basket,
                            title: 'Orders',
                            onTap: () 
                              onIconPressed();
                              BlocProvider.of<MdNavBloc>(context)
                                  .add(NavigationEvents.OrderClickedEvent);
                              changeHighlight(2);
                            ,
                          ),
                          MdNavItem(
                            wasTaped: isTaped[3],
                            icon: Icons.card_giftcard,
                            title: 'Wishlist',
                            onTap: () 
                              onIconPressed();
                              BlocProvider.of<MdNavBloc>(context)
                                  .add(NavigationEvents.WishlistClickedEvent);
                              changeHighlight(3);
                            ,
                          ),
                          Divider(
                            height: 30,
                          ),
                          MdNavItem(
                            wasTaped: isTaped[4],
                            icon: Icons.settings,
                            title: 'Settings',
                            onTap: () 
                              onIconPressed();
                              BlocProvider.of<MdNavBloc>(context)
                                  .add(NavigationEvents.SettingsClickedEvent);
                              changeHighlight(4);
                            ,
                          ),
                          MdNavItem(
                            icon: Icons.exit_to_app,
                            title: 'Logout',
                          ),
                          Divider(
                            height: 45,
                          ),
                        ],
                      ),
                    ],
                  ),
                ),
              ),
              ......
            ],
          ),
        );
      ,
    );
  

这是你的 MdNavItem:

class MdNavItem extends StatelessWidget 
  final IconData icon;
  final String title;
  final Function onTap;
  final bool wasTaped;     //receiving the bool value (if was taped or not)

  const MdNavItem(this.icon, this.title, this.onTap, this.wasTaped);

  @override
  Widget build(BuildContext context) 
    return GestureDetector(
      onTap: onTap,
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Container(
          color: Theme.of(context).backgroundColor,
          child: Row(
            children: <Widget>[
              Icon(
                icon,
                size: 25,
                color: wasTaped ? Colors.red : Theme.of(context).iconTheme.color, //the condition to change the color
              ),
              SizedBox(
                width: 20,
              ),
              Text(
                title,
                style: wasTaped ? TextStyle(
                  color: Colors.red,
                ) : Theme.of(context).textTheme.headline,
              ),
            ],
          ),
        ),
      ),
    );
  

旧答案,第二种方式:

第一个屏幕,PageView 所在的位置:

class FirstScreen extends StatelessWidget 

  final PageController pageController = PageController(initialPage: 0);

  @override
  Widget build(BuildContext context) 
    return Scaffold(
      appBar: AppBar(
        title: Text('Kit App'),
      ),
      drawer: CustomDrawer(
        pageController: pageController,
      ),
      body: PageView(
        controller: pageController,
        physics: NeverScrollableScrollPhysics(), //to prevent scroll
        children: <Widget>[
          HomeScreen(),
          AccountScreen(),   //your pages
          OrdersScreen(),
          WishListScreen(),
        ],
      ),
    );
  

自定义抽屉:

class CustomDrawer extends StatelessWidget 
  CustomDrawer(@required this.pageController);

  final PageController pageController;

  @override
  Widget build(BuildContext context) 
    return Drawer(
      child: Padding(
        padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 20),
        child: Column(
          children: <Widget>[
            DrawerItem(
              onTap: ()
                Navigator.pop(context); //to close the drawer
                pageController.jumpToPage(0);
              ,
              leading: Icons.home,
              title: 'Home',
              index: 0,
              controller: pageController,
            ),
            DrawerItem(
              onTap: ()
                Navigator.pop(context);
                pageController.jumpToPage(1);
              ,
              leading: Icons.account_box,
              title: 'Account',
              index: 1,
              controller: pageController,
            ),
            DrawerItem(
              onTap: ()
                Navigator.pop(context);
                pageController.jumpToPage(2);
              ,
              leading: Icons.shopping_cart,
              title: 'Orders',
              index: 2,
              controller: pageController,
            ),
            DrawerItem(
              onTap: ()
                Navigator.pop(context);
                pageController.jumpToPage(3);
              ,
              leading: Icons.card_travel,
              title: 'Wishlist',
              index: 3,
              controller: pageController,
            ),
          ],
        ),
      ),
    );
  

还有DrawerItem,其中放置了改变物品颜色的条件:

class DrawerItem extends StatelessWidget 
  DrawerItem(
    @required this.onTap,
    @required this.leading,
    @required this.title,
    @required this.index,
    @required this.controller,
  );

  final VoidCallback onTap;
  final IconData leading;
  final String title;
  final int index;
  final PageController controller;

  @override
  Widget build(BuildContext context) 
    return GestureDetector(
      onTap: onTap,
      child: ListTile(
        leading: Icon(
          leading,
          color: controller.page.round() == index ? Colors.red : Colors.grey,
        ),
        title: Text(
          title,
          style: TextStyle(
            color: controller.page.round() == index ? Colors.red : Colors.grey,
          ),
        ),
      ),
    );
  

结果:

现在你只需要在你的代码上实现它。

【讨论】:

如果不使用PageView,就没有办法解决这个问题吗?我正在为我的抽屉使用自定义视图。 好的,我建议使用页面视图,因为我认为这是一种更简洁的方式,但我会用其他形式编辑我的答案。 我已将更新后的代码添加到我的项目中。但是我遇到了一个问题。在const MdNavItem(this.icon, this.title, this.onTap, this.wasTaped); 上,我收到了错误消息-Failed assertion: boolean expression must not be null。我将初始值设置为 false 并没有帮助 我很乐意提供帮助!

以上是关于Flutter 高亮当前选中的导航项的主要内容,如果未能解决你的问题,请参考以下文章

请问如何用TreeView控件导航无法让选中的节点高亮

elementUI的导航栏在刷新页面的时候选中状态消失的解决

更改 ListView 中当前顶部可见项的颜色 - Flutter

Flutter - 底部导航 - 如何重建页面?

Dismissible confirmDismiss 结合新的路由导航导致 Flutter 崩溃

Flutter BaseScreen 不断重建/不想要的重建