我正在尝试创建一个可在应用中所有页面访问的统一抽屉。如何使其在所有页面中持久存在,而无需在每个dart文件中重新创建我的自定义抽屉小部件?
nzk0hqpo1#
这里有几种不同的选择,最基本的可能是你已经做过的,但我还是要列出它:
1:为抽屉创建类
你的小部件应该是有状态的或者无状态的,这样的话,你只需要每次示例化它。
class MyDrawer extends StatelessWidget { @override Widget build(BuildContext context) { return Drawer(...); } }
然后在每页使用时:
Scaffold( drawer: MyDrawer(...), ... )
我希望你已经在这么做了如果没有,那么您应该这样做。类构建函数不应该太大,否则会导致性能低下,且更难维护代码;从长远来看,将事物划分为逻辑单元将对您有所帮助。
**2:**为您的scaffold创建一个类
如果在一个scaffold中为每个页面包含相同的drawer仍然需要太多的代码,那么可以使用一个类来封装scaffold,它实际上会为您实际使用的每个scaffold输入获取输入。
class MyScaffold extends StatelessWidget { final Widget body; MyScaffold({this.body}); @override Widget build(BuildContext context) { return Scaffold( body: body, drawer: MyDrawer(...), ); } }
然后在代码中使用MyScaffold(但请将其命名为更好的名称=D),而不是使用Scaffold。
3:多层脚手架
我只是为了完整起见才加入这种方式,我并不推荐这种方式,也就是说,有些事情在flutter的正常工作流程中是无法实现的,而你可以通过这种方式来完成--例如,如果你想要一个自定义的动画,当用户点击抽屉中的不同物品时。基本上,在这种情况下,您要做的是在MaterialApp或Navigator之外创建一个Scaffold(我相信这也意味着你必须有另一个领航员以外的,但我不是100%肯定)。您会让导航栏之外的scaffold显示抽屉,而另一个scaffold显示抽屉(在导航中的每个页面上)将执行您需要它执行的任何其他操作。有一些警告-您必须确保获得正确的支架(也就是说,Scaffold.of(context)本身并不能解决这个问题--您必须获取第一个scaffold的上下文,并使用它来查找更高级别的scaffold),并且您可能需要将一个GlobalKey(属于较低级别的scaffold)传递给Drawer,以便它能够实际更改其中的页面。正如我所说的,我不推荐这种方法,所以我不打算深入任何细节,而是把它作为一个练习留给读者,如果他们想进入兔子洞!
Scaffold.of(context)
qyyhg6bp2#
rmtmckenzie是非常正确的。如果您对多支架解决方案感到好奇,这可能比您想象的更优雅。要在所有页面之间共享一个抽屉,我们可以在MaterialApp示例中添加一个builder,这将在Navigator下但在所有路由之上示例化一个Scaffold。
MaterialApp
builder
Navigator
Scaffold
MaterialApp( title: 'Flutter Demo', builder: (context, child) { return Scaffold( drawer: MyDrawer(), body: child, ); }, home: MyHome() )
在页面内部,可以像往常一样不受限制地示例化另一个Scaffold。然后,您可以通过在 * MaterialApp下的任何小工具 * 中执行以下操作来显示共享抽屉:
final ScaffoldState scaffoldState = context.rootAncestorStateOfType(TypeMatcher<ScaffoldState>()); scaffoldState.openDrawer();
您可以提取到一个很好的助手的代码:
class RootScaffold { static openDrawer(BuildContext context) { final ScaffoldState scaffoldState = context.rootAncestorStateOfType(TypeMatcher<ScaffoldState>()); scaffoldState.openDrawer(); } }
然后使用RootScaffold.openDrawer(context)重新使用
RootScaffold.openDrawer(context)
kx7yvsdv3#
除了@Rémi Rousselet的回答
对于根抽屉中的导航,如果使用Navigator.of(context) // push or pop,则会抛出错误,因此必须使用子部件导航到不同的页面像这样
Navigator.of(context) // push or pop
(child.key as GlobalKey<NavigatorState>).currentState // push or pop
Demo project in Github
vohkndzv4#
如果有人在导航时寻找花哨的东西,看看这里。我用来作为我项目的抽屉的是flutter_inner_drawer包。我创建了一个名为CustomDrawer的有状态类。
class CustomDrawer extends StatefulWidget { final Widget scaffold; final GlobalKey<InnerDrawerState> innerDrawerKey; CustomDrawer({ Key key, this.scaffold, this.innerDrawerKey, }) : super(key: key); @override _CustomDrawerState createState() => _CustomDrawerState(); } class _CustomDrawerState extends State<CustomDrawer> { MainPageIcons assets = MainPageIcons();//From my actual code dont care it final vars = GlobalVars.shared; //From my actual code dont care it @override Widget build(BuildContext context) { return InnerDrawer( key: widget.innerDrawerKey, onTapClose: true, // default false tapScaffoldEnabled: true, swipe: true, // default true colorTransition: Colors.teal, // default Color.black54 //innerDrawerCallback: (a) => print(a ),// return bool leftOffset: 0.2, // default 0.4 leftScale: 1,// default 1 boxShadow: [ BoxShadow(color: Colors.teal,blurRadius: 20.0, // has the effect of softening the shadow spreadRadius: 10.0, // has the effect of extending the shadow offset: Offset( 10.0, // horizontal, move right 10 10.0, // vertical, move down 10 ),)], borderRadius: 20, // default 0 leftAnimationType: InnerDrawerAnimation.quadratic, // default static //when a pointer that is in contact with the screen and moves to the right or left onDragUpdate: (double val, InnerDrawerDirection direction) => setState(() => _dragUpdate = val), //innerDrawerCallback: (a) => print(a), // innerDrawerCallback: (a) => print(a), // return true (open) or false (close) leftChild: menus(), // required if rightChild is not set scaffold:widget.scaffold ); } double _dragUpdate = 0; Widget menus(){ return Material( child: Stack( children: <Widget>[ Container( decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topRight, end: Alignment.bottomLeft, colors: [ ColorTween( begin: Colors.blueAccent, end: Colors.blueGrey[400].withRed(100), ).lerp(_dragUpdate), ColorTween( begin: Colors.green, end: Colors.blueGrey[800].withGreen(80), ).lerp(_dragUpdate), ], ), ), child: Stack( children: <Widget>[ Padding( padding: EdgeInsets.only(left: 30), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Column( children: <Widget>[ Container( margin: EdgeInsets.only(left: 10, bottom: 15), width: 80, child: ClipRRect( child: Image.network( "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSrWfWLnxIT5TnuE-JViLzLuro9IID2d7QEc2sRPTRoGWpgJV75", ), borderRadius: BorderRadius.circular(60), ), ), Text( "User", style: TextStyle(color: Colors.white, fontSize: 18), ) ], //mainAxisAlignment: MainAxisAlignment.center, ), Padding( padding: EdgeInsets.all(10), ), ListTile( onTap: ()=>navigate(Profile.tag), title: Text( "Profile", style: TextStyle(color: Colors.white, fontSize: 14), ), leading: Icon( Icons.dashboard, color: Colors.white, size: 22, ), ), ListTile( title: Text( "Camera", style: TextStyle(fontSize: 14,color:Colors.white), ), leading: Icon( Icons.camera, size: 22, color: Colors.white, ), onTap: ()=>navigate(Camera.tag) ), ListTile( title: Text( "Pharmacies", style: TextStyle(fontSize: 14,color:Colors.white), ), leading: Icon( Icons.add_to_photos, size: 22, color: Colors.white, ), onTap: ()=>navigate(Pharmacies.tag) ), ], ), ), Positioned( bottom: 20, child: Container( alignment: Alignment.bottomLeft, margin: EdgeInsets.only(top: 50), padding: EdgeInsets.symmetric(vertical: 15, horizontal: 25), width: double.maxFinite, child: Row( mainAxisAlignment: MainAxisAlignment.start, children: <Widget>[ Icon( Icons.all_out, size: 18, color: Colors.grey, ), Text( " LogOut", style: TextStyle( fontSize: 16, color: Colors.grey, ), ), ], ), ), ) ], ), ), _dragUpdate < 1 ? BackdropFilter( filter: ImageFilter.blur( sigmaX: (10 - _dragUpdate * 10), sigmaY: (10 - _dragUpdate * 10)), child: Container( decoration: BoxDecoration( color: Colors.black.withOpacity(0), ), ), ) : null, ].where((a) => a != null).toList(), )); } navigate(String route) async{ await navigatorKey.currentState.pushNamed(route).then((_){ Timer(Duration(milliseconds: 500),()=>widget.innerDrawerKey.currentState.toggle() ); }); } }
我从软件包中复制了一个例子,没有太多的接触到原来的。只有一个功能切换后返回。
navigate(String route) async{ await navigatorKey.currentState.pushNamed(route).then((_){ Timer(Duration(milliseconds: 500),()=>widget.innerDrawerKey.currentState.toggle() ); }); }
从全局使用GlobalKey的所有页面导航,以便可以从每个类访问
final GlobalKey<NavigatorState> navigatorKey = GlobalKey(debugLabel: "Main Navigator");
inner_drawer也需要一个globalkey状态toogle,但如果你只创建一个当导航之间的页面,它给出重复的全局键错误.为了避免我创建了一个名为innerKeys的全局变量
Map<String,GlobalKey<InnerDrawerState>>innerKeys={ 'main':GlobalKey<InnerDrawerState>(), 'profile':GlobalKey<InnerDrawerState>(), 'pharmacies':GlobalKey<InnerDrawerState>(), };
最后我把这个CustomDrawer添加到每个页面
@override Widget build(BuildContext context) { return CustomDrawer( innerDrawerKey: vars.innerKeys['profile'], scaffold:Scaffold( appBar: CustomAppBar( title: 'Profile', actions: <Widget>[ ],), body: Stack( children: <Widget>[ Background(), ]))); }
我希望这会对某人有所帮助。注意:请检查原始的flutter包,如果有任何更新。是avare,这个例子是不完美的,需要注意,如果许多导航在这个抽屉,然后部件树将有许多页面和性能将受到影响。任何调整建议将appriciated。
u0njafvf5#
我的解决方案导航抽屉,包含使用块包的多个片段首先,在pubspec.yaml文件中添加以下dependencies
pubspec.yaml
dependencies
flutter_bloc: ^4.0.0
现在创建以下文件抽屉_事件.省道
import 'nav_drawer_state.dart'; abstract class NavDrawerEvent { const NavDrawerEvent(); } class NavigateTo extends NavDrawerEvent { final NavItem destination; const NavigateTo(this.destination); }
导航抽屉块.dart
import 'package:bloc/bloc.dart'; import 'drawer_event.dart'; import 'nav_drawer_state.dart'; class NavDrawerBloc extends Bloc<NavDrawerEvent, NavDrawerState> { @override NavDrawerState get initialState => NavDrawerState(NavItem.homePage); @override Stream<NavDrawerState> mapEventToState(NavDrawerEvent event) async* { if (event is NavigateTo) { if (event.destination != state.selectedItem) { yield NavDrawerState(event.destination); } } } }
导航抽屉状态.dart
class NavDrawerState { final NavItem selectedItem; const NavDrawerState(this.selectedItem); } enum NavItem { homePage, profilePage, orderPage, myCart, }
抽屉_小工具.dart
import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutterdrawerwithbloc/bloc/drawer_event.dart'; import 'package:flutterdrawerwithbloc/bloc/nav_drawer_bloc.dart'; import 'package:flutterdrawerwithbloc/bloc/nav_drawer_state.dart'; class NavDrawerWidget extends StatelessWidget { final String accountName; final String accountEmail; final List<_NavigationItem> _listItems = [ _NavigationItem(true, null, null, null), _NavigationItem(false, NavItem.homePage, "Home", Icons.home), _NavigationItem(false, NavItem.profilePage, "Profile Page", Icons.person), _NavigationItem(false, NavItem.orderPage, "My Orders", Icons.list), _NavigationItem(false, NavItem.myCart, "My Cart", Icons.shopping_cart), ]; NavDrawerWidget(this.accountName, this.accountEmail); @override Widget build(BuildContext context) => Drawer( child: Container( child: ListView.builder( padding: EdgeInsets.zero, itemCount: _listItems.length, itemBuilder: (BuildContext context, int index) => BlocBuilder<NavDrawerBloc, NavDrawerState>( builder: (BuildContext context, NavDrawerState state) => _buildItem(_listItems[index], state), )), )); Widget _buildItem(_NavigationItem data, NavDrawerState state) => data.header ? _makeHeaderItem() : _makeListItem(data, state); Widget _makeHeaderItem() => UserAccountsDrawerHeader( accountName: Text(accountName, style: TextStyle(color: Colors.white)), accountEmail: Text(accountEmail, style: TextStyle(color: Colors.white)), decoration: BoxDecoration(color: Colors.indigo), currentAccountPicture: CircleAvatar( backgroundColor: Colors.white, foregroundColor: Colors.amber, child: Icon( Icons.person, size: 54, ), ), ); Widget _makeListItem(_NavigationItem data, NavDrawerState state) => Card( shape: ContinuousRectangleBorder(borderRadius: BorderRadius.zero), borderOnForeground: true, elevation: 0, margin: EdgeInsets.zero, child: Builder( builder: (BuildContext context) => ListTile( title: Text( data.title, style: TextStyle( color: data.item == state.selectedItem ? Colors.green : Colors.blueGrey, ), ), leading: Icon( data.icon, color: data.item == state.selectedItem ? Colors.green : Colors.blueGrey, ), onTap: () => _handleItemClick(context, data.item), ), ), ); void _handleItemClick(BuildContext context, NavItem item) { BlocProvider.of<NavDrawerBloc>(context).add(NavigateTo(item)); Navigator.pop(context); } } class _NavigationItem { final bool header; final NavItem item; final String title; final IconData icon; _NavigationItem(this.header, this.item, this.title, this.icon); }
main.dart
import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutterdrawerwithbloc/bloc/nav_drawer_bloc.dart'; import 'package:flutterdrawerwithbloc/bloc/nav_drawer_state.dart'; import 'package:flutterdrawerwithbloc/drawer_widget.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, title: 'Navigation Drawer Demo', theme: ThemeData(primarySwatch: Colors.blue, scaffoldBackgroundColor: Colors.white), home: MyHomePage(), ); ; } } class MyHomePage extends StatefulWidget { @override _MyHomePageState createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { NavDrawerBloc _bloc; Widget _content; @override void initState() { super.initState(); _bloc = NavDrawerBloc(); _content = _getContentForState(_bloc.state.selectedItem); } @override void dispose() { super.dispose(); } @override Widget build(BuildContext context) => BlocProvider<NavDrawerBloc>( create: (BuildContext context) => _bloc, child: BlocListener<NavDrawerBloc, NavDrawerState>( listener: (BuildContext context, NavDrawerState state) { setState(() { _content = _getContentForState(state.selectedItem); }); }, child: BlocBuilder<NavDrawerBloc, NavDrawerState>( builder: (BuildContext context, NavDrawerState state) => Scaffold( drawer: NavDrawerWidget("AskNilesh", "rathodnilsrk@gmail.com"), appBar: AppBar( title: Text(_getAppbarTitle(state.selectedItem)), centerTitle: false, brightness: Brightness.light, backgroundColor: Colors.indigo, ), body: AnimatedSwitcher( switchInCurve: Curves.easeInExpo, switchOutCurve: Curves.easeOutExpo, duration: Duration(milliseconds: 300), child: _content, ), ), ), )); _getAppbarTitle(NavItem state) { switch (state) { case NavItem.homePage: return 'Home'; case NavItem.profilePage: return 'Profile Page'; case NavItem.orderPage: return 'My Orders'; case NavItem.myCart: return 'My Cart'; default: return ''; } } _getContentForState(NavItem state) { switch (state) { case NavItem.homePage: return Center( child: Text( 'Home Page', style: TextStyle(fontWeight: FontWeight.bold), ), ); case NavItem.profilePage: return Center( child: Text( 'Profile Page', style: TextStyle(fontWeight: FontWeight.bold), ), ); case NavItem.orderPage: return Center( child: Text( 'My Orders', style: TextStyle(fontWeight: FontWeight.bold), ), ); case NavItem.myCart: return Center( child: Text( 'My Cart', style: TextStyle(fontWeight: FontWeight.bold), ), ); default: return Center( child: Text( 'Home Page', style: TextStyle(fontWeight: FontWeight.bold), ), ); } } }
您可以在这里找到完整的项目Navigation Drawer with Multiple Fragments using bloc
aiazj4mn6#
除了@Rémi Rousselet Answer之外,由于安全性修正无效,代码略有变化(2022)。替换如下:
和...
class RootScaffold { static openDrawer(BuildContext context) { final ScaffoldState? scaffoldState = context.findRootAncestorStateOfType<ScaffoldState>(); scaffoldState?.openDrawer(); } }
irtuqstp7#
当你确保所有的页面只有body不同时,你可以创建ScaffoldCustom,但是我觉得这种方法限制性太强,所以我使用了这种方法。对于AppBar:
body
ScaffoldCustom
AppBar
class AppBarPattern1 extends StatelessWidget implements PreferredSizeWidget { const AppBarPattern1({Key? key}) : super(key: key); @override // TODO: implement preferredSize Size get preferredSize => const Size.fromHeight(kToolbarHeight); // You can change it. /* /// The height of the toolbar component of the [AppBar]. const double kToolbarHeight = 56.0; */ @override Widget build(BuildContext context) { return AppBar(); } }
对于Drawer:
Drawer
class DrawerPattern1 extends StatelessWidget { const DrawerPattern1({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return const Drawer(); } }
像这样使用:
class ExamplePage extends StatelessWidget { const ExamplePage({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( appBar: const AppBarPattern1(), endDrawer: const DrawerPattern1(), body: SafeArea(child: Container()), ); } }
如您所见,这些自定义小部件可以是const。
const
7条答案
按热度按时间nzk0hqpo1#
这里有几种不同的选择,最基本的可能是你已经做过的,但我还是要列出它:
1:为抽屉创建类
你的小部件应该是有状态的或者无状态的,这样的话,你只需要每次示例化它。
然后在每页使用时:
我希望你已经在这么做了如果没有,那么您应该这样做。类构建函数不应该太大,否则会导致性能低下,且更难维护代码;从长远来看,将事物划分为逻辑单元将对您有所帮助。
**2:**为您的scaffold创建一个类
如果在一个scaffold中为每个页面包含相同的drawer仍然需要太多的代码,那么可以使用一个类来封装scaffold,它实际上会为您实际使用的每个scaffold输入获取输入。
然后在代码中使用MyScaffold(但请将其命名为更好的名称=D),而不是使用Scaffold。
3:多层脚手架
我只是为了完整起见才加入这种方式,我并不推荐这种方式,也就是说,有些事情在flutter的正常工作流程中是无法实现的,而你可以通过这种方式来完成--例如,如果你想要一个自定义的动画,当用户点击抽屉中的不同物品时。
基本上,在这种情况下,您要做的是在MaterialApp或Navigator之外创建一个Scaffold(我相信这也意味着你必须有另一个领航员以外的,但我不是100%肯定)。您会让导航栏之外的scaffold显示抽屉,而另一个scaffold显示抽屉(在导航中的每个页面上)将执行您需要它执行的任何其他操作。有一些警告-您必须确保获得正确的支架(也就是说,
Scaffold.of(context)
本身并不能解决这个问题--您必须获取第一个scaffold的上下文,并使用它来查找更高级别的scaffold),并且您可能需要将一个GlobalKey(属于较低级别的scaffold)传递给Drawer,以便它能够实际更改其中的页面。正如我所说的,我不推荐这种方法,所以我不打算深入任何细节,而是把它作为一个练习留给读者,如果他们想进入兔子洞!
qyyhg6bp2#
rmtmckenzie是非常正确的。
如果您对多支架解决方案感到好奇,这可能比您想象的更优雅。
要在所有页面之间共享一个抽屉,我们可以在
MaterialApp
示例中添加一个builder
,这将在Navigator
下但在所有路由之上示例化一个Scaffold
。在页面内部,可以像往常一样不受限制地示例化另一个
Scaffold
。然后,您可以通过在 *
MaterialApp
下的任何小工具 * 中执行以下操作来显示共享抽屉:您可以提取到一个很好的助手的代码:
然后使用
RootScaffold.openDrawer(context)
重新使用kx7yvsdv3#
除了@Rémi Rousselet的回答
对于根抽屉中的导航,如果使用
Navigator.of(context) // push or pop
,则会抛出错误,因此必须使用子部件导航到不同的页面像这样
Demo project in Github
vohkndzv4#
如果有人在导航时寻找花哨的东西,看看这里。我用来作为我项目的抽屉的是flutter_inner_drawer包。
我创建了一个名为CustomDrawer的有状态类。
我从软件包中复制了一个例子,没有太多的接触到原来的。只有一个功能切换后返回。
从全局使用GlobalKey的所有页面导航,以便可以从每个类访问
inner_drawer也需要一个globalkey状态toogle,但如果你只创建一个当导航之间的页面,它给出重复的全局键错误.为了避免我创建了一个名为innerKeys的全局变量
最后我把这个CustomDrawer添加到每个页面
我希望这会对某人有所帮助。
注意:请检查原始的flutter包,如果有任何更新。是avare,这个例子是不完美的,需要注意,如果许多导航在这个抽屉,然后部件树将有许多页面和性能将受到影响。任何调整建议将appriciated。
u0njafvf5#
我的解决方案导航抽屉,包含使用块包的多个片段
首先,在
pubspec.yaml
文件中添加以下dependencies
现在创建以下文件
抽屉_事件.省道
导航抽屉块.dart
导航抽屉状态.dart
抽屉_小工具.dart
main.dart
您可以在这里找到完整的项目Navigation Drawer with Multiple Fragments using bloc
aiazj4mn6#
除了@Rémi Rousselet Answer之外,由于安全性修正无效,代码略有变化(2022)。替换如下:
和...
irtuqstp7#
当你确保所有的页面只有
body
不同时,你可以创建ScaffoldCustom
,但是我觉得这种方法限制性太强,所以我使用了这种方法。对于
AppBar
:对于
Drawer
:像这样使用:
如您所见,这些自定义小部件可以是
const
。