英文:
Confusion between two different stateful widgets
问题
我正在开发这个应用程序,其中这段代码代表其中一个屏幕。基本上,它有一个图表并显示与之相关的一些信息。我正在做的是,在选项卡栏中,根据我选择的选项卡,我正在更新父容器的高度。
我使用SetState() 完成了这个操作,其中基于容器高度的新值重新渲染了UI。一切看起来都很好,但在每次调用SetState() 时,图表都会被重新渲染,这会导致它在屏幕上消失约一秒钟。
我想要的是让图表保持在原地,不要发生任何改变。当我切换选项卡时,我希望图表仍然可见。
我考虑将图表元素的状态与其余元素分离,并为该部分创建一个有状态的小部件,但似乎没有起作用。
以下是代码:
class IndChartWidget extends StatefulWidget {
const IndChartWidget({super.key, required this.id});
final String id;
@override
State<IndChartWidget> createState() => _IndChartWidgetState();
}
class _IndChartWidgetState extends State<IndChartWidget> {
@override
Widget build(BuildContext context) {
return SizedBox(
width: double.infinity,
height: 250,
child: ChartWidget(id: widget.id),
);
}
}
class CoinScreen extends StatefulWidget {
final String id;
final String name;
final String sym;
final double price;
final double per;
final String img;
const CoinScreen({super.key, required this.id, required this.name, required this.sym, required this price, required this.per, required this.img});
static Map<String, String> descData = {};
@override
State<CoinScreen> createState() => _CoinScreenState();
}
class _CoinScreenState extends State<CoinScreen> {
String? desc = '';
double containerHeight = 300;
@override
void initState() {
super.initState();
if (CoinScreen.descData.containsKey(widget.id)) {
desc = CoinScreen.descData[widget.id];
} else {
API().getDesc(widget.id).then((value) {
setState(() {
CoinScreen.descData[widget.id] = value;
desc = value;
});
});
}
}
void containerheight(index) {
setState(() {
containerHeight = index == 0 ? 300 : 800;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.name),
centerTitle: true,
leading: IconButton(
icon: const Icon(FontAwesomeIcons.backward),
onPressed: () {
Navigator.pop(context);
},
),
),
body: ListView(
children: [
Padding(
padding: const EdgeInsets.only(left: 10, right: 10, bottom: 40),
child: Column(
children: [
Container(
margin: const EdgeInsets.only(top: 60),
width: double.infinity,
height: 450,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
Colors.black.withOpacity(0.2),
Colors.deepPurple.withOpacity(0.2),
],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
stops: const [0.1, 0.7],
),
border: Border.all(
width: 3,
color: Colors.deepPurple.withOpacity(0.2),
),
borderRadius: BorderRadius.circular(10),
),
child: Column(
children: [
SizedBox(
height: 70,
child: Image.network(widget.img),
),
SizedBox(
child: Text(
'\$${widget.price.toString()}',
style: const TextStyle(
fontSize: 40,
),
),
),
SizedBox(
child: Text(
widget.per.toStringAsFixed(4),
style: Theme.of(context).textTheme.bodySmall,
),
),
IndChartWidget(id: widget.id)
],
),
),
DefaultTabController(
length: 2,
child: Container(
width: double.infinity,
height: containerHeight,
margin: const EdgeInsets.only(
top: 30,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TabBar(
indicatorColor: Colors.transparent,
labelColor: const Color.fromRGBO(127, 157, 255, 1),
unselectedLabelColor: Colors.white.withOpacity(0.8),
splashFactory: NoSplash.splashFactory,
onTap: (value) {
containerheight(value);
},
tabs: const [
Tab(
child: Text(
'About',
style: TextStyle(fontSize: 18),
),
),
Tab(
child: Text(
'News',
style: TextStyle(fontSize: 18),
),
),
],
),
const Divider(
color: Color.fromRGBO(127, 157, 255, 0.3),
thickness: 2,
),
Expanded(
child: TabBarView(
children: [
Container(
margin: const EdgeInsets.only(top: 20),
child: Text(
desc!,
maxLines: 11,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.bodySmall,
),
),
SizedBox(
child: ListView.builder(
itemBuilder: (context, index) {
return Container(
margin: const EdgeInsets only(bottom: 50),
child: Column(
children: [
Container(
width: double.infinity,
height: 200,
color: Colors.grey,
),
Container(
height: 50,
width: double.infinity,
margin: const EdgeInsets.only(top: 10),
child: Row(
children: [
Flexible(
flex: 1,
child: Container(
color: Colors.grey,
),
),
Flexible(
flex: 4,
child: Container(
color: Colors.grey,
margin: const EdgeInsets.only(left: 5),
),
),
],
),
),
],
),
);
},
itemCount: 4,
),
),
],
),
),
],
),
),
)
],
),
),
],
),
);
}
}
这方面有什么想法吗?任何帮助将不胜感激。谢谢!
英文:
So I am developing this app, where this code represents one screen of it. Basically it has a graph and displays some information related to it. What I am doing is that in the tab bar, based on the tab I have selected, I am updating the height of the parent container.
I have done this using SetState() where the UI is rendered again based on the new value for the container height. Everything seems fine, but on every SetState() call, the chart gets rendered again, which makes it disappear from the screen for about a second.
What I want to do is let the chart be where it is and not let anything happen to it at all. When I am switching tabs I want the chart to be visible.
I thought of separating the chart elements state from the rest of the elements and created a stateful widget for that part but it doesn't seem to be working.
Here is the code :
class IndChartWidget extends StatefulWidget {
const IndChartWidget({super.key, required this.id});
final String id;
@override
State<IndChartWidget> createState() => _IndChartWidgetState();
}
class _IndChartWidgetState extends State<IndChartWidget> {
@override
Widget build(BuildContext context) {
return SizedBox(
width: double.infinity,
height: 250,
child: ChartWidget(id: widget.id),
);
}
}
class CoinScreen extends StatefulWidget {
final String id;
final String name;
final String sym;
final double price;
final double per;
final String img;
const CoinScreen({super.key, required this.id, required this.name, required this.sym, required this.price, required this.per, required this.img});
static Map<String, String> descData = {};
@override
State<CoinScreen> createState() => _CoinScreenState();
}
class _CoinScreenState extends State<CoinScreen> {
String? desc = '';
double containerHeight = 300;
@override
void initState() {
super.initState();
if (CoinScreen.descData.containsKey(widget.id)) {
desc = CoinScreen.descData[widget.id];
} else {
API().getDesc(widget.id).then((value) {
setState(() {
CoinScreen.descData[widget.id] = value;
desc = value;
});
});
}
}
void containerheight(index) {
setState(() {
containerHeight = index == 0 ? 300 : 800;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.name),
centerTitle: true,
leading: IconButton(
icon: const Icon(FontAwesomeIcons.backward),
onPressed: () {
Navigator.pop(context);
},
),
),
body: ListView(
children: [
Padding(
padding: const EdgeInsets.only(left: 10, right: 10, bottom: 40),
child: Column(
children: [
Container(
margin: const EdgeInsets.only(top: 60),
width: double.infinity,
height: 450,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
Colors.black.withOpacity(0.2),
Colors.deepPurple.withOpacity(0.2),
],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
stops: const [0.1, 0.7],
),
border: Border.all(
width: 3,
color: Colors.deepPurple.withOpacity(0.2),
),
borderRadius: BorderRadius.circular(10),
),
child: Column(
children: [
SizedBox(
height: 70,
child: Image.network(widget.img),
),
SizedBox(
child: Text(
'\$${widget.price.toString()}',
style: const TextStyle(
fontSize: 40,
),
),
),
SizedBox(
child: Text(
widget.per.toStringAsFixed(4),
style: Theme.of(context).textTheme.bodySmall,
),
),
IndChartWidget(id: widget.id)
],
),
),
DefaultTabController(
length: 2,
child: Container(
width: double.infinity,
height: containerHeight,
margin: const EdgeInsets.only(
top: 30,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TabBar(
indicatorColor: Colors.transparent,
labelColor: const Color.fromRGBO(127, 157, 255, 1),
unselectedLabelColor: Colors.white.withOpacity(0.8),
splashFactory: NoSplash.splashFactory,
onTap: (value) {
containerheight(value);
},
tabs: const [
Tab(
child: Text(
'About',
style: TextStyle(fontSize: 18),
),
),
Tab(
child: Text(
'News',
style: TextStyle(fontSize: 18),
),
),
],
),
const Divider(
color: Color.fromRGBO(127, 157, 255, 0.3),
thickness: 2,
),
Expanded(
child: TabBarView(
children: [
Container(
margin: const EdgeInsets.only(top: 20),
child: Text(
desc!,
maxLines: 11,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.bodySmall,
),
),
SizedBox(
child: ListView.builder(
itemBuilder: (context, index) {
return Container(
margin: const EdgeInsets.only(bottom: 50),
child: Column(
children: [
Container(
width: double.infinity,
height: 200,
color: Colors.grey,
),
Container(
height: 50,
width: double.infinity,
margin: const EdgeInsets.only(top: 10),
child: Row(
children: [
Flexible(
flex: 1,
child: Container(
color: Colors.grey,
),
),
Flexible(
flex: 4,
child: Container(
color: Colors.grey,
margin: const EdgeInsets.only(left: 5),
),
),
],
),
),
],
),
);
},
itemCount: 4,
),
),
],
),
),
],
),
),
)
],
),
),
],
),
);
}
}
Any ideas on this? All help would be much appreciated.
Thank You.
答案1
得分: 1
你考虑过将DefaultTabController包装在StatefulBuilder内部吗?您可以在只想更新部分小部件而不是整个小部件时使用StatefulBuilder。
StatefulBuilder(
builder: (context, innerSetState) {
return DefaultTabController(
length: 2,
child: Container(
...
child: Column(
...
children: [
TabBar(
...
onTap: (value) {
innerSetState(() => containerHeight = index == 0 ? 300 : 800);
},
...
),
...
],
),
),
);
}
)
查看StatefulBuilder
和https://stackoverflow.com/questions/74976937/flutter-setstate-redraws-the-entire-screen-instead-of-just-the-widget
英文:
Have you considered wrapping your DefaultTabController inside a StatefulBuilder? You can use a StatefulBuilder when you only want to update part of a widget and not the entire widget.
StatefulBuilder(
builder: (context, innerSetState) {
return DefaultTabController(
length: 2,
child: Container(
...
child: Column(
...
children: [
TabBar(
...
onTap: (value) {
innerSetState(() => containerHeight = index == 0 ? 300 : 800);
},
...
),
...
],
),
),
)
}
)
see StatefulBuilder
and https://stackoverflow.com/questions/74976937/flutter-setstate-redraws-the-entire-screen-instead-of-just-the-widget
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论