【问题标题】:Flutter - RepaintBoundary causes state reset of StatefulWidgetFlutter - RepaintBoundary 导致 StatefulWidget 的状态重置
【发布时间】:2019-10-09 23:07:20
【问题描述】:

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

现在我还需要用 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),
        ),
      ),
    );
  }
}

【问题讨论】:

    标签: dart flutter statefulwidget


    【解决方案1】:

    看看RepaintBoundary.wrap,它为RepaintBoundary 小部件分配了一个基于其子级或childIndex 的键,因此保持状态:

    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

    编辑:根据下面的 cmets,看起来此解决方案会破坏屏幕截图功能,因此您必须将子小部件列表存储在您的状态中,如下所示:

    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);
      }
    }
    

    【讨论】:

    • 这似乎完全解决了所描述的状态问题,但是,现在我不能使用创建的 RepaintBoundary 来截取屏幕截图,因为在 RepaintBoundary.wrap 中只创建了一个 ValueKey 并截取小部件的屏幕截图似乎仅适用于 GlobalKey。有什么想法吗?
    • 啊哈!好点子。您可能需要尝试不同的解决方案。我已经更新了我的答案。
    • 这适用于本示例,但不适用于我的实际实现,因为列表可能很长,而且价格昂贵,而且我不想将所有元素都保存在内存中,关于您的第一个解决方案,您知道为什么/从孩子派生的密钥究竟如何帮助维持状态?因为其余的是正常的 RepaintBoundary,也许我可以使用 GlobalKeys 做类似的事情
    • 另外,示例代码中的每个 Navigator.push/pop 似乎都会调用 _TestListState 的 initState,我也不明白为什么
    猜你喜欢
    • 2020-10-01
    • 2020-12-18
    • 1970-01-01
    • 2018-07-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-08
    • 2018-12-21
    相关资源
    最近更新 更多