how to implement a sliverAppBar with a tabBar
Asked Answered
H

5

28

the flutter document show a demo for SliverAppBar + TabBar + TabBarView with ListView use NestedScrollView, and it's a bit complex, so I wonder is there a simply and clear way to implement it. I tried this:

CustomScrollView
  slivers:
    SliverAPPBar
      bottom: TabBar
    TabBarView
      children: MyWidget(list or plain widget)

got error:

flutter: The following assertion was thrown building Scrollable(axisDirection: right, physics:
flutter: A RenderViewport expected a child of type RenderSliver but received a child of type _RenderExcludableScrollSemantics.
flutter: RenderObjects expect specific types of children because they coordinate with their children during layout and paint. For example, a RenderSliver cannot be the child of a RenderBox because a RenderSliver does not understand the RenderBox layout protocol.

and

flutter: Another exception was thrown: 'package:flutter/src/widgets/framework.dart': Failed assertion: line 3497 pos 14: 'owner._debugCurrentBuildTarget == this': is not true.

HERE IS MY CODE:

import 'package:flutter/material.dart';

main(List<String> args) {
  runApp(MyScrollTabListApp());
}

class MyScrollTabListApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(title: "aa", home: MyScrollTabListHomePage());
  }
}

class MyScrollTabListHomePage extends StatefulWidget {
  @override
  MyScrollTabListHomePageState createState() {
    return new MyScrollTabListHomePageState();
  }
}

class MyScrollTabListHomePageState extends State<MyScrollTabListHomePage>
    with SingleTickerProviderStateMixin {
  final int _listItemCount = 300;
  final int _tabCount = 8;
  TabController _tabController;

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: CustomScrollView(
        slivers: <Widget>[
          SliverAppBar(
            expandedHeight: 240.0,
            title: Text("Title"),
            pinned: true,
            bottom: TabBar(
              controller: _tabController,
              isScrollable: true,
              tabs: List<Tab>.generate(_tabCount, (int i) {
                return Tab(text: "TAB$i");
              }),
            ),
          ),
          TabBarView(
            controller: _tabController,
            children: List<Widget>.generate(_tabCount, (int i) {
              return Text('line $i');
            }),
          ),
        ],
      ),
    );
  }
}

and for the official demo, it use struct like this

DefaultTabController
    NestedScrollView
      headerSliverBuilder
        SliverOverlapAbsorber
          handle
          SliverAppBar
        TabBarView
          CustomScrollView
            SliverOverlapInjector
              handle
              SliverPadding
Hemichordate answered 7/6, 2018 at 12:53 Comment(1)
check the answer here: https://mcmap.net/q/304079/-sliverappbar-with-tabbar/…Percussionist
D
33

Use NestedScrollView, here is the working code.

@override
Widget build(BuildContext context) {
  return Scaffold(
    body: DefaultTabController(
      length: 2,
      child: NestedScrollView(
        headerSliverBuilder: (context, value) {
          return [
            SliverAppBar(
              bottom: TabBar(
                tabs: [
                  Tab(icon: Icon(Icons.call), text: "Call"),
                  Tab(icon: Icon(Icons.message), text: "Message"),
                ],
              ),
            ),
          ];
        },
        body: TabBarView(
          children: [
            CallPage(),
            MessagePage(),
          ],
        ),
      ),
    ),
  );
}
Derian answered 29/5, 2019 at 18:29 Comment(0)
E
5

Here is an example for TabView with SilverAppBar

class SilverAppBarWithTabBarScreen extends StatefulWidget {
  @override
  _SilverAppBarWithTabBarState createState() => _SilverAppBarWithTabBarState();
}

class _SilverAppBarWithTabBarState extends State<SilverAppBarWithTabBarScreen>
    with SingleTickerProviderStateMixin {
  TabController controller;

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

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      body: new CustomScrollView(
        slivers: <Widget>[
          new SliverAppBar(
            title: Text("Silver AppBar With ToolBar"),
            pinned: true,
            expandedHeight: 160.0,
            bottom: new TabBar(
              tabs: [
                new Tab(text: 'Tab 1'),
                new Tab(text: 'Tab 2'),
                new Tab(text: 'Tab 3'),
              ],
              controller: controller,
            ),
          ),
          new SliverList(
          new SliverFillRemaining(
        child: TabBarView(
          controller: controller,
          children: <Widget>[
               Text("Tab 1"),
               Text("Tab 2"),
               Text("Tab 3"),
             ],
            ),
          ),
        ],
      ),
    );
  }
}
Earhart answered 14/6, 2018 at 12:53 Comment(3)
yes, as @Negora say, it's there is no TabBarView, and when the tabs in header bottom changes, the page body will not changeHemichordate
@DhirajSharma as the code new SliverList( new SliverFillRemaining(...), seems SliverList doesn't have such a constructor which we can pass SliverFillRemaining as a parameter directlyHemichordate
@DhirajSharma and my problem is that each TabView contains a sliver list(which means a scrollable widget), not a simple widget.Hemichordate
H
2

Yeah. You can use NestedScrollView To Achieve Tabs. Here is Some Addition Code To That.

class AppView extends StatelessWidget {
final double _minValue = 8.0;

@override
Widget build(BuildContext context) {
final textTheme = Theme.of(context).textTheme;

return Scaffold(
  appBar: MyAppBar(),
  drawer: DrawerDialog(),
  body: DefaultTabController(
    length: 3,
    child: SafeArea(
      child: NestedScrollView(
        body: TabBarView(
          children: [Text("Page 1"), Text("Page 2"), Text("Page 3")],
        ),
        headerSliverBuilder:
            (BuildContext context, bool innerBoxIsScrolled) => [
          SliverPadding(
            padding: EdgeInsets.all(_minValue * 2.5),
            sliver: SliverToBoxAdapter(
              child: Text(
                "Hiding Header",
                style: textTheme.headline6,
                textAlign: TextAlign.center,
              ),
            ),
          ),
          SliverAppBar(
            backgroundColor: Colors.grey[100],
            pinned: true,
            elevation: 12.0,
            leading: Container(),
            titleSpacing: 0.0,
            toolbarHeight: 10,
            bottom: TabBar(tabs: [
              Tab(
                child: Text(
                  "All",
                  style: textTheme.subtitle2,
                ),
              ),
              Tab(
                child: Text(
                  "Categories",
                  style: textTheme.subtitle2,
                ),
              ),
              Tab(
                child: Text(
                  "Upcoming",
                  style: textTheme.subtitle2,
                ),
              ),
            ]),
          ),
        ],
      ),
    ),
  ),
);

} }

Hanyang answered 25/2, 2021 at 16:8 Comment(0)
D
1

If you want to use CustomScrollView, you can make use of SliverToBoxAdapter:

Widget build(BuildContext context) {
  return Scaffold(
    body: DefaultTabController(
      length: 2,
      child: CustomScrollView(
        slivers: [
          SliverAppBar(
            title: Text('SliverAppBar'),
            bottom: TabBar(
              tabs: [
                Tab(icon: Icon(Icons.call), text: "Call"),
                Tab(icon: Icon(Icons.message), text: "Message"),
              ],
            ),
          ),
          SliverToBoxAdapter(
            child: SizedBox(
              height: MediaQuery.of(context).size.height,
              child: TabBarView(
                children: [
                  Container(color: Colors.red),
                  Container(color: Colors.blue),
                ],
              ),
            ),
          ),
        ],
      ),
    ),
  );
}
Derian answered 14/12, 2021 at 15:56 Comment(0)
H
0

You can Also Implement it by Providing _tabController to Both TabBar() and TabBarView So It will bind. And For the children of TabBarView if you're using ListView then give it physics: NeverScrollablePhysics() so it won't move, kindly not You have to give dynamic height to the Container of ListView So it will load all Children's.

class _HomeState extends State<Home> with SingleTickerProviderStateMixin {
  TabController _tabController;
  @override
  void initState() {
    _tabController = TabController(length: 2, vsync: this);
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: CustomScrollView(
      slivers: [
        SliverAppBar(
          floating: true,
          expandedHeight: 50,
          title: Column(
            children: [
              Row(
                children: [
                  Text('Hello, User'),
                  Spacer(),
                  InkWell(
                    child: Icon(Icons.map_rounded),
                  ),
                
                ],
              ),
            ],
          ),
        ),
        SliverList(
            delegate: SliverChildListDelegate([
          _tabSection(context),
        ])),
      ],
    ));
  }

  Widget _tabSection(BuildContext context) {
    final height = MediaQuery.of(context).size.height;
    final width = MediaQuery.of(context).size.width;
    double mainAxisHeight = height > width ? height : width;
    return DefaultTabController(
        length: 2,
        child: Column(mainAxisSize: MainAxisSize.min, children: <Widget>[
          Container(
            height: 48,
            decoration: BoxDecoration(
                color: Colors.green,
                borderRadius: BorderRadius.only(
                    bottomRight: Radius.circular(10),
                    bottomLeft: Radius.circular(10))),
            child: TabBar(
                indicatorColor: Colors.white,
                indicator: UnderlineTabIndicator(
                  borderSide: BorderSide(color: Colors.white, width: 5.0),
                  insets: EdgeInsets.symmetric(horizontal: 40),
                ),
                labelColor: Colors.white,
                unselectedLabelColor: Colors.grey[300],
                tabs: [
                  Tab(
                    iconMargin: EdgeInsets.only(top: 5),
                    text: "Tab Bar 1",
                  ),
                  Tab(
                    iconMargin: EdgeInsets.only(top: 5),
                    text: "Tab bar 2",
                  ),
                ]),
          ),
          
          Container(
                height: 200 * 6 // 200 will be Card Size and 6 is number of Cards
              child: TabBarView(controller: _tabController, children: [
                tabDetails(),
                tabDetails(),
              ]))
        ]));
  }

  tabDetails() {
    final height = MediaQuery.of(context).size.height;
    final width = MediaQuery.of(context).size.width;
    double mainAxisHeight = height > width ? height : width;
    return Container(
    
      padding: EdgeInsets.symmetric(horizontal: 15),
      decoration: BoxDecoration(
          gradient: LinearGradient(
              begin: Alignment.topCenter,
              end: Alignment.bottomCenter,
              colors: [
     
            Colors.red[100],
            Colors.red[200],
          ])),
      child: ListView(
        physics: NeverScrollableScrollPhysics(),  // This will disable LitView'Scroll so only Scroll is possible by TabBarView Scroll.
        children: [
          SizedBox(height: 10),        
          Container(
          height:140,
            width: width,
            child: ListView.builder(
              scrollDirection: Axis.vertical,
              itemCount: 6,
              itemBuilder: (BuildContext context, int indexChild) {
                return Row(
                  children: [
                    MyListTile(
                      name: "Name",
                   
                    ),
                    SizedBox(width: 5),
                  ],
                );
              },
            ),
          ),
        
       
          SizedBox(height: 1000),
        ],
      ),
    );
  }
}
Hardspun answered 1/3, 2021 at 11:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.