18
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Flutter大学Advent Calendar 2021

Day 20

【Flutter】Sliverでスクロールした時に上にくっつくTabBarの実装する

Last updated at Posted at 2021-12-19

上にくっつくTabBarとは

そもそも上にくっつくTabBarってなんやねんと思う方もいるかもしれません
ここではいわゆるこんな感じのやつです。(正しい呼び方があれば教えてください!)

SliverAppBar SliverPersistentHeader
SliverAppBar SliverPersistentHeader

こちらの記事ではStickyTabBarと呼んでいました!

この実装方法を主に2つ紹介します

実装方法

  1. SliverAppBarによる実装
  2. SliverPersistentHeaderによる実装

SliverAppBarによる実装

SliverAppBar

SliverAppBarについてはこちらの公式ドキュメントによくまとまっています。いろんな例も詳しく載っているのでぜひご覧ください!

NestedScrollViewを使用することによりSliverAppBar+TabBarViewの実装を可能にしています。
ドキュメントによるとNestedScrollViewのheaderにSliverAppBar、bodyにTabBarViewを設定するという実装が一般的だそう。

実装例
//DefaultTabControllerでTabの数を設定する
 DefaultTabController(
        length: 2,
        child: NestedScrollView(
          headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
	  //headerSliverBuilder内でSliverAppBarを設定
            return <Widget>[
              const SliverAppBar(
                pinned: true,//trueの場合、スクロースしても上にAppBarが残る
                expandedHeight: 150,//拡大されている際のAppBarの高さ
                bottom: TabBar(
                  //AppBarと同様にbottomにTabBarを設定
                ),
              ),
            ];
          },
	  //bodyにTabBarViewを設定
          body: TabBarView(
            children: [
		//Tabごとにウィジェットを設定する
            ],
          ),
        ),
      ),

SliverPersistentHeaderによる実装

SliverPersistentHeader

そこのあなた!AppBarとTabBarの間に何か挟みたい時、ありますよね??
そんな時に使うのがSliverPersistentHeaderです!! 

ということでまずは全体的に見ていきましょう

Scaffold(
      //AppBarを設定する
      appBar: AppBar(),
      body: DefaultTabController(
        length: 2,
        child: NestedScrollView(
          headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
            return <Widget>[
              _headerSection(),//オレンジ色の部分
              _tabSection(),//TabBarの部分
            ];
          },
          body: TabBarView(
            children: [
            //省略
            ],
          ),
        ),
      ),
    );

_headerSection()について

NestedScrollViewのheaderSliverBuilder内ではSliverとつくWidget群を入れる必要があります。そこで_headerSectionはSliverListを用いて作成しました。

_headerSection
Widget _headerSection() {
  return SliverList(
    delegate: SliverChildListDelegate(
      [
      //ここをカスタマイズする
        Container(
          color: Colors.orangeAccent,
          height: 100,
          child: const Center(
            child: Text('headerSection'),
          ),
        ),
      ],
    ),
  );
}

_tabSection()について

SliverとつくWidget群を入れる必要があるためSliverPersistentHeaderを用いてTabBarを設定するのですが、SliverPersistentHeaderDelegateが必要となります

SliverPersistentHeader(
    delegate: //SliverPersistentHeaderDelegateが必要(TabBar部分)
  );

そこでSliverPersistentHeaderを継承したクラスを作成します

SliverPersistentHeaderDelegateを継承したTabBar
class _StickyTabBarDelegate extends SliverPersistentHeaderDelegate {
  const _StickyTabBarDelegate({required this.tabBar});

  final TabBar tabBar;

  @override
  double get minExtent => tabBar.preferredSize.height;

  @override
  double get maxExtent => tabBar.preferredSize.height;

  @override
  Widget build(
    BuildContext context,
    double shrinkOffset,
    bool overlapsContent,
  ) {
    return Container(
      color: Colors.white,
      child: tabBar,
    );
  }

  @override
  bool shouldRebuild(_StickyTabBarDelegate oldDelegate) {
    return tabBar != oldDelegate.tabBar;
  }
}

以上からSliverで使用できるTabBarができたので、_tabSectionはこのようになります

_tabSection()
Widget _tabSection() {
  return const SliverPersistentHeader(
    pinned: true,
    delegate: _StickyTabBarDelegate(
      tabBar: TabBar(
        tabs: [
          Tab(
            text: '1',
          ),
          Tab(
            text: '2',
          )
        ],
      ),
    ),
  );
}

全体のコード

こちらにSliverPersistentHeaderによる実装のサンプルコードを置いておくので色々試してみてください!

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

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

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

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: DefaultTabController(
        length: 2,
        child: NestedScrollView(
          headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
            return <Widget>[
              _headerSection(),
              _tabSection(),
            ];
          },
          body: TabBarView(
            children: [
              ListView.builder(
                itemCount: 10,
                itemBuilder: (context, index) {
                  return Center(
                    child: Text(
                      index.toString(),
                      style: const TextStyle(
                        fontSize: 100,
                      ),
                    ),
                  );
                },
              ),
              ListView.builder(
                itemCount: 10,
                itemBuilder: (context, index) {
                  return Center(
                    child: Text(
                      index.toString(),
                      style: const TextStyle(
                        fontSize: 100,
                      ),
                    ),
                  );
                },
              ),
            ],
          ),
        ),
      ),
    );
  }
}

//header部分
Widget _headerSection() {
  return SliverList(
    delegate: SliverChildListDelegate(
      [
        Container(
          color: Colors.orangeAccent,
          height: 100,
          child: const Center(
            child: Text('headerSection'),
          ),
        ),
      ],
    ),
  );
}

//TabBar部分
Widget _tabSection() {
  return const SliverPersistentHeader(
    pinned: true,
    delegate: _StickyTabBarDelegate(
      tabBar: TabBar(
        labelColor: Colors.black,
        tabs: [
          Tab(
            text: '1',
          ),
          Tab(
            text: '2',
          )
        ],
      ),
    ),
  );
}

//SliverPersistentHeaderDelegateを継承したTabBarを作る
class _StickyTabBarDelegate extends SliverPersistentHeaderDelegate {
  const _StickyTabBarDelegate({required this.tabBar});

  final TabBar tabBar;

  @override
  double get minExtent => tabBar.preferredSize.height;

  @override
  double get maxExtent => tabBar.preferredSize.height;

  @override
  Widget build(
    BuildContext context,
    double shrinkOffset,
    bool overlapsContent,
  ) {
    return Container(
      color: Colors.white,
      child: tabBar,
    );
  }

  @override
  bool shouldRebuild(_StickyTabBarDelegate oldDelegate) {
    return tabBar != oldDelegate.tabBar;
  }
}

18
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
18
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?