Home > Software design >  How to Scroll to Top by Tapping BottomNavigationBar where There is Also a Tabbar
How to Scroll to Top by Tapping BottomNavigationBar where There is Also a Tabbar

Time:03-29

As the title above, I want the page I've scrolled down to scroll back up when the BottomNavigationBar is pressed (double-pressed). I've managed to make it, no problem. But there is a problem if on the page there is contain a TabBar.


For Example I have a bottom navigation bar, that contains two pages:

HomePage and ProfilePage


On the HomePage I have a TabBar, and that contains two pages also:

FirstTabBarView and SecondTabBarView


The problem is when I've scrolled those two pages down (HomePage & ProfilePage at a different) I just want the FirstTabBarView view page to scroll to the top if I'm currently opening the FirstTabBarView page. But from my code, if I press the BottomNavigationBar button twice, both FirstTabBarView and SecondTabBarView both scroll up.

This is just a simple code sample, you can copy-paste and test it on the dart pad directly

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  static const String _title = 'Flutter Code Sample';

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: _title,
      home: MyStatefulWidget(),
    );
  }
}

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

  @override
  State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}

class _MyStatefulWidgetState extends State<MyStatefulWidget> {
  final ScrollController _firstTabBarScrollController = ScrollController();
  final ScrollController _secondTabBarScrollController = ScrollController();
  List<Widget> _widgetOptions = <Widget>[];
  int _selectedIndex = 0;
  
  @override
  void initState(){
    _widgetOptions = <Widget>[
      MyHomePage(
        firstTabBarScrollController: _firstTabBarScrollController, 
        secondTabBarScrollController: _secondTabBarScrollController,
        title: "Home Page",
      ),
      Text('Profile'),
    ];
  }


  void _onItemTapped(int index) {
    setState(() {
      _selectedIndex = index;
    });
    if(index == 0 && _firstTabBarScrollController.hasClients){
       _firstTabBarScrollController.animateTo(0,
            duration: const Duration(milliseconds: 500), curve: Curves.easeOut,);
    }
    
    if(index == 0 && _secondTabBarScrollController.hasClients){
       _secondTabBarScrollController.animateTo(0,
            duration: const Duration(milliseconds: 500), curve: Curves.easeOut,);
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: _widgetOptions.elementAt(_selectedIndex),
      ),
      bottomNavigationBar: BottomNavigationBar(
        items: const <BottomNavigationBarItem>[
          BottomNavigationBarItem(
            icon: Icon(Icons.home),
            label: 'Home',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.business),
            label: 'Profile',
          ),
        ],
        currentIndex: _selectedIndex,
        selectedItemColor: Colors.amber[800],
        onTap: _onItemTapped,
      ),
    );
  }
}

class MyHomePage extends StatefulWidget{
  final ScrollController firstTabBarScrollController;
  final ScrollController secondTabBarScrollController;
  final String title;

  const MyHomePage({
    Key? key,
    required this.title,
    required this.firstTabBarScrollController,
    required this.secondTabBarScrollController,
  }) : super(key: key);

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

class _MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin {
  late TabController _tabController;
  
  @override
  void initState() {
    super.initState();
    _tabController = TabController(length: 2, vsync: this);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
        bottom: TabBar(
          controller: _tabController,
          tabs: const <Widget>[
            Tab(
              icon: Icon(Icons.cloud_outlined),
            ),
            Tab(
              icon: Icon(Icons.beach_access_sharp),
            ),
          ],
        ),
      ),
      body: TabBarView(
        controller: _tabController,
        children: <Widget>[
          FirstTabBarView(firstTabScrollController: widget.firstTabBarScrollController),
          SecondTabBarView(secondTabScrollController: widget.secondTabBarScrollController),
        ],
      ),
    );
  }
}

class FirstTabBarView extends StatefulWidget{
  final ScrollController firstTabScrollController;

  const FirstTabBarView({
    Key? key,
    required this.firstTabScrollController,
  }) : super(key: key);

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

class _FirstTabBarView extends State<FirstTabBarView> with AutomaticKeepAliveClientMixin<FirstTabBarView> {
 
  @override
  Widget build(BuildContext context) {
    super.build(context);
    return Scaffold(
      body: ListView.builder(
        controller: widget.firstTabScrollController,
        itemCount: 1000,
        itemBuilder: (context, index) {
          return ListTile(
            title: Text('$index'),
          );
        },
      ),
    );
  }
  
  @override
  bool get wantKeepAlive => true;
}


class SecondTabBarView extends StatefulWidget{
  final ScrollController secondTabScrollController;

  const SecondTabBarView({
    Key? key,
    required this.secondTabScrollController,
  }) : super(key: key);

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

class _SecondTabBarView extends State<SecondTabBarView> with AutomaticKeepAliveClientMixin<SecondTabBarView> {
 
  @override
  Widget build(BuildContext context) {
    super.build(context);
    return Scaffold(
      backgroundColor: Colors.yellow,
      body: ListView.builder(
        controller: widget.secondTabScrollController,
        itemCount: 1000,
        itemBuilder: (context, index) {
          return ListTile(
            title: Text('$index'),
          );
        },
      ),
    );
  }
  
  @override
  bool get wantKeepAlive => true;
}

Note: For example case of the code above

  1. Scroll down the FirstTabBarView,

  2. Open the SecondTabBarView, and scroll down the SecondTabBarView,

  3. Double-tap Home Icon button

  4. You will notice that both the FirstTabBarView and SecondTabBarView pages will scroll up. What I want is only the currently open page to scroll to the top (one of them).

CodePudding user response:

You will have to lift up the TabController along with the scrollcontrollers. Then use the TabController's index getter to check which tab is currently open.

TabController _tabController = TabController(length: 2, vsync: this);

if(index == 0 && _tabController.index == 0){

   //Scroll up the first scroll controller

}

if(index == 0 && _tabController.index == 1){

  //Scroll up the second scroll controller
}
  • Related