Flutter - RepaintBoundary导致StatefulWidget的状态重置

Flutter - RepaintBoundary causes state reset of StatefulWidget

我有一个预览小部件,可以在用户点击后加载数据。在滚动(预览位于列表中)或浏览其他屏幕时,不应丢失此状态(已点击或未点击)。 滚动是通过添加 AutomaticKeepAliveClientMixin 来解决的,它在滚动时保存状态。

现在我还需要用 RepaintBoundary 包装预览小部件(实际上是一个包含预览的更复杂的小部件),以便能够单独制作此小部件的 "screenshot"。

在我用 RepaintBoundary 包装小部件之前,在滚动和导航到另一个屏幕时都会保存状态。 添加 RepaintBoundary 后,滚动仍然有效,但对于导航,状态已重置。

我如何包装应使用 RepaintBoundary 保持其状态的有状态小部件?

代码是我实现相同问题的简化示例。

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {

  MyApp({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final title = 'Test';
    return MaterialApp(
      title: title,
      home: Scaffold(
        appBar: AppBar(
          title: Text(title),
        ),
        body: TestList(40),
      ),
    );
  }
}


class TestList extends StatefulWidget {

  final int numberOfItems;

  TestList(this.numberOfItems);

  @override
  _TestListState createState() => _TestListState();

}

class _TestListState extends State<TestList> {

  @override
  Widget build(BuildContext context) {
    print('_TestListState build.');
    return ListView.builder(
      itemCount: widget.numberOfItems,
      itemBuilder: (context, index) {

        return RepaintBoundary(
          key: GlobalKey(),
          child: Preview()
        );
      },
    );
  }
}


class Preview extends StatefulWidget {
  @override
  _PreviewState createState() => _PreviewState();
}

class _PreviewState extends State<Preview> with AutomaticKeepAliveClientMixin {

  bool loaded;

  @override
  void initState() {
    super.initState();
    print('_PreviewState initState.');

    loaded = false;
  }

  @override
  bool get wantKeepAlive => true;

  @override
  Widget build(BuildContext context) {
    super.build(context);

    print('_PreviewState build.');

    if(loaded) {
      return GestureDetector(
        onTap: () {
          Navigator.push(
            context,
            MaterialPageRoute(builder: (context) => NewScreen()),
          );
        },
        child: ListTile(
          title: Text('Loaded. Tap to navigate.'),
          leading: Icon(Icons.visibility),
        ),
      );
    } else {
      return GestureDetector(
        onTap: () {
          setState(() {
            loaded = true;
          });
        },
        child: ListTile(
          title: Text('Tap to load.'),
        ),
      );
    }
  }
}


class NewScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('New Screen')),
      body: Center(
        child: Text(
          'Navigate back and see if loaded state is gone.',
          style: TextStyle(fontSize: 14.0),
        ),
      ),
    );
  }
}

看看 RepaintBoundary.wrap,它根据其子项或 childIndexRepaintBoundary 小部件分配一个键,以便保持状态:

class _TestListState extends State<TestList> {
  @override
  Widget build(BuildContext context) {
    print('_TestListState build.');
    return ListView.builder(
      itemCount: widget.numberOfItems,
      itemBuilder: (context, index) {
        return RepaintBoundary.wrap(
          Preview(),
          index,
        );
      },
    );
  }
}

https://api.flutter.dev/flutter/widgets/RepaintBoundary/RepaintBoundary.wrap.html

编辑: 根据以下评论,看起来此解决方案会破坏屏幕截图功能,因此您必须像这样在您的状态下存储子部件列表:

class _TestListState extends State<TestList> {
  List<Widget> _children;

  @override
  void initState() {
    super.initState();
    _children = List.generate(
        widget.numberOfItems,
        (_) => RepaintBoundary(
              key: GlobalKey(),
              child: Preview(),
            ));
  }

  @override
  Widget build(BuildContext context) {
    print('_TestListState build.');
    return ListView(children: _children);
  }
}