dart Flutter 中导航栏的子菜单

carvr3hs  于 5个月前  发布在  Flutter
关注(0)|答案(1)|浏览(85)

我有我的网站在Flutter,我需要为我的导航栏创建一个插件,当你点击NavbarItem的用户名是(users.firstName)。下面我与你分享的代码。

class NavBar extends StatelessWidget {
  const NavBar({Key? key});

  @override
  Widget build(BuildContext context) {
    final size = MediaQuery.of(context).size;
    final user = Provider.of<AuthProvider>(context).user!;
    final token = LocalStorage.prefs.getString('token');
    return Container(
      width: double.infinity,
      height: 75,
      decoration: buildBoxDecoration(),
      child: Row(
        children: [
          if (size.width <= 779) ...[
            IconButton(
              onPressed: () {
                SideMenuProvider.openMenu();
              },
              icon: const Icon(Icons.menu_outlined),
            ),
            const Spacer(),
            Padding(
              padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 2),
              child: Container(
                width: 250,
                child: const AutoSizeText(
                  'SISTEMA WEB DE COSTOS Y RENTABILIDAD DEL PROCESO DEL CULTIVO DEL BANANO',
                  style: TextStyle(
                    fontWeight: FontWeight.bold,
                  ),
                  maxLines: 5, // Número máximo de líneas permitidas
                  minFontSize: 10, // Tamaño de fuente mínimo
                  maxFontSize: 30, // Tamaño de fuente máximo
                  overflow: TextOverflow.ellipsis, // Opción de desbordamiento
                ),
              ),
            ),
          ],
          if (size.width >= 779) ...[
            const SizedBox(
              width: 5,
            ),
            Padding(
              padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 2),
              child: Container(
                width: 250,
                child: const AutoSizeText(
                  'SISTEMA WEB DE COSTOS Y RENTABILIDAD DEL PROCESO DEL CULTIVO DEL BANANO',
                  style: TextStyle(
                    fontWeight: FontWeight.bold,
                  ),
                  maxLines: 5, // Número máximo de líneas permitidas
                  minFontSize: 10, // Tamaño de fuente mínimo
                  maxFontSize: 30, // Tamaño de fuente máximo
                  overflow: TextOverflow.ellipsis, // Opción de desbordamiento
                ),
              ),
            ),
            const Spacer(),
            NavbarItem(
                text: 'Inicio',
                icon: Icons.home_sharp,
                onPressed: () {
                  NavigationService.replaceTo(Flurorouter.dashboardRoute);
                }),
            /*NavbarItem(
                text: 'Opciones de perfil',
                icon: Icons.settings,
                onPressed: () {}),*/
            NavbarItem(
                text: 'Datos',
                icon: Icons.people,
                onPressed: () {
                  NavigationService.replaceTo('/dashboard/info/$token');
                },
                isActive: false),
            NavbarItem(
                text: 'Password',
                icon: Icons.people,
                onPressed: () {
                  NavigationService.replaceTo(Flurorouter.changePassRoute);
                },
                isActive: false),
            NavbarItem(
                text: user.firstName,
                icon: Icons.people,
                onPressed: () {
                  //crearMenu(context);
                },
                isActive: false),
            NavbarItem(
              text: 'Salir',
              icon: Icons.exit_to_app_outlined,
              onPressed: () {
                Provider.of<AuthProvider>(context, listen: false).logout();
              },
            ),
          ],
        ],
      ),
    );
  }

  BoxDecoration buildBoxDecoration() => const BoxDecoration(
          gradient: LinearGradient(
            begin: Alignment.centerLeft, // Comienza desde la izquierda
            end: Alignment.centerRight, // Termina en la derecha
            colors: [
              Color(0xFF679C64), // Color de inicio
              Color(0xFFADCFAB), // Color de finalización
            ],
          ),
          boxShadow: [
            BoxShadow(
              blurRadius: 5,
              color: Colors.black12,
            )
          ]);

字符串
NavbarItem包括:

class NavbarItem extends StatefulWidget {
  final String text;
  final IconData icon;
  final bool isActive;
  final Function onPressed;

  const NavbarItem({
    required this.text,
    required this.icon,
    this.isActive = false,
    required this.onPressed,
  });

  @override
  State<NavbarItem> createState() => _NavbarItemState();
}

class _NavbarItemState extends State<NavbarItem> {
  bool isHover = false;

  @override
  Widget build(BuildContext context) {
    return Container(
      child: InkWell(
        onTap: widget.isActive ? null : () => widget.onPressed(),
        child: Padding(
          padding: const EdgeInsets.symmetric(horizontal: 15),
          child: MouseRegion(
            onEnter: (_) => setState(() {
              isHover = true;
            }),
            onExit: (_) => setState(() {
              isHover = false;
            }),
            child: Row(
              crossAxisAlignment: CrossAxisAlignment.end,
              children: [
                Icon(widget.icon),
                Text(
                  widget.text,
                  style: TextStyle(
                    decoration: isHover
                        ? TextDecoration.underline
                        : TextDecoration.none,
                  ),
                )
              ],
            ),
          ),
        ),
      ),
    );
  }
}


我该怎么做?
我需要改变一些小部件的NavbarItem,使我能够实现显示更多的选项来获得一个插件的行为。我还想知道这些插件选项是否可以自定义,就像下面的小部件一样,这是一个AnimatedContainer,我使用的侧菜单,我喜欢它的工作方式,我想集成该小部件作为自定义选项:

class SidebarItem extends StatefulWidget {
  final String text;
  final Function onPressed;
  final bool isActive;
  final IconData icon;

  const SidebarItem({
    Key? key,
    required this.text,
    required this.onPressed,
    this.isActive = false,
    required this.icon,
  }) : super(key: key);

  @override
  State<SidebarItem> createState() => _SidebarItemState();
}

class _SidebarItemState extends State<SidebarItem> {
  bool isHover = false;

  @override
  Widget build(BuildContext context) {
    Color backgroundColor = isHover
        ? const Color(0xFF184D26)
        : widget.isActive
            ? const Color(0xFF184D26)
            : Colors.transparent;
    return AnimatedContainer(
        duration: const Duration(milliseconds: 250),
        color: backgroundColor,
        child: Material(
          color: Colors.transparent,
          child: InkWell(
              onTap: widget.isActive ? null : () => widget.onPressed(),
              child: Padding(
                padding:
                    const EdgeInsets.symmetric(horizontal: 30, vertical: 10),
                child: MouseRegion(
                  onEnter: (_) => setState(() {
                    isHover = true;
                  }),
                  onExit: (_) => setState(() {
                    isHover = false;
                  }),
                  child: Row(
                    crossAxisAlignment: CrossAxisAlignment.center,
                    children: [
                      Icon(widget.icon),
                      const SizedBox(
                        width: 5,
                      ),
                      Text(
                        widget.text,
                        /*style: GoogleFonts.roboto(
                          fontSize: 14,
                          color: Colors.black.withOpacity(0.8),
                        ),*/
                      ),
                    ],
                  ),
                ),
              )),
        ));
  }
}


我想实现这样的东西:我需要显示两个选项'更改密码'和'更新信息'. enter image description here

MRE

这是我的主要:

return MaterialApp(
  debugShowCheckedModeBanner: false,
  title: 'BananoProject',
  initialRoute: '/',
  onGenerateRoute: Flurorouter.router.generator,
  navigatorKey: NavigationService.navigatorKey,
  scaffoldMessengerKey: NotificationsService.messengerKey,
  builder: (_, child) {

    final authProvider = Provider.of<AuthProvider>(context);

    if (authProvider.authStatus == AuthStatus.checking){
      return SplashLayout();
    }

    if(authProvider.authStatus == AuthStatus.authenticated){
      return DashboardLayout(child: child!);


登录后,用户可以访问DashboardLayout,这是应用程序的主窗口。

class DashboardLayout extends StatefulWidget {
 final Widget child;

 const DashboardLayout({ required this.child});

 @override
 State<DashboardLayout> createState() => _DashboardLayoutState();
}
class _DashboardLayoutState extends State<DashboardLayout>
with SingleTickerProviderStateMixin {
@override
void initState() {
// TODO: implement initState
super.initState();

SideMenuProvider.menuController = AnimationController(
    vsync: this, duration: const Duration(microseconds: 300));
 }

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

return Scaffold(
    body: Container(
  width: MediaQuery.of(context).size.width,
  height: MediaQuery.of(context).size.height,
  decoration: const BoxDecoration(
    image: DecorationImage(
      image: AssetImage('assets/moduls_background.jpeg'),
      fit: BoxFit.cover,
    ),
  ),
  child: Stack(
    children: [
      Row(
        children: [
          Expanded(
            child: Column(
              children: [
                const NavBar( ),

                //navbar
                Expanded(child: widget.child),
              ],
            ),
          ),
        ],
      ),
    ],
  ),
));
}
}


正如你所看到的,导航栏在 Jmeter 板布局中,只有登录的用户可以看到它。当实现你的共享代码时,我得到了提到的错误,在这里我分享导航栏:

enum Options { changePassword, logout }

void onSelected(item) {
  switch (item) {
  case Options.changePassword:
   debugPrint('Change Password pressed');
  break;
  case Options.logout:
   debugPrint('Change Password pressed');
  break;
}
}

class NavBar extends StatelessWidget {
const NavBar({Key? key});

@override
Widget build(BuildContext context) {
final navbarItem = AbsorbPointer(
  child: NavbarItem(
    text: 'Salir',
    icon: Icons.exit_to_app_outlined,
    onPressed: () {
      print('Hola');
    },
  ),
);

List<PopupMenuEntry<Options>> itemBuilder(context) => [
      const PopupMenuItem(
        value: Options.changePassword,
        child: Text('Change Password'),
      ),
      const PopupMenuItem(
        value: Options.logout,
        child: Text('Logout'),
      ),
    ];

final size = MediaQuery.of(context).size;
final user = Provider.of<AuthProvider>(context).user!;
final token = LocalStorage.prefs.getString('token');

return Builder(builder: (BuildContext context) {
  return Container(
    width: double.infinity,
    height: 75,
    decoration: buildBoxDecoration(),
    child: Row(
      children: [
        if (size.width <= 779) ...[
          IconButton(
            onPressed: () {
              SideMenuProvider.openMenu();
            },
            icon: const Icon(Icons.menu_outlined),
          ),
          const Spacer(),
          Padding(
            padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 2),
            child: Container(
              width: 250,
              child: const AutoSizeText(
                'SISTEMA WEB DE COSTOS Y RENTABILIDAD DEL PROCESO DEL CULTIVO DEL BANANO',
                style: TextStyle(
                  fontWeight: FontWeight.bold,
                ),
                maxLines: 5, // Número máximo de líneas permitidas
                minFontSize: 10, // Tamaño de fuente mínimo
                maxFontSize: 30, // Tamaño de fuente máximo
                overflow: TextOverflow.ellipsis, // Opción de desbordamiento
              ),
            ),
          ),
        ],
        if (size.width >= 779) ...[
          const SizedBox(
            width: 5,
          ),
          Padding(
            padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 2),
            child: Container(
              width: 250,
              child: const AutoSizeText(
                'SISTEMA WEB DE COSTOS Y RENTABILIDAD DEL PROCESO DEL CULTIVO DEL BANANO',
                style: TextStyle(
                  fontWeight: FontWeight.bold,
                ),
                maxLines: 5, // Número máximo de líneas permitidas
                minFontSize: 10, // Tamaño de fuente mínimo
                maxFontSize: 30, // Tamaño de fuente máximo
                overflow: TextOverflow.ellipsis, // Opción de desbordamiento
              ),
            ),
          ),
          const Spacer(),
          NavbarItem(
              text: 'Inicio',
              icon: Icons.home_sharp,
              onPressed: () {
                NavigationService.replaceTo(Flurorouter.dashboardRoute);
              }),
          /*NavbarItem(
            text: 'Opciones de perfil',
            icon: Icons.settings,
            onPressed: () {}),*/
          NavbarItem(
              text: 'Datos',
              icon: Icons.people,
              onPressed: () {
                NavigationService.replaceTo('/dashboard/info/$token');
              },
              isActive: false),
          NavbarItem(
              text: 'Password',
              icon: Icons.people,
              onPressed: () {
                NavigationService.replaceTo(Flurorouter.changePassRoute);
              },
              isActive: false),
          NavbarItem(
              text: user.firstName,
              icon: Icons.people,
              onPressed: () {
                //crearMenu(context);
              },
              isActive: false),
          Container(
              width: 50,
              child: Overlay(
                initialEntries: [
                  OverlayEntry(builder: (context) {
                    return PopupMenuButton(
                      onSelected: onSelected,
                      itemBuilder: itemBuilder,
                      child: navbarItem,
                    );
                  })
                ],
              )),
          NavbarItem(
            text: 'Salir',
            icon: Icons.exit_to_app_outlined,
            onPressed: () {
              Provider.of<AuthProvider>(context, listen: false).logout();
            },
          ),
        ],
      ],
    ),
  );
});
}

BoxDecoration buildBoxDecoration() => const BoxDecoration(
        gradient: LinearGradient(
          begin: Alignment.centerLeft, // Comienza desde la izquierda
          end: Alignment.centerRight, // Termina en la derecha
          colors: [
            Color(0xFF679C64), // Color de inicio
            Color(0xFFADCFAB), // Color de finalización
          ],
        ),
        boxShadow: [
        BoxShadow(
          blurRadius: 5,
          color: Colors.black12,
        )
      ]);

iswrvxsc

iswrvxsc1#

试试PopupMenuButton

最终外观可能因主题而异。
100d1x

的字符串
使用child参数指定要显示为按钮的内容,使用onSelected参数指定要如何处理在PopupMenuItem处提供的值,使用itemBuilder参数来生成项。
1.在这种情况下,请声明一个选项列表或枚举,以使其更具描述性。

enum Options { changePassword, logout }

字符串
1.用作按钮的子级,否则它可能显示默认的3点图标。这是NavbarItem,用AbsorbPointer Package 以覆盖其onPressed函数。

final navbarItem = AbsorbPointer(
  child: NavbarItem(
    text: 'Salir',
    icon: Icons.exit_to_app_outlined,
    onPressed: () {},
  ),
);


1.已选择

void onSelected(item) {
  switch (item) {
    case Options.changePassword:
      debugPrint('Change Password pressed');
      break;
    case Options.logout:
      debugPrint('Change Password pressed');
      break;
  }
}


1.项目生成器

List<PopupMenuEntry<Options>> itemBuilder(context) => [
      const PopupMenuItem(
        value: Options.changePassword,
        child: Text('Change Password'),
      ),
      const PopupMenuItem(
        value: Options.logout,
        child: Text('Logout'),
      ),
    ];


1.在您想使用的地方进行调整

PopupMenuButton(
  onSelected: onSelected,
  itemBuilder: itemBuilder,
  child: navbarItem,
),

MRE

import 'package:flutter/material.dart';

void main() {
  runApp(const App());
}

class App extends StatelessWidget {
  const App({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: Home(),
    );
  }
}

enum Options { changePassword, logout }

class Home extends StatelessWidget {
  const Home({super.key});

  @override
  Widget build(BuildContext context) {
    return Material(
      child: Center(
        child: PopupMenuButton(
          onSelected: onSelected,
          itemBuilder: itemBuilder,
          child: navbarItem,
        ),
      ),
    );
  }
}

final navbarItem = AbsorbPointer(
  child: NavbarItem(
    text: 'Salir',
    icon: Icons.exit_to_app_outlined,
    onPressed: () {},
  ),
);
void onSelected(item) {
  switch (item) {
    case Options.changePassword:
      debugPrint('Change Password pressed');
      break;
    case Options.logout:
      debugPrint('Change Password pressed');
      break;
  }
}

List<PopupMenuEntry<Options>> itemBuilder(context) => [
      const PopupMenuItem(
        value: Options.changePassword,
        child: Text('Change Password'),
      ),
      const PopupMenuItem(
        value: Options.logout,
        child: Text('Logout'),
      ),
    ];

class NavbarItem extends StatefulWidget {
  final String text;
  final IconData icon;
  final bool isActive;
  final Function onPressed;

  const NavbarItem({
    super.key,
    required this.text,
    required this.icon,
    this.isActive = false,
    required this.onPressed,
  });

  @override
  State<NavbarItem> createState() => _NavbarItemState();
}

class _NavbarItemState extends State<NavbarItem> {
  bool isHover = false;

  @override
  Widget build(BuildContext context) {
    return InkWell(
      onTap: widget.isActive ? null : () => widget.onPressed(),
      child: Padding(
        padding: const EdgeInsets.symmetric(horizontal: 15),
        child: MouseRegion(
          onEnter: (_) => setState(() {
            isHover = true;
          }),
          onExit: (_) => setState(() {
            isHover = false;
          }),
          child: Row(
            crossAxisAlignment: CrossAxisAlignment.end,
            children: [
              Icon(widget.icon),
              Text(
                widget.text,
                style: TextStyle(
                  decoration:
                      isHover ? TextDecoration.underline : TextDecoration.none,
                ),
              )
            ],
          ),
        ),
      ),
    );
  }
}

相关问题