Flutter :抽屉打开时是否可以检测到?

zd287kbt  于 2022-12-05  发布在  Flutter
关注(0)|答案(9)|浏览(257)

有没有可能检测抽屉何时打开,以便我们可以运行一些例程来更新其内容?
我的一个典型用例是显示关注者、喜欢者的数量......为此,我需要轮询服务器以获得此信息,然后显示它。
我尝试实现一个NavigatorObserver来捕捉抽屉可见/隐藏的时刻,但是NavigatorObserver没有检测到抽屉的任何信息。
下面是链接到NavigatorObserver的代码:

import 'package:flutter/material.dart';

typedef void OnObservation(Route<dynamic> route, Route<dynamic> previousRoute);
typedef void OnStartGesture();

class NavigationObserver extends NavigatorObserver {
  OnObservation onPushed;
  OnObservation onPopped;
  OnObservation onRemoved;
  OnObservation onReplaced;
  OnStartGesture onStartGesture;

  @override
  void didPush(Route<dynamic> route, Route<dynamic> previousRoute) {
    if (onPushed != null) {
      onPushed(route, previousRoute);
    }
  }

  @override
  void didPop(Route<dynamic> route, Route<dynamic> previousRoute) {
    if (onPopped != null) {
      onPopped(route, previousRoute);
    }
  }

  @override
  void didRemove(Route<dynamic> route, Route<dynamic> previousRoute) {
    if (onRemoved != null)
      onRemoved(route, previousRoute);
  }

  @override
  void didReplace({ Route<dynamic> oldRoute, Route<dynamic> newRoute }) {
    if (onReplaced != null)
      onReplaced(newRoute, oldRoute);
  }

  @override
  void didStartUserGesture() { 
    if (onStartGesture != null){
      onStartGesture();
    }
  }
}

这个观察器的初始化

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

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => new _MyAppState();
}

class _MyAppState extends State<MyApp> {
  final NavigationObserver _observer = new NavigationObserver()
                                              ..onPushed = (Route<dynamic> route, Route<dynamic> previousRoute) {
                                                print('** pushed route: $route');
                                              }
                                              ..onPopped = (Route<dynamic> route, Route<dynamic> previousRoute) {
                                                print('** poped route: $route');
                                              }
                                              ..onReplaced = (Route<dynamic> route, Route<dynamic> previousRoute) {
                                                print('** replaced route: $route');
                                              }
                                              ..onStartGesture = () {
                                                print('** on start gesture');
                                              };

  @override
  void initState(){
    super.initState();
  }

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Title',
      theme: new ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: new SplashScreen(),
        routes: <String, WidgetBuilder> {
          '/splashscreen': (BuildContext context) => new SplashScreen(),
        },
        navigatorObservers: <NavigationObserver>[_observer],
    );
  }
}

谢谢你的帮助。

icomxhvb

icomxhvb1#

此答案已过时。请查看@dees91的答案。

抽屉打开/关闭时检测和运行功能

  • 通过任何操作打开抽屉时运行initState()
  • 当通过任何操作关闭抽屉时,运行dispose()
class MyDrawer extends StatefulWidget {
    @override
    _MyDrawerState createState() => _MyDrawerState();
}

class _MyDrawerState extends State<MyDrawer> {

    @override
    void initState() {
        super.initState();
        print("open");
    }

    @override
    void dispose() {
        print("close");
        super.dispose();
    }

    @override
    Widget build(BuildContext context) {
        return Drawer(
            child: Column(
                children: <Widget>[
                    Text("test1"),
                    Text("test2"),
                    Text("test3"),
                ],
            ),
        );
    }
}

状态管理注意事项

如果使用这些函数改变状态以重建抽屉项,可能会遇到以下错误:Unhandled Exception: setState() or markNeedsBuild() called during build .
这可以通过在initState()source中使用以下两个函数来处理

选项1

WidgetsBinding.instance.addPostFrameCallback((_){
  // Add Your Code here.
});

选项2

SchedulerBinding.instance.addPostFrameCallback((_) {
  // add your code here.
});

选项1的完整示例

@override
void initState() {
    super.initState();
    WidgetsBinding.instance.addPostFrameCallback((_) {
        // Your Code Here
    });
}
zzzyeukh

zzzyeukh2#

由于https://github.com/flutter/flutter/pull/67249已与Flutter 2.0合并并发布,因此,以下是检测抽屉打开/关闭的正确方法:

Scaffold(
      onDrawerChanged: (isOpened) {
        //todo what you need for left drawer
      },
      onEndDrawerChanged: (isOpened) {
        //todo what you need for right drawer
      },
)
nzkunb0c

nzkunb0c3#

最佳解决方案

ScaffoldState有一个有用的方法isDrawerOpen,它提供打开/关闭的状态。

**示例:**在背面按下时,它首先检查抽屉是否打开,如果是,则在退出前首先关闭抽屉。

/// create a key for the scaffold in order to access it later.
GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();

@override
Widget build(context) {
   return WillPopScope(
  child: Scaffold(
    // assign key (important)
    key: _scaffoldKey,
    drawer: SideNavigation(),
  onWillPop: () async {
    // drawer is open then first close it
    if (_scaffoldKey.currentState.isDrawerOpen) {
      Navigator.of(context).pop();
      return false;
    }
    // we can now close the app.
    return true;
  });}
rur96b6h

rur96b6h4#

我认为一个简单的解决方案是覆盖AppBarleading属性,这样当按下菜单图标时,您就可以访问并基于此运行API调用。
然而,我可能误解了你的问题,因为对于你提供的用例,你通常需要以一种可以 * 监听 * 任何更改的方式来管理它,这些更改将自动更新值,所以我不确定当抽屉打开时你试图触发什么。
这里有一个例子。

class DrawerExample extends StatefulWidget {
  @override
  _DrawerExampleState createState() => new _DrawerExampleState();
}

class _DrawerExampleState extends State<DrawerExample> {
  GlobalKey<ScaffoldState> _key = new GlobalKey<ScaffoldState>();
  int _counter =0;
  _handleDrawer(){
      _key.currentState.openDrawer();

           setState(() {
          ///DO MY API CALLS
          _counter++;
        });

  }
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      key: _key,
      appBar: new AppBar(
        title: new Text("Drawer Example"),
        centerTitle: true,
        leading: new IconButton(icon: new Icon(
          Icons.menu
        ),onPressed:_handleDrawer,),
      ),
      drawer: new Drawer(
        child: new Center(
          child: new Text(_counter.toString(),style: Theme.of(context).textTheme.display1,),
        ),
      ),
    );
  }
}
stszievb

stszievb5#

您可以简单地使用onDrawerChanged来检测抽屉是在Scaffold小部件中打开还是关闭。

属性:

{void函数(布尔)-绘图更改}
类型:void函数(bool)?
当Scaffold.drawer打开或关闭时调用的可选回调。

范例:

@覆盖小部件构建(BuildContext上下文){

return Scaffold(
  onDrawerChanged:(val){
    if(val){
      setState(() {
        //foo bar;
      });
    }else{
      setState(() {
        //foo bar;
      });
    }
},     
  drawer: Drawer(        
      child: Container(
      )
  ));

}

5q4ezhmt

5q4ezhmt6#

不幸的是,目前没有readymade solution
您可以使用肮脏的黑客:观察抽屉的可见位置。
例如,我使用这种方法来同步按钮上图标的动画和抽屉框的位置。

解决此问题的代码如下所示:

import 'package:flutter/material.dart';
    import 'package:flutter/scheduler.dart';

    class DrawerListener extends StatefulWidget {
      final Widget child;
      final ValueChanged<FractionalOffset> onPositionChange;

      DrawerListener({
        @required this.child,
        this.onPositionChange,
      });

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

    class _DrawerListenerState extends State<DrawerListener> {
      GlobalKey _drawerKey = GlobalKey();
      int taskID;
      Offset currentOffset;

      @override
      void initState() {
        super.initState();
        _postTask();
      }

      _postTask() {
        taskID = SchedulerBinding.instance.scheduleFrameCallback((_) {
          if (widget.onPositionChange != null) {
            final RenderBox box = _drawerKey.currentContext?.findRenderObject();
            if (box != null) {
              Offset newOffset = box.globalToLocal(Offset.zero);
              if (newOffset != currentOffset) {
                currentOffset = newOffset;
                widget.onPositionChange(
                  FractionalOffset.fromOffsetAndRect(
                    currentOffset,
                    Rect.fromLTRB(0, 0, box.size.width, box.size.height),
                  ),
                );
              }
            }
          }

          _postTask();
        });
      }

      @override
      void dispose() {
        SchedulerBinding.instance.cancelFrameCallbackWithId(taskID);
        if (widget.onPositionChange != null) {
          widget.onPositionChange(FractionalOffset(1.0, 0));
        }
        super.dispose();
      }

      @override
      Widget build(BuildContext context) {
        return Container(
          key: _drawerKey,
          child: widget.child,
        );
      }
    }

如果您只对打开或关闭盒子的最终事件感兴趣,那么调用initStatedispose函数中的回调就足够了。

gkl3eglg

gkl3eglg7#

ScaffoldState中有isDrawerOpen属性,因此您可以在需要检查时进行检查。
创建全局密钥;

GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();

将其指定给脚手架

Scaffold(
      key: scaffoldKey,
      appBar: ..)

检查应用程序中的任何位置

bool opened =scaffoldKey.currentState.isDrawerOpen;
vpfxa7rd

vpfxa7rd8#

当这个问题被贴出来的时候,要完成这个任务还需要一些技巧。但是在Flutter 2.0中,这就很容易了。在你的Scaffold中,你可以检测到右边的抽屉和左边的抽屉,如下所示。

@override
  Widget build(BuildContext context) {
    return Scaffold(
      onDrawerChanged: (isOpened) {
        *//Left drawer, Your code here,*
      },
      onEndDrawerChanged: (isOpened) {
        *//Right drawer, Your code here,*
      },
    );
  }
e0bqpujr

e0bqpujr9#

您可以使用如下Scaffold.of(context)来检测抽屉状态:

注意:您必须将代码放在生成器小部件中才能使用包含scaffold的上下文。

Builder(
                builder: (context) => IconButton(
                  icon: Icon(
                    Icons.menu,
                    color: getColor(context, opacity.value),
                  ),
                  onPressed: () {
                    if (Scaffold.of(context).isDrawerOpen) {
                      Scaffold.of(context).closeDrawer();
                    } else {
                      Scaffold.of(context).openDrawer();
                    }
                  },
                ),
              ),

相关问题