【Flutter】Sliverでスクロールした時に上にくっつくTabBarの実装方法
上にくっつくTabBarとは
そもそも上にくっつくTabBarってなんやねんと思う方もいるかもしれません
ここではいわゆるこんな感じのやつです。(正しい呼び方があれば教えてください)
SliverAppBar | SliverPersistentHeader |
---|---|
こちらの記事ではStickyTabBarと呼んでいました!
この実装方法を主に2つ紹介します
実装方法
- SliverAppBarによる実装
- SliverPersistentHeaderによる実装
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による実装
そこのあなた!AppBarとTabBarの間に何か挟みたい時、ありますよね??
そんな時に使うのがSliverPersistentHeaderです!! SliverPersistentHeaderを使うと色々自由にUIを組めそうなのでいいと思います!
ということでまずは全体的に見ていきましょう
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群を入れる必要があり、それ以外を入れるとエラーが発生します(TabBarを試しに入れてみるとエラーが出ます)↓
_headerSectionはSliverListを用いて作成しました。
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を継承したクラスを作成します
class _StickyTabBarDelegate extends SliverPersistentHeaderDelegate {
const _StickyTabBarDelegate({required this.tabBar});
final TabBar tabBar;
double get minExtent => tabBar.preferredSize.height;
double get maxExtent => tabBar.preferredSize.height;
Widget build(
BuildContext context,
double shrinkOffset,
bool overlapsContent,
) {
return Container(
color: Colors.white,
child: tabBar,
);
}
bool shouldRebuild(_StickyTabBarDelegate oldDelegate) {
return tabBar != oldDelegate.tabBar;
}
}
以上からSliverで使用できるTabBarができたので、_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);
Widget build(BuildContext context) {
return const MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({Key? key}) : super(key: key);
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;
double get minExtent => tabBar.preferredSize.height;
double get maxExtent => tabBar.preferredSize.height;
Widget build(
BuildContext context,
double shrinkOffset,
bool overlapsContent,
) {
return Container(
color: Colors.white,
child: tabBar,
);
}
bool shouldRebuild(_StickyTabBarDelegate oldDelegate) {
return tabBar != oldDelegate.tabBar;
}
}
Discussion