使用AppBar和Drawer进行布局的最佳做法:重复使用与“复制/粘贴”

时间:2019-06-05 01:06:45

标签: flutter flutter-layout

我对Flutter还是很陌生,并且在构建页面布局时正在寻找一些“最佳实践”建议。我来自Java背景,在这里我总是尽可能地重复使用,但是我不确定这真的是最好的方法。我有几个页面都将有一个应用栏,但它们都有自己的动作。这些页面中的每个页面将共享一个公用抽屉。最初,我开始着手创建通用根页面小部件,在其中选择抽屉中的项目时,通用页面的主体会发生变化,如下所示:

class HomePage extends StatefulWidget {
  final BaseAuth auth;
  final Function onSignedOut;

  const HomePage({Key key, this.auth, this.onSignedOut}) : super(key: key);

  @override
  State<StatefulWidget> createState() => new _HomePageState();
}

class _HomePageState extends State<HomePage> {
  final drawerItems = [
    new DrawerItem("Home", Icons.home),
    new DrawerItem("Pantry", Icons.store),
    new DrawerItem("Barcode Scanner", Icons.scanner)
  ];

  int _selectedDrawerIndex = 0;
  bool _isEmailVerified;

  _getDrawerItemWidget(int pos) {
    switch (pos) {
      case 0:
        return new HomePageFragment();
      case 1:
        return new UserPantryFragment();
      case 2:
        return new BarcodeScannerFragment();
      default:
        return new Text("Error");
    }
  }

  _onSelectItem(int index) {
    setState(() => _selectedDrawerIndex = index);
    Navigator.of(context).pop(); // close the drawer
  }

  @override
  Widget build(BuildContext context) {
    var drawerOptions = <Widget>[];
    for (var i = 0; i < drawerItems.length; i++) {
      var d = drawerItems[i];
      drawerOptions.add(new ListTile(
        leading: new Icon(d.icon),
        title: new Text(d.title),
        selected: i == _selectedDrawerIndex,
        onTap: () => _onSelectItem(i),
      ));
    }
    AuthenticationContext authenticationContext =
        AuthenticationContext.of(context);
    return new FutureBuilder<FirebaseUser>(
        future: authenticationContext.auth.getCurrentUser(),
        initialData: null,
        builder: (BuildContext context, AsyncSnapshot<FirebaseUser> data) {
          var name = data.data != null ? data.data.displayName : "";
          var email = data.data != null ? data.data.email : " ";
          var photoUrl = data.data != null && data.data.photoUrl != null
              ? data.data.photoUrl
              : null;
          return new Scaffold(
              appBar: new AppBar(
                  title: new Text(drawerItems[_selectedDrawerIndex].title),
                  actions: <Widget>[
                    IconButton(
                      icon: Icon(Icons.search),
                      onPressed: () {
                      },
                    ),
                    // overflow menu
                    PopupMenuButton<String>(
//                      onSelected: _signOut,
                      itemBuilder: (BuildContext context) {
                        return ['Logout'].map((String choice) {
                          return PopupMenuItem<String>(
                            value: choice,
                            child: Text(choice),
                          );
                        }).toList();
                      },
                    )
                  ]),
              drawer: new Drawer(
                child: new Column(
                  children: <Widget>[
                    UserAccountsDrawerHeader(
                      accountName: Text(name != null ? name : ""),
                      accountEmail: Text(email),
                      currentAccountPicture: CircleAvatar(
//                        backgroundImage: FadeInImage.memoryNetwork(
//                          placeholder: kTransparentImage,
//                          image: photoUrl != null ? photoUrl : "",
//                        ).image,
                        child: new Text(
                            photoUrl == null ? email[0].toUpperCase() : ""),
                      ),
                    ),
                    new Column(children: drawerOptions)
                  ],
                ),
              ),
              body: _getDrawerItemWidget(_selectedDrawerIndex));
        });
  }

但是,我现在想知道在每个屏幕上从头开始创建Scaffold而不是尝试使用共享的根页面是否会更好,因为我遇到了为每个页面轻松自定义AppBar的问题。最初我以为我可以在每个页面小部件上创建一些“ buildAppBar”函数并让根页面使用它,但这似乎不是一个容易实现的解决方案……至少不是以一种优雅的方式,我可以找到。

2 个答案:

答案 0 :(得分:1)

您可以扩展StatelessWidget以向类添加自定义参数,并在build方法中返回自定义的Scaffold。类似于:

class MyScaffold extends StatelessWidget {
  final Widget option1;
  final Widget option2;
  final Widget body;

  const MyScaffold({
    this.option1,
    this.option2,
    this.body,
    Key key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: option1,
      drawer: option2,
      body: body,
    );
  }
}

您还可以复制Scaffold类中的其他属性,并将它们作为成员添加到MyScaffold中(记住要像body和options参数那样在构造函数中对其进行初始化)。

另一种将状态(读取:变量)传递到小部件树的选项是InheritedWidget

答案 1 :(得分:0)

为抽屉创建一个单独的小部件,并在需要的地方使用它。

使用Provider to Manage State

管理抽屉状态
class DrawerStateInfo with ChangeNotifier {
  int _currentDrawer = 0;
  int get getCurrentDrawer => _currentDrawer;

  void setCurrentDrawer(int drawer) {
    _currentDrawer = drawer;
    notifyListeners();
  }

  void increment() {
    notifyListeners();
  }
}

将状态管理添加到小部件树

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      child: MaterialApp(
        title: 'Flutter Demo',
        theme: ThemeData(
          primarySwatch: Colors.teal,
        ),
        home: MyHomePage(title: 'Flutter Demo Home Page'),
      ),
      providers: <SingleChildCloneableWidget>[
        ChangeNotifierProvider<DrawerStateInfo>(
            builder: (_) => DrawerStateInfo()),
      ],
    );
  }
}

创建抽屉小部件以在应用程序中重用

class MyDrawer extends StatelessWidget {
  MyDrawer(this.currentPage);

  final String currentPage;

  @override
  Widget build(BuildContext context) {
    var currentDrawer = Provider.of<DrawerStateInfo>(context).getCurrentDrawer;
    return Drawer(
      child: ListView(
        children: <Widget>[
          ListTile(
            title: Text(
              "Home",
              style: currentDrawer == 0
                  ? TextStyle(fontWeight: FontWeight.bold)
                  : TextStyle(fontWeight: FontWeight.normal),
            ),
            trailing: Icon(Icons.arrow_forward),
            onTap: () {
              Navigator.of(context).pop();
              if (this.currentPage == "Home") return;

              Provider.of<DrawerStateInfo>(context).setCurrentDrawer(0);

              Navigator.of(context).pushReplacement(MaterialPageRoute(
                  builder: (BuildContext context) =>
                      MyHomePage(title: "Home")));
            },
          ),
          ListTile(
            title: Text(
              "About",
              style: currentDrawer == 1
                  ? TextStyle(fontWeight: FontWeight.bold)
                  : TextStyle(fontWeight: FontWeight.normal),
            ),
            trailing: Icon(Icons.arrow_forward),
            onTap: () {
              Navigator.of(context).pop();
              if (this.currentPage == "About") return;

              Provider.of<DrawerStateInfo>(context).setCurrentDrawer(1);

              Navigator.of(context).pushReplacement(MaterialPageRoute(
                  builder: (BuildContext context) => MyAboutPage()));
            },
          ),
        ],
      ),
    );
  }
}

在您的页面之一中使用抽屉

class MyAboutPage extends StatefulWidget {
  @override
  _MyAboutPageState createState() => _MyAboutPageState();
}

class _MyAboutPageState extends State<MyAboutPage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('About Page'),
      ),
      drawer: MyDrawer("About"),
    );
  }
}