【问题标题】:Best practices for layouts with AppBar and Drawer: re-use vs "copy/paste"AppBar 和 Drawer 布局的最佳实践:重用 vs “复制/粘贴”
【发布时间】:2019-10-20 12:38:51
【问题描述】:

我是 Flutter 的新手,在构建页面布局时正在寻找一些“最佳实践”建议。我来自 Java 背景,我总是尽可能地重复使用,但我不确定这是否真的是最好的方法。我有几个页面都有一个 Appbar 但有自己的操作。这些页面中的每一个都将共享一个公共抽屉。最初,我开始沿着创建公共根页面 Widget 的路径前进,当在抽屉中选择一个项目时,公共页面的正文会发生变化,如下所示:

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));
        });
  }

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

【问题讨论】:

    标签: flutter flutter-layout


    【解决方案1】:

    为抽屉创建一个单独的小部件,然后在您需要的任何地方使用。

    使用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"),
        );
      }
    }
    

    【讨论】:

      【解决方案2】:

      您可以扩展StatelessWidget 以将自定义参数添加到类并在构建方法中返回自定义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 和选项参数)。

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-05-05
        • 1970-01-01
        • 1970-01-01
        • 2012-04-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多