Flutter:如何以编程方式打开抽屉

82

我希望能通过编程方式打开Drawer,而不是通过滑动来打开它。如何禁用该滑动功能(即Drawer的触摸功能)?


你想通过滑动来访问它吗? - Son of Stackoverflow
不,我想禁用触摸滑动,我只想通过编程方式打开它。 - krishnaji
8个回答

183

Null safe code

  • 使用GlobalKey

    final GlobalKey<ScaffoldState> _key = GlobalKey(); // Create a key
    
    @override
    Widget build(BuildContext context) {
      return Scaffold(
        key: _key, // Assign the key to Scaffold.
        drawer: Drawer(),
        floatingActionButton: FloatingActionButton(
          onPressed: () => _key.currentState!.openDrawer(), // <-- Opens drawer
        ),
      );
    }
    
  • 使用Builder

    @override
    Widget build(BuildContext context) {
      return Scaffold(
        drawer: Drawer(),
        floatingActionButton: Builder(builder: (context) {
          return FloatingActionButton(
            onPressed: () => Scaffold.of(context).openDrawer(), // <-- Opens drawer.
          );
        }),
      );
    }
    

如果您想禁用使用拖动手势打开 Drawer,可以设置

Scaffold(
  drawerEnableOpenDragGesture: false
  // above code ...
)

谢谢您的回答,抱歉我表述不清楚,实际上是滑动而非点击,如何禁用该滑动功能,我只想通过编程来打开它。 - krishnaji
你非常聪明,你只是简单地改变了问题,最初是点击,现在是滑动。 - CopsOnRoad
据我所知,没有办法通过滑动停止抽屉的打开,这违反了材料设计指南,您可以创建自己的自定义抽屉来实现该效果。 - CopsOnRoad
2
是的,我修改了问题,因为我的提问有误,对此很抱歉,能否请您提供构建自定义抽屉的源代码? - krishnaji
1
这应该是被接受的答案,因为被接受的答案禁用了滑动打开功能,而这实际上是不需要的行为。 - Sjoerd Van Den Berg
显示剩余2条评论

87
  1. 要禁用滑动打开功能,您可以将Scaffold的属性drawerEnableOpenDragGesture设置为false。

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        // this to prevent the default sliding behaviour
        drawerEnableOpenDragGesture: false,
        drawer: Drawer(),
        appBar: AppBar(
          leading: Builder(builder: (context) => // Ensure Scaffold is in context
            IconButton(
              icon: Icon(Icons.menu),
              onPressed: () => Scaffold.of(context).openDrawer()
            ),
          ),
        )
      )
    );
  }
}

  1. 要以编程方式使用Scaffold.of(context)打开抽屉,您必须确保(感谢Krolaw!)调用所在的上下文知道Scaffold。

    一个干净的方法是将按钮包装在生成器中。我编辑了答案以包括一个最小的完整工作示例。

    Scaffold是一个实现材料设计原则的小部件,因此请注意,为了能够调用此方法,您需要import 'package:flutter/material.dart';且您的小部件需要有一个MaterialApp作为祖先。

Codepen演示


与许多Flutter事物一样,确保Scaffold处于上下文中还有其他解决方案。

在我看来,错误消息是Flutter框架中最好的功能之一,请允许我谦卑地建议始终仔细阅读它们并探索它们指向的文档。

例如,如果在适当的上下文之外调用openDrawer,则会收到以下错误消息的一部分:

使用不包含Scaffold的上下文调用Scaffold.of()。

从传递给Scaffold.of()的上下文开始,找不到Scaffold祖先。通常会发生这种情况,当提供的上下文来自与实际创建正在寻找的Scaffold小部件的build函数相同的StatefulWidget时。

有几种方法可以避免此问题。最简单的方法是使用Builder获取“位于”Scaffold下的上下文。关于此的示例,请参见Scaffold.of()的文档: https://api.flutter.dev/flutter/material/Scaffold/of.html

更有效的解决方案是将生成函数分成几个小部件。这会引入一个新的上下文,您可以从中获取Scaffold。在此解决方案中,您将具有创建由新的内部小部件实例填充的Scaffold的外部小部件,然后在这些内部小部件中使用Scaffold.of()。

一个不太优雅但更快捷的解决方案是将GlobalKey分配给Scaffold,然后使用key.currentState属性而不是使用Scaffold.of()函数来获取ScaffoldState。


代码本身并不能正常工作,它只是一个建议。我已经更新了答案,并提供了一个自包含的完全可用演示,在模拟的像素2上进行了测试,使用的是OSX和Android Studio :) - giulp

18

因为上下文中不包含Scaffold,所以调用Scaffold.of无效。一些上面的解决方案忽略了这一点,另一些使用了GlobalKey。我认为最干净的解决方案是将按钮包装在Builder中:

Scaffold(
   drawerEnableOpenDragGesture: false, // Prevent user sliding open
   appBar: AppBar(
      automaticallyImplyLeading: false,
      title: Text("Some Title"),
      actions: [
         Builder(builder: (context) => // Ensure Scaffold is in context
            IconButton(
               icon: Icon(Icons.settings),
               onPressed: () => Scaffold.of(context).openDrawer()
         )),
      ],
   ),
   // TODO ...
)

在某些情况下(例如我们的情况),如果要处理全局快捷键,则可能需要使用GlobalKey(),但如果您在脚手架内部,则建议使用您提出的方法。 - Venkat D.

14

以下是另一个通过汉堡图标程序化地打开抽屉、不使用应用栏的示例:

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => MyAppState();
}

class MyAppState extends State<MyApp> {
  var scaffoldKey = GlobalKey<ScaffoldState>();

  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        key: scaffoldKey,
        drawer: new Drawer(
          child: new ListView(
            padding: EdgeInsets.zero,
            children: <Widget>[
              DrawerHeader(
                child: Text('Drawer Header'),
                decoration: BoxDecoration(
                  color: Colors.blue,
                ),
              ),
              ListTile(
                title: Text('Item 1'),
                onTap: () {
                  //Do some stuff here
                  //Closing programmatically - very less practical use
                  scaffoldKey.currentState.openEndDrawer();
                },
              )
            ],
          ),
        ),
        body: Stack(
          children: <Widget>[
            new Center(
                child: new Column(
              children: <Widget>[],
            )),
            Positioned(
              left: 10,
              top: 20,
              child: IconButton(
                icon: Icon(Icons.menu),
                onPressed: () => scaffoldKey.currentState.openDrawer(),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

4

只需按照以下步骤:

在类中创建一个变量,如下所示

var scaffoldKey = GlobalKey<ScaffoldState>();

然后在你的脚手架中像这样使用这个键

Scaffold(
    key: scaffoldKey,
    appBar: AppBar(
        automaticallyImplyLeading: false,
        leading: IconButton(
            onPressed: () {
              scaffoldKey.currentState?.openDrawer();
            },
            icon: Icon(
              Icons.menu,
              color: ExtraColors.PRIMARY_800,
            )),
        title: Text(
          '${AppStrings.appName}',
        ),
        centerTitle: true,
      ),
   )

3
appBar: AppBar(

      automaticallyImplyLeading: false,
      title: Text(
        "Infilon Technologies",
        style:
            TextStyle(fontFamily: "Poppins", fontWeight: FontWeight.w600),
      ),
      actions: <Widget>[
        IconButton(
          icon: Icon(Icons.menu),
          onPressed: () {
            if (_scaffoldKey.currentState.isEndDrawerOpen) {
              _scaffoldKey.currentState.openDrawer();
            } else {
              _scaffoldKey.currentState.openEndDrawer();
            }
          },
        ),
      ],
    ),

我知道这是一个相当老的问题,但这确实激发了我一个“最佳实践”的问题 - 大多数人是开发单页面应用程序模型,使用整个应用程序的一个脚手架,还是为每个页面提供自己的完整构建,并在它们之间进行页面->路由? - ChrisH
1
@ChrisH:这个问题值得单独提出来问一下,你觉得呢? - Daniel Leiszen

0

您可以使用这个完美的方法打开抽屉 它适用于Flutter 2.12以上的空值安全模块

class DashBoardScreen extends StatefulWidget {
  final String? screen;
  const DashBoardScreen(this.screen, {super.key});

  @override
  State<DashBoardScreen> createState() => _DashBoardScreenState();
}

class _DashBoardScreenState extends State<DashBoardScreen> {
  DashBoardScreenController controller =
      Get.put(getIt<DashBoardScreenController>());

  @override
  Widget build(BuildContext context) {
    controller.scaffoldKey = GlobalKey<ScaffoldState>();
    return Obx(() => Scaffold(
          key: controller.scaffoldKey,
          onDrawerChanged: (isOpened) {
            if (!isOpened) {
              setState(() {});
            }
          },
          appBar: AppBar(
            title: const Text("Test drawer App"),
            actions: const [
              const Padding(
                  padding: EdgeInsets.only(right: 20),
                  child: Icon(Icons.search))
            ],
            leading: UnconstrainedBox(
              child: GestureDetector(
                onTap: () {
                  controller.scaffoldKey.currentState!.openDrawer();
                },
                child: const AbsorbPointer(
                  absorbing: true,
                  child: SizedBox(
                    height: 50,
                    child: CircleAvatar(
                        backgroundImage: CachedNetworkImageProvider(
                      "https://cdn.pixabay.com/photo/2014/07/09/10/04/man-388104_960_720.jpg",
                    )),
                  ),
                ),
              ),
            ),
          ),
          drawerEdgeDragWidth:
              kIsWeb ? MediaQuery.of(context).size.width * 0.2 : null,
          drawer: Drawer(
              key: controller.scaffoldKey,
              child: ListView(
                children: [
                  DropdownMenuItem(onTap: () {}, child: const Text("Add Anime"))
                ],
              )),
          body: widget.screen == StringVariables.ADD_ANIME
              ? AddAnimeFragment(widget.screen!)
              : Container(),
          bottomNavigationBar: BottomNavigationBar(
            items: const <BottomNavigationBarItem>[
              BottomNavigationBarItem(
                icon: Icon(Icons.home),
                label: 'Add Anime',
              ),
              BottomNavigationBarItem(
                icon: Icon(Icons.business),
                label: 'Favourite',
              ),
              BottomNavigationBarItem(
                icon: Icon(Icons.settings),
                label: 'Settings',
              ),
            ],
            currentIndex: controller.bottomSheetIndex.value,
            selectedItemColor: ColorName.primaryColor,
            onTap: (s) {
              controller.bottomSheetIndex.value = s;
            },
          ),
        ));
  }
}

0
如果您在Scaffold中使用endDrawer(从右到左),则应该使用以下代码:
Scaffold.of(context).openEndDrawer();

如果您在 Scaffold 中使用了 drawer(从左至右),则应使用以下代码:
Scaffold.of(context).openDrawer();

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接