Search code examples
flutterscrollcontroller

How to reset scrollstate everytime I switch Tabs?


enter image description here

Whenever, I switch from About to Recs or References tab my scroll position in the tab is maintained across all tabs. I want it to reset everytime I switch tabs. i.e Scrolling should start from top and not where I left in my previous tab before switching.

Here is my code:

// ignore_for_file: prefer_const_constructors


import 'package:flutter/material.dart';

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

  @override
  _JobDetailsState createState() => _JobDetailsState();
}

class _JobDetailsState extends State<JobDetails>
    with SingleTickerProviderStateMixin {
  late TabController _tabController;


  @override
  void initState() {
    super.initState();
    _tabController = TabController(length: 3, vsync: this);

  }

  @override
  void dispose() {
    _tabController.dispose();

    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: NestedScrollView(
        headerSliverBuilder: (context, isScrolled) {
          return [
            SliverPersistentHeader(
              pinned: true,
              delegate: MyHeaderDelegate(),
            ),
            
            // SliverPadding(
            //   padding: EdgeInsets.symmetric(vertical: 26, horizontal: 18),
            SliverPadding(
              padding: EdgeInsets.symmetric(horizontal: 18),
              sliver: SliverAppBar(
                shape: ContinuousRectangleBorder(
                  borderRadius: BorderRadius.all(Radius.circular(20)),
                ),
                automaticallyImplyLeading: false,
                toolbarHeight: kTextTabBarHeight,
                primary: false,
                backgroundColor: Colors.grey[200],
                pinned: true,
                titleSpacing: 0,
                title: TabBar(
                  indicator: BoxDecoration(
                      border: Border.all(
                        color: AppConstants.primaryColor,
                      ),
                      borderRadius: BorderRadius.circular(7),
                      color: AppConstants.primaryColor),
                  unselectedLabelColor: const Color(0x66181D1A),
                  splashBorderRadius: BorderRadius.circular(7),
                  controller: _tabController,
                  //labelPadding: EdgeInsets.only(bottom: 20),
                  tabs: const [
                    Tab(
                      child: Text('About'),
                    ),
                    Tab(text: 'Recs'),
                    Tab(text: 'References'),
                  ],
                ),
              ),
            ),
          ];
        },
        body: TabBarView(
          controller: _tabController,
          children: [
            SingleChildScrollView(
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  // Content for Tab 1
                  Padding(
                    padding: EdgeInsets.all(18.0),
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        
                        Text('Tab1')
                        
                       
                      ],
                    ),
                  ),
                ],
              ),
            ),
            SingleChildScrollView(
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: const [
                  // Content for Tab 2
                  Padding(
                    padding: EdgeInsets.all(18.0),
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        SizedBox(height: 12,),
                        Text('Tab 2 Content'),
                        // Add your content for Tab 2 here
                      ],
                    ),
                  ),
                ],
              ),
            ),
             SingleChildScrollView(
               child: Column(
                 crossAxisAlignment: CrossAxisAlignment.start,
                 children: [
                   // Content for Tab 3
                   Padding(
                     padding: EdgeInsets.all(18.0),
                     child: Column(
                       crossAxisAlignment: CrossAxisAlignment.start,
                       children: [
                        SizedBox(height: 12,),
                        Text('Tab3')
                         // Add your content for Tab 3 here
                       ],
                     ),
                   ),
                 ],
               ),
             ),
          ],
        ),
      ),
    );
  }
}

class MyHeaderDelegate extends SliverPersistentHeaderDelegate {
  //delegate implementation
}

Tried using a scroll controller and resetting the state for every page change by adding a custom function after init, but did not work


Solution

  • The issue with your code is that all your TabBarView children are within the same class of JobDetails, this causes all of the tabs to load during build time even when not selected.

    This is not the best practice because unnecessary code is executing even when the tab is not selected, and therefore your app could be slower during build time and can create unnecessary read/write to your database if you have any. Also there is no separate key that will reset the scroll of the widgets.

    It would be better practice to load the TabBarView children only and only if the tab is selected by doing this:

    body: TabBarView(
              controller: _tabController,
              children: [
                Widget1
                Widget2
                Widget3
              ]),
    
    class Widget1 extends..... return SingleChildSrollView(...)
    

    This would cause your scrolls to reset when switching Tabs because the children is being re-build (code executes) only after the specific tab is selected and the way to save the scroll position would be by using key: const PageStorageKey<String>('key') in the scrollable widget

    However if you're still insisting on doing it your way, then you need to add a ScrollController and a Listener to listen for tab changes.

    ScrollController _scrollController = ScrollController();
    
    @override
      void initState() {
        _tabController = TabController(length: 3, vsync: this);
        _tabController.addListener(_handleTabChange);
        super.initState();
      }
    
      @override
      void dispose() {
        _tabController.dispose();
        _scrollController.dispose();
        super.dispose();
      }
    
      void _handleTabChange() {
        if (!_tabController.indexIsChanging) {
          _resetScroll();
        }
      }
    
      void _resetScroll() {
        _scrollController.jumpTo(0.0);
        //or use .animateTo() for a smooth scroll
      }
    

    Now add controller: _scrollController in every SingleChildScrollView() widget.

    Keep in mind that the scroll of NestedScrollView() widget could takeover the SingleChildScrollView() widget. If you are facing issues with that, you can either add controller: _scrollController to the NestedScrollView widget too or make it unscrollable by adding the parameter: physics: const NeverScrollableScrollPhysics(),