Persist scroll position of TabBarViews in NestedScrollView in flutter during swipes

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

Persist scroll position of TabBarViews in NestedScrollView in flutter during swipes

问题

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

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

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

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

import 'package:flutter/material.dart';

class SampleListApp extends StatefulWidget {
  const SampleListApp({Key? key}) : super(key: key);

  @override
  State<SampleListApp> createState() => _SampleListAppState();
}

class _SampleListAppState extends State<SampleListApp>
    with TickerProviderStateMixin {
  TabController? _tabController;
  late ScrollController _scrollViewController;
  int _currentTabIndex = 0;

  _handleTabChange() {
    setState(() {
      if (_tabController != null) {
        _currentTabIndex = _tabController!.index;
      }
    });
  }

  _initTabController() {
    _tabController?.dispose();
    _tabController =
        TabController(initialIndex: _currentTabIndex, vsync: this, length: 2);
    _tabController?.addListener(_handleTabChange);
  }

  @override
  void initState() {
    super.initState();
    _initTabController();
    _scrollViewController = ScrollController(initialScrollOffset: 0.0);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: NestedScrollView(
        controller: _scrollViewController,
        headerSliverBuilder: (BuildContext context, bool boxIsScrolled) => [
          SliverAppBar(
            title: const Text("Hello World"),
            bottom: TabBar(
              indicatorColor: Colors.white,
              isScrollable: true,
              labelColor: Colors.orange,
              unselectedLabelColor: Colors.grey,
              tabs: const [
                Tab(
                  text: "Tab 1",
                ),
                Tab(
                  text: "Tab 2",
                )
              ],
              controller: _tabController,
            ),
            pinned: true,
            floating: true,
            forceElevated: boxIsScrolled,
          ),
        ],
        body: TabBarView(
          key: UniqueKey(),
          controller: _tabController,
          children: const [
            ChildView(),
            ChildView(),
          ],
        ),
      ),
    );
  }
}

class ChildView extends StatefulWidget {
  const ChildView({Key? key}) : super(key: key);

  @override
  State<ChildView> createState() => _ChildViewState();
}

class _ChildViewState extends State<ChildView> {
  @override
  Widget build(BuildContext context) {
    final List<String> itemList =
        List.generate(100, (index) => "Item ${index + 1}");
    return SingleChildScrollView(
      child: Column(
        children: [
          const Padding(
            padding: EdgeInsets.only(
              top: 16.0,
              left: 16,
              right: 16,
            ),
            child: TextField(
              textInputAction: TextInputAction.search,
            ),
          ),
          ListView.builder(
            shrinkWrap: true,
            physics: const NeverScrollableScrollPhysics(),
            itemCount: itemList.length,
            itemBuilder: (BuildContext context, int index) {
              return ListTile(
                title: Text(itemList[index]),
              );
            },
          ),
        ],
      ),
    );
  }
}

在滑动离开并返回时,如何始终保留我的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.

import &#39;package:flutter/material.dart&#39;;

class SampleListApp extends StatefulWidget {
  const SampleListApp({Key? key}) : super(key: key);

  @override
  State&lt;SampleListApp&gt; createState() =&gt; _SampleListAppState();
}

class _SampleListAppState extends State&lt;SampleListApp&gt;
    with TickerProviderStateMixin {
  TabController? _tabController;
  late ScrollController _scrollViewController;
  int _currentTabIndex = 0;

  _handleTabChange() {
    setState(() {
      if (_tabController != null) {
        _currentTabIndex = _tabController!.index;
      }
    });
  }

  _initTabController() {
    _tabController?.dispose();
    _tabController =
        TabController(initialIndex: _currentTabIndex, vsync: this, length: 2);
    _tabController?.addListener(_handleTabChange);
  }

  @override
  void initState() {
    super.initState();
    _initTabController();
    _scrollViewController = ScrollController(initialScrollOffset: 0.0);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: NestedScrollView(
        controller: _scrollViewController,
        headerSliverBuilder: (BuildContext context, bool boxIsScrolled) =&gt; [
          SliverAppBar(
            title: const Text(&quot;Hello World&quot;),
            bottom: TabBar(
              indicatorColor: Colors.white,
              isScrollable: true,
              labelColor: Colors.orange,
              unselectedLabelColor: Colors.grey,
              tabs: const [
                Tab(
                  text: &quot;Tab 1&quot;,
                ),
                Tab(
                  text: &quot;Tab 2&quot;,
                )
              ],
              controller: _tabController,
            ),
            pinned: true,
            floating: true,
            forceElevated: boxIsScrolled,
          ),
        ],
        body: TabBarView(
          key: UniqueKey(),
          controller: _tabController,
          children: const [
            ChildView(),
            ChildView(),
          ],
        ),
      ),
    );
  }
}

class ChildView extends StatefulWidget {
  const ChildView({Key? key}) : super(key: key);

  @override
  State&lt;ChildView&gt; createState() =&gt; _ChildViewState();
}

class _ChildViewState extends State&lt;ChildView&gt; {
  @override
  Widget build(BuildContext context) {
    final List&lt;String&gt; itemList =
        List.generate(100, (index) =&gt; &quot;Item ${index + 1}&quot;);
    return SingleChildScrollView(
      child: Column(
        children: [
          const Padding(
            padding: EdgeInsets.only(
              top: 16.0,
              left: 16,
              right: 16,
            ),
            child: TextField(
              textInputAction: TextInputAction.search,
            ),
          ),
          ListView.builder(
            shrinkWrap: true,
            physics: const NeverScrollableScrollPhysics(),
            itemCount: itemList.length,
            itemBuilder: (BuildContext context, int index) {
              return ListTile(
                title: Text(itemList[index]),
              );
            },
          ),
        ],
      ),
    );
  }
}

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时重新构建:

class ChildView extends StatefulWidget {
  const ChildView({Key? key}) : super(key: key);

  @override
  State<ChildView> createState() => _ChildViewState();
}

class _ChildViewState extends State<ChildView>
    with AutomaticKeepAliveClientMixin {
  @override
  Widget build(BuildContext context) {
    super.build(context);
    final List<String> itemList =
        List.generate(100, (index) => "Item ${index + 1}");
    return SingleChildScrollView(
      child: Column(
        children: [
          const Padding(
            padding: EdgeInsets.only(
              top: 16.0,
              left: 16,
              right: 16,
            ),
            child: TextField(
              textInputAction: TextInputAction.search,
            ),
          ),
          ListView.builder(
            shrinkWrap: true,
            physics: const NeverScrollableScrollPhysics(),
            itemCount: itemList.length,
            itemBuilder: (BuildContext context, int index) {
              return ListTile(
                title: Text(itemList[index]),
              );
            },
          ),
        ],
      ),
    );
  }

  @override
  bool get wantKeepAlive => true;
}

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

英文:

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

class ChildView extends StatefulWidget {
  const ChildView({Key? key}) : super(key: key);

  @override
  State&lt;ChildView&gt; createState() =&gt; _ChildViewState();
}

class _ChildViewState extends State&lt;ChildView&gt;
    with AutomaticKeepAliveClientMixin {
  @override
  Widget build(BuildContext context) {
    super.build(context);
    final List&lt;String&gt; itemList =
        List.generate(100, (index) =&gt; &quot;Item ${index + 1}&quot;);
    return SingleChildScrollView(
      child: Column(
        children: [
          const Padding(
            padding: EdgeInsets.only(
              top: 16.0,
              left: 16,
              right: 16,
            ),
            child: TextField(
              textInputAction: TextInputAction.search,
            ),
          ),
          ListView.builder(
            shrinkWrap: true,
            physics: const NeverScrollableScrollPhysics(),
            itemCount: itemList.length,
            itemBuilder: (BuildContext context, int index) {
              return ListTile(
                title: Text(itemList[index]),
              );
            },
          ),
        ],
      ),
    );
  }

  @override
  bool get wantKeepAlive =&gt; true;
}

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:

确定