Persist scroll position of TabBarViews in NestedScrollView in flutter during swipes

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

Persist scroll position of TabBarViews in NestedScrollView in flutter during swipes

问题

我有一个选项卡栏中的选项卡列表。

然而,我遇到了一个问题。如果您运行下面的示例代码并滚动到选项卡1中的项目30,然后滑动到选项卡2,再返回到选项卡1,您会立即注意到选项卡1中的项目30不再处于焦点状态。滚动位置已跳转到项目1。这很奇怪。

我已经尝试了一切可能的方法,以确保每个ListView的滚动位置都会在我多次滑动离开并返回时得以保留。

这里是一个完整的可运行示例代码,准确展示了我面临的问题。

  1. import 'package:flutter/material.dart';
  2. class SampleListApp extends StatefulWidget {
  3. const SampleListApp({Key? key}) : super(key: key);
  4. @override
  5. State<SampleListApp> createState() => _SampleListAppState();
  6. }
  7. class _SampleListAppState extends State<SampleListApp>
  8. with TickerProviderStateMixin {
  9. TabController? _tabController;
  10. late ScrollController _scrollViewController;
  11. int _currentTabIndex = 0;
  12. _handleTabChange() {
  13. setState(() {
  14. if (_tabController != null) {
  15. _currentTabIndex = _tabController!.index;
  16. }
  17. });
  18. }
  19. _initTabController() {
  20. _tabController?.dispose();
  21. _tabController =
  22. TabController(initialIndex: _currentTabIndex, vsync: this, length: 2);
  23. _tabController?.addListener(_handleTabChange);
  24. }
  25. @override
  26. void initState() {
  27. super.initState();
  28. _initTabController();
  29. _scrollViewController = ScrollController(initialScrollOffset: 0.0);
  30. }
  31. @override
  32. Widget build(BuildContext context) {
  33. return Scaffold(
  34. body: NestedScrollView(
  35. controller: _scrollViewController,
  36. headerSliverBuilder: (BuildContext context, bool boxIsScrolled) => [
  37. SliverAppBar(
  38. title: const Text("Hello World"),
  39. bottom: TabBar(
  40. indicatorColor: Colors.white,
  41. isScrollable: true,
  42. labelColor: Colors.orange,
  43. unselectedLabelColor: Colors.grey,
  44. tabs: const [
  45. Tab(
  46. text: "Tab 1",
  47. ),
  48. Tab(
  49. text: "Tab 2",
  50. )
  51. ],
  52. controller: _tabController,
  53. ),
  54. pinned: true,
  55. floating: true,
  56. forceElevated: boxIsScrolled,
  57. ),
  58. ],
  59. body: TabBarView(
  60. key: UniqueKey(),
  61. controller: _tabController,
  62. children: const [
  63. ChildView(),
  64. ChildView(),
  65. ],
  66. ),
  67. ),
  68. );
  69. }
  70. }
  71. class ChildView extends StatefulWidget {
  72. const ChildView({Key? key}) : super(key: key);
  73. @override
  74. State<ChildView> createState() => _ChildViewState();
  75. }
  76. class _ChildViewState extends State<ChildView> {
  77. @override
  78. Widget build(BuildContext context) {
  79. final List<String> itemList =
  80. List.generate(100, (index) => "Item ${index + 1}");
  81. return SingleChildScrollView(
  82. child: Column(
  83. children: [
  84. const Padding(
  85. padding: EdgeInsets.only(
  86. top: 16.0,
  87. left: 16,
  88. right: 16,
  89. ),
  90. child: TextField(
  91. textInputAction: TextInputAction.search,
  92. ),
  93. ),
  94. ListView.builder(
  95. shrinkWrap: true,
  96. physics: const NeverScrollableScrollPhysics(),
  97. itemCount: itemList.length,
  98. itemBuilder: (BuildContext context, int index) {
  99. return ListTile(
  100. title: Text(itemList[index]),
  101. );
  102. },
  103. ),
  104. ],
  105. ),
  106. );
  107. }
  108. }

在滑动离开并返回时,如何始终保留我的ListView的滚动位置?

英文:

I have a List of Tabs in a Tabbar.

However, I have an issue. If you run the sample code below and scroll to item 30 on Tab 1 then swipe to Tab 2 and back to Tab 1 you will immediately notice that item 30 in Tab 1 is no longer focused. The scroller has jumped to item 1 instead. This is weird.

I have tried everything possible to ensure the scroll position of each ListView is preserved irrespective of how many times I swipe away and back to it.

Here is a full runnable sample code showing exactly the issue I'm facing.

  1. import &#39;package:flutter/material.dart&#39;;
  2. class SampleListApp extends StatefulWidget {
  3. const SampleListApp({Key? key}) : super(key: key);
  4. @override
  5. State&lt;SampleListApp&gt; createState() =&gt; _SampleListAppState();
  6. }
  7. class _SampleListAppState extends State&lt;SampleListApp&gt;
  8. with TickerProviderStateMixin {
  9. TabController? _tabController;
  10. late ScrollController _scrollViewController;
  11. int _currentTabIndex = 0;
  12. _handleTabChange() {
  13. setState(() {
  14. if (_tabController != null) {
  15. _currentTabIndex = _tabController!.index;
  16. }
  17. });
  18. }
  19. _initTabController() {
  20. _tabController?.dispose();
  21. _tabController =
  22. TabController(initialIndex: _currentTabIndex, vsync: this, length: 2);
  23. _tabController?.addListener(_handleTabChange);
  24. }
  25. @override
  26. void initState() {
  27. super.initState();
  28. _initTabController();
  29. _scrollViewController = ScrollController(initialScrollOffset: 0.0);
  30. }
  31. @override
  32. Widget build(BuildContext context) {
  33. return Scaffold(
  34. body: NestedScrollView(
  35. controller: _scrollViewController,
  36. headerSliverBuilder: (BuildContext context, bool boxIsScrolled) =&gt; [
  37. SliverAppBar(
  38. title: const Text(&quot;Hello World&quot;),
  39. bottom: TabBar(
  40. indicatorColor: Colors.white,
  41. isScrollable: true,
  42. labelColor: Colors.orange,
  43. unselectedLabelColor: Colors.grey,
  44. tabs: const [
  45. Tab(
  46. text: &quot;Tab 1&quot;,
  47. ),
  48. Tab(
  49. text: &quot;Tab 2&quot;,
  50. )
  51. ],
  52. controller: _tabController,
  53. ),
  54. pinned: true,
  55. floating: true,
  56. forceElevated: boxIsScrolled,
  57. ),
  58. ],
  59. body: TabBarView(
  60. key: UniqueKey(),
  61. controller: _tabController,
  62. children: const [
  63. ChildView(),
  64. ChildView(),
  65. ],
  66. ),
  67. ),
  68. );
  69. }
  70. }
  71. class ChildView extends StatefulWidget {
  72. const ChildView({Key? key}) : super(key: key);
  73. @override
  74. State&lt;ChildView&gt; createState() =&gt; _ChildViewState();
  75. }
  76. class _ChildViewState extends State&lt;ChildView&gt; {
  77. @override
  78. Widget build(BuildContext context) {
  79. final List&lt;String&gt; itemList =
  80. List.generate(100, (index) =&gt; &quot;Item ${index + 1}&quot;);
  81. return SingleChildScrollView(
  82. child: Column(
  83. children: [
  84. const Padding(
  85. padding: EdgeInsets.only(
  86. top: 16.0,
  87. left: 16,
  88. right: 16,
  89. ),
  90. child: TextField(
  91. textInputAction: TextInputAction.search,
  92. ),
  93. ),
  94. ListView.builder(
  95. shrinkWrap: true,
  96. physics: const NeverScrollableScrollPhysics(),
  97. itemCount: itemList.length,
  98. itemBuilder: (BuildContext context, int index) {
  99. return ListTile(
  100. title: Text(itemList[index]),
  101. );
  102. },
  103. ),
  104. ],
  105. ),
  106. );
  107. }
  108. }

What can I do to always preserve the scroll positions of my ListViews when swiped away and back

答案1

得分: 1

如果你从TabBarView中移除UniqueKey,并在类似以下方式中添加AutomaticKeepAliveClientMixin混入,TabBarView将不会在你调用setState时重新构建:

  1. class ChildView extends StatefulWidget {
  2. const ChildView({Key? key}) : super(key: key);
  3. @override
  4. State<ChildView> createState() => _ChildViewState();
  5. }
  6. class _ChildViewState extends State<ChildView>
  7. with AutomaticKeepAliveClientMixin {
  8. @override
  9. Widget build(BuildContext context) {
  10. super.build(context);
  11. final List<String> itemList =
  12. List.generate(100, (index) => "Item ${index + 1}");
  13. return SingleChildScrollView(
  14. child: Column(
  15. children: [
  16. const Padding(
  17. padding: EdgeInsets.only(
  18. top: 16.0,
  19. left: 16,
  20. right: 16,
  21. ),
  22. child: TextField(
  23. textInputAction: TextInputAction.search,
  24. ),
  25. ),
  26. ListView.builder(
  27. shrinkWrap: true,
  28. physics: const NeverScrollableScrollPhysics(),
  29. itemCount: itemList.length,
  30. itemBuilder: (BuildContext context, int index) {
  31. return ListTile(
  32. title: Text(itemList[index]),
  33. );
  34. },
  35. ),
  36. ],
  37. ),
  38. );
  39. }
  40. @override
  41. bool get wantKeepAlive => true;
  42. }

请注意,我只翻译了代码部分,没有其他内容。

英文:

If you remove the UniqueKey from TabBarView, the TabBarView won't rebuild when you setState and add AutomaticKeepAliveClientMixin mixin like this:

  1. class ChildView extends StatefulWidget {
  2. const ChildView({Key? key}) : super(key: key);
  3. @override
  4. State&lt;ChildView&gt; createState() =&gt; _ChildViewState();
  5. }
  6. class _ChildViewState extends State&lt;ChildView&gt;
  7. with AutomaticKeepAliveClientMixin {
  8. @override
  9. Widget build(BuildContext context) {
  10. super.build(context);
  11. final List&lt;String&gt; itemList =
  12. List.generate(100, (index) =&gt; &quot;Item ${index + 1}&quot;);
  13. return SingleChildScrollView(
  14. child: Column(
  15. children: [
  16. const Padding(
  17. padding: EdgeInsets.only(
  18. top: 16.0,
  19. left: 16,
  20. right: 16,
  21. ),
  22. child: TextField(
  23. textInputAction: TextInputAction.search,
  24. ),
  25. ),
  26. ListView.builder(
  27. shrinkWrap: true,
  28. physics: const NeverScrollableScrollPhysics(),
  29. itemCount: itemList.length,
  30. itemBuilder: (BuildContext context, int index) {
  31. return ListTile(
  32. title: Text(itemList[index]),
  33. );
  34. },
  35. ),
  36. ],
  37. ),
  38. );
  39. }
  40. @override
  41. bool get wantKeepAlive =&gt; true;
  42. }

huangapple
  • 本文由 发表于 2023年5月30日 06:29:01
  • 转载请务必保留本文链接:https://go.coder-hub.com/76360644.html
匿名

发表评论

匿名网友

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

确定