首页 > 解决方案 > Flutter:我有一个持久的自定义覆盖,里面有一个导航器。如何根据导航页面显示/隐藏覆盖?

问题描述

我想实现一个使用auto_route包在其中的页面之间导航的覆盖。我希望覆盖在页面之间保持不变,所以如果搜索栏或抽屉打开,一旦你推送或弹出页面,它的外观就会保持不变。

问题来自下一个要求:我希望根据要导航到的页面隐藏或显示它。也就是说,如果我在显示覆盖的“Page1”中并且执行“Navigator.pushNamed(/Page2)”并且Page2 隐藏了覆盖,则覆盖隐藏。如果我执行“Navigator.pop()”,它会返回到“Page1”,再次显示叠加层。

因为页面是由“ExtendedNavigator”类给出的,并且 Overlay 本身是首先构建的,所以我无法在构建时知道 Navigator 导航到的位置或当前正在构建的页面。我需要事先知道这些信息才能相应地隐藏或显示叠加层。

我尝试了几种解决方案均无济于事。

第一种方法

在需要渲染的每个页面中实例化 Overlay,并使用 Inherited Widget 来维护状态。

问题:无法保持所有覆盖的状态同步。键不能重复使用,也不能复制它们的状态(例如,对于 ScaffoldState)。因此,每当其中一个叠加层发生更改时,我都应该将相同的更改复制到所有其他叠加层,以相应地更改它们的状态。此外,不能实例化具有特定状态的 Scaffold。

此解决方案的代码大致如下所示:

class Page1 extends StatefulWidget {
  Page1 ({
    Key key,
  }) : super(key: key);

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

class _Page1State extends State<Page1> {
  @override
  Widget build(BuildContext context) {
    return Overlay(
      child: (...)
    );
  }
}
class Page2 extends StatefulWidget {
  Page2 ({
    Key key,
  }) : super(key: key);

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

class _Page2State extends State<Page2> {
  @override
  Widget build(BuildContext context) {
    return Overlay(
      child: (...)
    );
  }
}

class OverlayData extends InheritedWidget {
  bool _hidden = true;

  OverlayData({
    Key key,
    @required Widget child,
  }) : super(key: key, child: child);

  static OverlayData of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<OverlayData>();
  }

  void setHiddenTo(bool value) {
    _hidden = value;
    print("Overlay is now in state: " + value.toString());
  }

  bool isHidden() {
    return _hidden;
  }

  @override
  bool updateShouldNotify(OverlayData oldWidget) =>
      _hidden != oldWidget._hidden;
}


//// Main
    void main() {
      runApp(App());
    }

    class App extends StatefulWidget {
       @override
       State<StatefulWidget> createState() => _AppState();
    }

    class _AppState extends State<App> {
      void initState() {
        super.initState();
      }

      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          builder: ExtendedNavigator.builder<router.Router>(
            router: router.Router(),
            builder: (context, extendedNav) => Theme(
              data: ThemeData(
                brightness: Brightness.light,
              ),
              child: OverlayData(child: Overlay(child: extendedNav)),
            ),
          ),
          color: StyleValues.blue,
        );
      }
    }

第二种方法

每次手动推送/弹出到另一个页面时,使用其 GlobalKey 手动触发覆盖的隐藏/显示。推送页面时,我可以使用 [Guards] 检查我正在导航到的页面是否显示或隐藏了叠加层,并使用叠加层的键触发更改。

问题:当我使用 Android 按钮弹出时,我不知道要弹出到哪个页面并相应地触发更改。即使我用“WillPopScope”包裹我的叠加层,我仍然不知道我要弹出哪个页面,所以它没有用。

这个代码看起来大概是这样的:

/// Main
    void main() {
      runApp(App());
    }

    class App extends StatefulWidget {
       @override
       State<StatefulWidget> createState() => _AppState();
    }

    class _AppState extends State<App> {
      void initState() {
        super.initState();
      }

      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          builder: ExtendedNavigator.builder<router.Router>(
            router: router.Router(),
            guards: [HideGuard(), ShowGuard()]
            builder: (context, extendedNav) => Theme(
              data: ThemeData(
                brightness: Brightness.light,
              ),
              child: Overlay(child: extendedNav),
            ),
          ),
          color: StyleValues.blue,
        );
      }
    }

/// Overlay
class Overlay extends StatefulWidget {
  final Widget body;

  Overlay({this.body});

  @override
  OverlayState createState() => OverlayState(this.body);
}

class OverlayState extends State<Overlay> {
  final Widget body;
  bool _hidden;

  OverlayState(this.body);

  void setHiddenTo(bool value){
    if (value != _hidden)
    {
       SetState(() {
           _hidden = value;
       })
    }
  }


  @override
  Widget build(BuildContext context) {
    /// Here I check if it is hidden or not
    if (_hidden) {
        return body;
    }

    return Scaffold(
      appBar: PreferredSize(
        child: AppBar(...)
      ),

      /// Bottom Bar
      bottomNavigationBar: (...),

      /// Content (Second Scaffold to render Drawer behind AppBar and BottomBar)
      body: Scaffold(
        key: _scaffoldKey,
        drawer: Drawer(
          child: (...)
        ),
        body: body,
      ),
    );
  }
}


/// The router
@MaterialAutoRouter(routes: <AutoRoute>[
  MaterialRoute(path: PageRoutes.page1, page: Page1, initial: true, guards: [HideGuard]),
  MaterialRoute(path: PageRoutes.page2, page: Page2, guards: [ShowGuard]),
  MaterialRoute(path: PageRoutes.page3, page: Page3, guards: [ShowGuard]),
])
class $Router {}

/// The Key (in a separate class)
class Keys{
    GlobalKey<OverlayState> overlayKey = GlobalKey<OverlayState>();
}

/// A guard
class ShowGuard extends RouteGuard {
 @override
 Future<bool> canNavigate(
     ExtendedNavigatorState navigator, String routeName, Object arguments) async {
    Keys.OverlayKey.currentState.setHiddenTo(false);
 }
}

class HideGuard extends RouteGuard {
 @override
 Future<bool> canNavigate(
     ExtendedNavigatorState navigator, String routeName, Object arguments) async {
    Keys.OverlayKey.currentState.setHiddenTo(true);
 }
}

我应该如何处理这个问题?在构建覆盖之前,有什么方法可以检索将要推送/弹出的当前页面?有没有办法让几个小部件共享相同的状态?预先感谢您的帮助。

标签: flutterimplementation

解决方案


推荐阅读