Flutter web - 如何监听打开的抽屉状态并关闭它

Flutter web - How to listen on open Drawer state and close it

我正在开发 Flutter 响应式 Web UI。而且我想在特定屏幕宽度上关闭打开的抽屉,用于移动和桌面屏幕宽度,所以如果我拉伸浏览器,抽屉应该关闭。

比如我打开了抽屉(屏幕宽度小于500)

并且当屏幕宽度大于500时,我想让打开的抽屉自动关闭

注意:当抽屉打开时。我已经有一个代码可以检查是否显示按钮菜单抽屉的屏幕宽度。但基本上,当用户打开抽屉然后突然拉伸浏览器时,抽屉应该关闭。

代码如下。感谢帮助

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final size = MediaQuery.of(context).size.width;

    return Scaffold(
      drawer: Drawer(),
      body: CustomNavBar(screenSize: size),
    );
  }
}

class CustomNavBar extends StatefulWidget {
  final double screenSize;
  const CustomNavBar({Key key, this.screenSize}) : super(key: key);

  @override
  _CustomNavBarState createState() => _CustomNavBarState();
}

class _CustomNavBarState extends State<CustomNavBar> {
  @override
  Widget build(BuildContext context) {
    if (Scaffold.of(context).isDrawerOpen && widget.screenSize > 500) {
      print("Drawer is Opened");
      Scaffold.of(context).openEndDrawer(); //animation error
      setState(() {});
    }

    return widget.screenSize > 500
        ? Container(color: Colors.red) //desktop screen
        : Center(
            //mobile screen
            child: IconButton(
              icon: Icon(Icons.menu),
              onPressed: () => Scaffold.of(context).openDrawer(),
            ),
          );
  }
}

您不必手动关闭抽屉。当屏幕宽度小于 500 时,为什么不干脆去掉抽屉?

class SampleDrawer extends StatelessWidget {
  final GlobalKey<ScaffoldState> k = GlobalKey();

  @override
  Widget build(BuildContext context) {
    // new
    final size = MediaQuery.of(context).size.width;
    if (k.currentState.isDrawerOpen && size < 500) {
      Navigator.pop(context); // close drawer
    }

    return Scaffold(
      key: k,
      drawer: size > 500 ? Drawer() : null,
      body: CustomNavBar(),
    );
  }
}

class CustomNavBar extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final size = MediaQuery.of(context).size.width;

    return size > 500
        ? Container(color: Colors.red) //desktop screen
        : Center( //mobile screen
            child: IconButton(
              icon: Icon(Icons.menu),
              onPressed: () => Scaffold.of(context).openDrawer(),
            ),
          );
  }
}

Scaffold会在设备宽度变化时重建,宽度小于500会自动省略drawer

这是解决方案。

You're Code is enough. Just a few changes to your code

  1. 将此 Scaffold.of(context).openEndDrawer(); 包装在

    WidgetsBinding.instance.addPostFrameCallback((_) {
      Scaffold.of(context).openEndDrawer(); //No Error
        ///The Error was coming, As you're trying to build a widget when it is 
        ///rebuilding widget Tree due to the change in the width of the browser.
        ///Wrapping it inside ensures that the code will run after the build.
    });
    
  2. 不要使用setState(() {});

  3. 使用 520 而不是 500


原因

The error was coming, As you're trying to build a widget when it is rebuilding Widget Tree due to the change in the width of the browser. Wrapping this Scaffold.of(context).openEndDrawer(); inside WidgetsBinding.instance.addPostFrameCallback((_) {}); ensures that the code will run after the widget get's build.

这是更新后的代码

  class HomePage extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
      final size = MediaQuery.of(context).size.width;
      print(size);
      return Scaffold(
        drawer: Drawer(),
        body: CustomNavBar(
          screenSize: size,
        ),
      );
    }
  }
  
  class CustomNavBar extends StatefulWidget {
    final double screenSize;
  
    const CustomNavBar({
      Key key,
      this.screenSize,
    }) : super(key: key);
  
    @override
    _CustomNavBarState createState() => _CustomNavBarState();
  }
  
  class _CustomNavBarState extends State<CustomNavBar> {
    @override
    Widget build(BuildContext context) {
      if (Scaffold.of(context).isDrawerOpen && widget.screenSize > 520) {
        print("Drawer is Opened");
        WidgetsBinding.instance.addPostFrameCallback((_) {
          Scaffold.of(context).openEndDrawer(); //No Error
          ///The error was coming, As you're trying to build a widget when it is 
          ///rebuilding widget Tree due to the change in the width of the browser.
          ///Wrapping it inside ensure that the code will run after the build.
        });
        // Don't call setState((){}); Not Required;
        // as every time you change the width it rebuilds all the widget again
        // setState(() {});
      }
  
      return widget.screenSize > 520
          ? Container(color: Colors.red) //desktop screen
          : Center(
              //mobile screen
              child: IconButton(
                icon: Icon(Icons.menu),
                onPressed: () => Scaffold.of(context).openDrawer(),
              ),
            );
    }
  }

错误是

  The following assertion was thrown while notifying status listeners for 
  AnimationController:
  setState() or markNeedsBuild() called during build.
  This Scaffold widget cannot be marked as needing to build because the framework is 
  already in the
  process of building widgets.  A widget can be marked as needing to be built during 
  the build phase
  only if one of its ancestors is currently building. This exception is allowed 
  because the framework
  builds parent widgets before children, which means a dirty descendant will always be 
  built.
  Otherwise, the framework might not visit this widget during this build phase.
  The widget on which setState() or markNeedsBuild() was called was:
  Scaffold
  The widget which was currently being built when the offending call was made was:
  CustomNavBar

提示

Use the Layout Builder for Responsive UI.

@override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (context, constraints) {
        if (constraints.maxWidth > 800) {
          return XScreen();
        } else if (constraints.maxWidth < 1200 && constraints.maxWidth > 800) {
          return Yscreen()?? ZScreen();
        } else {
          return XScreen()?? ZScreeen();
        }
      },
    );
  }