dart 在所有页面上保持AppBar Drawer

xjreopfe  于 2022-12-15  发布在  其他
关注(0)|答案(7)|浏览(129)

我正在尝试创建一个可在应用中所有页面访问的统一抽屉。如何使其在所有页面中持久存在,而无需在每个dart文件中重新创建我的自定义抽屉小部件?

nzk0hqpo

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,以便它能够实际更改其中的页面。
正如我所说的,我不推荐这种方法,所以我不打算深入任何细节,而是把它作为一个练习留给读者,如果他们想进入兔子洞!

qyyhg6bp

qyyhg6bp2#

rmtmckenzie是非常正确的。
如果您对多支架解决方案感到好奇,这可能比您想象的更优雅。
要在所有页面之间共享一个抽屉,我们可以在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)重新使用

kx7yvsdv

kx7yvsdv3#

除了@Rémi Rousselet的回答

MaterialApp(
 title: 'Flutter Demo',
 builder: (context, child) {
   return Scaffold(
     drawer: MyDrawer(),
     body: child,
   );
 },
 home: MyHome()
)

对于根抽屉中的导航,如果使用Navigator.of(context) // push or pop,则会抛出错误,因此必须使用子部件导航到不同的页面
像这样

(child.key as GlobalKey<NavigatorState>).currentState // push or pop

Demo project in Github

vohkndzv

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。

u0njafvf

u0njafvf5#

我的解决方案导航抽屉,包含使用块包的多个片段
首先,在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

aiazj4mn

aiazj4mn6#

除了@Rémi Rousselet Answer之外,由于安全性修正无效,代码略有变化(2022)。替换如下:

class RootScaffold {
   static openDrawer(BuildContext context) {
      final ScaffoldState scaffoldState =
       context.rootAncestorStateOfType(TypeMatcher<ScaffoldState>());
      scaffoldState.openDrawer();
   }
}

和...

class RootScaffold {
    static openDrawer(BuildContext context) {    
       final ScaffoldState? scaffoldState = context.findRootAncestorStateOfType<ScaffoldState>();
       scaffoldState?.openDrawer();
    }
  }
irtuqstp

irtuqstp7#

当你确保所有的页面只有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

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

相关问题