Flutter:在抽屉中更改主体时保持状态

Flutter: preserve state when changing body in Drawer

我正在使用 Material 库的 Drawer class 创建带有导航抽屉的 Flutter 应用程序。包含 DrawerWidget 是一个 StatefulWidgetScaffold 的内容根据导航抽屉上的所选项目显示。内容是 WidgetOneWidgetTwo,两者都将自己的状态保持为 StatefulWidget。请参阅下面的代码示例。

目前,当我从一个小部件切换到另一个小部件并返回时,会重新加载先前显示的小部件的整个状态。这并不理想,因为两个小部件都有来自 API 的网络调用,需要相应地重新绘制。

到目前为止我尝试了什么

代码

class DrawerWidget extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => _DrawerState();
}

class _DrawerState extends State<DrawerWidget> {
  Widget _activeWidget;

  @override
  void initState() {
    _activeWidget = FirstWidget();
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(title: Text("Drawer demo")),
        drawer: Drawer(
          child: ListView(
            padding: EdgeInsets.zero,
            children: <Widget>[
              ListTile(
                title: Text("First Widget"),
                onTap: () {
                  setState(() {
                    _activeWidget = FirstWidget();
                  });
                },
              ),
              ListTile(
                title: Text("Second Widget"),
                onTap: () {
                  setState(() {
                    _activeWidget = SecondWidget();
                  });
                },
              ),
            ],
          ),
        ),
        body: _activeWidget);
  }
}

class FirstWidget extends StatefulWidget {
  // [..]
}

class SecondWidget extends StatefulWidget {
  // [..]
}

想要的结果

WidgetOneWidgetTwo 仅在初始加载时加载(在 Drawer 中选择它们之后)。如果之前已经加载过,则切换到另一个小部件并返回不应重新加载该小部件。子控件不应全部直接加载,只有在最初按下时才会加载。

实际结果

每次在Drawer中选择FirstWidgetSecondWidget时都会重新加载和重绘。

我通过使用 PageView 并在所有子小部件上实施 AutomaticKeepAliveClientMixin 解决了这个问题:

class DrawerWidget extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => _DrawerState();
}

class _DrawerState extends State<DrawerWidget> {
  final _pageController = PageController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(title: Text("Drawer demo")),
        drawer: Drawer(
          child: ListView(
            padding: EdgeInsets.zero,
            children: <Widget>[
              ListTile(
                title: Text("First Widget"),
                onTap: () {
                  _pageController.jumpToPage(0);
                },
              ),
              ListTile(
                title: Text("Second Widget"),
                onTap: () {
                  _pageController.jumpToPage(1);
                },
              ),
            ],
          ),
        ),
        body: PageView(
          controller: _pageController,
          children: <Widget>[
            FirstWidget(),
            SecondWidget()
          ],
          physics: NeverScrollableScrollPhysics()
        ));
  }
}

class FirstWidget extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => _FirstWidgetState();
}

class _FirstWidgetState extends State<FirstWidget> with AutomaticKeepAliveClientMixin<FirstWidget> {
  // [..]

  @override
  bool get wantKeepAlive => true;
} 

class SecondWidget extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => _SecondWidgetState();
}

class _SecondWidgetState extends State<SecondWidget> with AutomaticKeepAliveClientMixin<SecondWidget> {
  // [..]

  @override
  bool get wantKeepAlive => true;
}

现在,所有小部件仅在导航抽屉中的初始切换时加载,切换回来时不会重新加载。