You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter中NestedScrollView内TabBarView切换时保持滚动位置

解决Flutter TabBar切换后ListView滚动位置丢失的问题

问题根源

  • TabBarView的UniqueKey导致重建:原代码中TabBarView(key: UniqueKey())会让Flutter在切换Tab时销毁并重建子Widget,滚动位置自然丢失。
  • 共用ScrollController:所有Tab共享同一个_scrollViewController,无法独立记录各自的滚动状态。
  • 嵌套滚动结构不合理:SingleChildScrollView包裹ListView的嵌套结构,加上shrinkWrap和NeverScrollablePhysics,会干扰滚动状态的持久化。

修复后的完整代码

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 {
  late TabController _tabController;
  // 为每个Tab维护独立的ScrollController
  final List<ScrollController> _scrollControllers = [
    ScrollController(),
    ScrollController(),
  ];
  int _currentTabIndex = 0;

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

  @override
  void initState() {
    super.initState();
    _tabController = TabController(initialIndex: _currentTabIndex, vsync: this, length: 2);
    _tabController.addListener(_handleTabChange);
  }

  @override
  void dispose() {
    _tabController.dispose();
    // 销毁所有ScrollController
    for (var controller in _scrollControllers) {
      controller.dispose();
    }
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: NestedScrollView(
        // 不需要给NestedScrollView设置全局controller,让子Widget管理自己的滚动
        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(
          // 移除UniqueKey,让Flutter复用子Widget状态
          controller: _tabController,
          children: [
            ChildView(scrollController: _scrollControllers[0]),
            ChildView(scrollController: _scrollControllers[1]),
          ],
        ),
      ),
    );
  }
}

class ChildView extends StatefulWidget {
  final ScrollController scrollController;

  const ChildView({Key? key, required this.scrollController}) : super(key: key);

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

class _ChildViewState extends State<ChildView> {
  final List<String> itemList = List.generate(100, (index) => "Item ${index + 1}");

  @override
  Widget build(BuildContext context) {
    // 优化为直接用ListView,将TextField作为header加入列表
    return ListView.builder(
      controller: widget.scrollController,
      itemCount: itemList.length + 1, // +1是header的数量
      itemBuilder: (BuildContext context, int index) {
        if (index == 0) {
          // 顶部的TextField
          return const Padding(
            padding: EdgeInsets.only(
              top: 16.0,
              left: 16,
              right: 16,
            ),
            child: TextField(
              textInputAction: TextInputAction.search,
            ),
          );
        }
        // 列表项
        return ListTile(
          title: Text(itemList[index - 1]),
        );
      },
    );
  }
}

关键修改说明

  1. 移除TabBarView的UniqueKey:让Flutter能够复用每个Tab对应的ChildView实例,保留其内部状态(包括滚动位置)。
  2. 为每个Tab分配独立的ScrollController:在父组件中创建两个ScrollController,分别传递给对应的ChildView,确保每个Tab的滚动位置被单独记录。
  3. 优化滚动结构:将原有的SingleChildScrollView嵌套ListView的结构,改为直接用ListView.builder并将TextField作为列表的第一个item,避免嵌套滚动带来的状态管理问题,同时提升性能。
  4. 正确管理控制器生命周期:在父组件的dispose方法中销毁所有ScrollController,避免内存泄漏。

内容的提问来源于stack exchange,提问作者ololo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 12:27:58