混淆两个不同的有状态小部件。

huangapple go评论72阅读模式
英文:

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&lt;IndChartWidget&gt; createState() =&gt; _IndChartWidgetState();
}
class _IndChartWidgetState extends State&lt;IndChartWidget&gt; {
@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&lt;String, String&gt; descData = {};
@override
State&lt;CoinScreen&gt; createState() =&gt; _CoinScreenState();
}
class _CoinScreenState extends State&lt;CoinScreen&gt; {
String? desc = &#39;&#39;;
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(
&#39;\$${widget.price.toString()}&#39;,
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(
&#39;About&#39;,
style: TextStyle(fontSize: 18),
),
),
Tab(
child: Text(
&#39;News&#39;,
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(() =&gt; containerHeight = index == 0 ? 300 : 800);
},
...
),
...
],
),
),
)
}
)

see StatefulBuilder
and https://stackoverflow.com/questions/74976937/flutter-setstate-redraws-the-entire-screen-instead-of-just-the-widget

huangapple
  • 本文由 发表于 2023年2月27日 13:59:17
  • 转载请务必保留本文链接:https://go.coder-hub.com/75577179.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定