Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Hide the TabBar like a SliverAppBar

Tags:

flutter

dart

So there are many examples on the web where you can use a SliverAppBar that hides on scroll, and the TabBar below is still showing. I can't find anything that does it the other way around: When I scroll up I want to hide only the TabBar, keeping the AppBar persistent showing at all times. Does anyone know how to achieve this?

Here is a example with AppBar hiding (This is not what I want, just helps understand better what I want).

UPDATE

This is what I tried so far, and I thought it works, but the problem is I can't get the AppBar in the Positioned field to have the correct height (e.g. iPhone X its height is way bigger and overlaps with the tab bar).

// this sliver app bar is only use to hide/show the tabBar, the AppBar  
// is invisible at all times. The to the user visible AppBar is below
return Scaffold(
  body: Stack(
    children: <Widget>[
      NestedScrollView(
        headerSliverBuilder:
            (BuildContext context, bool innerBoxIsScrolled) {
          return <Widget>[
            SliverAppBar(
              floating: true,
              snap: true,
              pinned: false,
              bottom: TabBar(
                tabs: [
                  Tab(
                    child: Text(
                      "1",
                      textAlign: TextAlign.center,
                    ),
                  ),
                  Tab(
                    child: Text(
                      "2",
                      textAlign: TextAlign.center,
                    ),
                  ),
                  Tab(
                    child: Text(
                      "3",
                      textAlign: TextAlign.center,
                    ),
                  ),
                ],
                controller: _tabController,
              ),
            ),
          ];
        },
        body: TabBarView(
          children: [
            MyScreen1(),
            MyScreen2(),
            MyScreen3(),
          ],
          controller: _tabController,
          physics: new NeverScrollableScrollPhysics(),
        ),
      ),


      // Here is the AppBar the user actually sees. The SliverAppBar 
      // above will slide the TabBar underneath this one. However,
      // I can´t figure out how to give it the correct height.
      Container(
        child: Positioned(
          top: 0.0,
          left: 0.0,
          right: 0.0,
          child: AppBar(
            iconTheme: IconThemeData(
              color: Colors.red, //change your color here
            ),
            automaticallyImplyLeading: true,
            elevation: 0,
            title: Text("My Title"),
            centerTitle: true,

          ),
        ),
      ),

    ],

  ),
);
like image 436
最白目 Avatar asked May 29 '19 17:05

最白目


1 Answers

Here is How you can do that, the idea is to use a postframecallback with the help of a GlobalKey to precalculate the appBar height and add an exapandedHeight like below,

import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(

        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);


  final String title;

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

class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin  {

  TabController _tabController;
  GlobalKey _appBarKey;
  double _appBarHight;
  @override
  void initState() {
    _appBarKey = GlobalKey();
    _tabController = TabController(length: 3, vsync: this);
    SchedulerBinding.instance.addPostFrameCallback(_calculateAppBarHeight);
    super.initState();
  }
  _calculateAppBarHeight(_){
    final RenderBox renderBoxRed = _appBarKey.currentContext.findRenderObject();
     setState(() {
  _appBarHight = renderBoxRed.size.height;
});
    print("AppbarHieght = $_appBarHight");
  }

  @override
  Widget build(BuildContext context) {
    // this sliver app bar is only use to hide/show the tabBar, the AppBar
    // is invisible at all times. The to the user visible AppBar is below
    return Scaffold(
      body: Stack(
        children: <Widget>[
          NestedScrollView(
            headerSliverBuilder:
                (BuildContext context, bool innerBoxIsScrolled) {
              return <Widget>[
                SliverAppBar(
                  floating: true,
                  expandedHeight: _appBarHight,
                  snap: true,
                  pinned: false,
                  bottom: TabBar(
                    tabs: [
                      Tab(
                        child: Text(
                          "1",
                          textAlign: TextAlign.center,
                        ),
                      ),
                      Tab(
                        child: Text(
                          "2",
                          textAlign: TextAlign.center,
                        ),
                      ),
                      Tab(
                        child: Text(
                          "3",
                          textAlign: TextAlign.center,
                        ),
                      ),
                    ],
                    controller: _tabController,
                  ),
                ),
              ];
            },
            body: TabBarView(
              children: [
                MyScreen1(),
                MyScreen2(),
                MyScreen3(),
              ],
              controller: _tabController,
              physics: new NeverScrollableScrollPhysics(),
            ),
          ),


          // Here is the AppBar the user actually sees. The SliverAppBar
          // above will slide the TabBar underneath this one. However,
          // I can¥t figure out how to give it the correct height.
          Container(
            key: _appBarKey,
            child: Positioned(
              top: 0.0,
              left: 0.0,
              right: 0.0,
              child: AppBar(

                backgroundColor: Colors.red,
                iconTheme: IconThemeData(
                  color: Colors.red, //change your color here
                ),
                automaticallyImplyLeading: true,
                elevation: 0,
                title: Text("My Title"),
                centerTitle: true,

              ),
            ),
          ),

        ],

      ),
    );
  }

}

class MyScreen1 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      child: Center(
        child: Text("My Screen 1"),
      ),
    );
  }
}
class MyScreen2 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      child: Center(
        child: Text("My Screen 2"),
      ),
    );
  }
}
class MyScreen3 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      child: Center(
        child: Text("My Screen 3"),
      ),
    );
  }
}

Edit:

After more investigation I found a solution without keys or MediaQuery "stuff" by using just SafeArea Widget . please check the following Complete code :

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(

        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);


  final String title;

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

class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin  {

  TabController _tabController;

  @override
  void initState() {

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

    super.initState();
  }


  @override
  Widget build(BuildContext context) {
    // this sliver app bar is only use to hide/show the tabBar, the AppBar
    // is invisible at all times. The to the user visible AppBar is below
    return Scaffold(
      body: Stack(
        children: <Widget>[
          NestedScrollView(

            headerSliverBuilder:
                (BuildContext context, bool innerBoxIsScrolled) {
              return <Widget>[
                SliverAppBar(
                  primary: true,
                  floating: true,
                  backgroundColor: Colors.blue,//.withOpacity(0.3),
                  snap: true,
                  pinned: false,
                  bottom: TabBar(
                    tabs: [
                      Tab(
                        child: Text(
                          "1",
                          textAlign: TextAlign.center,
                        ),
                      ),
                      Tab(
                        child: Text(
                          "2",
                          textAlign: TextAlign.center,
                        ),
                      ),
                      Tab(
                        child: Text(
                          "3",
                          textAlign: TextAlign.center,
                        ),
                      ),
                    ],
                    controller: _tabController,
                  ),
                ),
              ];
            },
            body: TabBarView(
              children: [
                MyScreen1(),
                MyScreen2(),
                MyScreen3(),
              ],
              controller: _tabController,
              physics: new NeverScrollableScrollPhysics(),
            ),
          ),


          // Here is the AppBar the user actually sees. The SliverAppBar
          // above will slide the TabBar underneath this one. 
          // by using SafeArea it will.
          Positioned(
            top: 0.0,
            left: 0.0,
            right: 0.0,
            child: Container(
              child: SafeArea(
                top: false,
                child: AppBar(
                  backgroundColor: Colors.blue,
//                iconTheme: IconThemeData(
//                  color: Colors.red, //change your color here
//                ),
                  automaticallyImplyLeading: true,
                  elevation: 0,
                  title: Text("My Title",),
                  centerTitle: true,
                ),
              ),
            ),
          ),

        ],

      ),
    );
  }

}

class MyScreen1 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.yellow,
      child: Center(
        child: Text("My Screen 1"),
      ),
    );
  }
}
class MyScreen2 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      child: Center(
        child: Text("My Screen 2"),
      ),
    );
  }
}
class MyScreen3 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      child: Center(
        child: Text("My Screen 3"),
      ),
    );
  }
}
like image 125
Saed Nabil Avatar answered Oct 19 '22 04:10

Saed Nabil