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
,它根据其子项或 childIndex
为 RepaintBoundary
小部件分配一个键,以便保持状态:
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);
}
}
我有一个预览小部件,可以在用户点击后加载数据。在滚动(预览位于列表中)或浏览其他屏幕时,不应丢失此状态(已点击或未点击)。
滚动是通过添加 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
,它根据其子项或 childIndex
为 RepaintBoundary
小部件分配一个键,以便保持状态:
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);
}
}