株式会社Neverの近藤です。
株式会社Neverはモバイルアプリケーションの受託開発、技術支援、コンサルティングを行っております。アプリ開発のご依頼や開発面でのお困りの際はお気楽にこちらへお問合せください。
今日は FlutterでExtendedNestedScrollViewを用いて、Twitterのようなプロフィール画面とタブ画面の実装 について解説します。
目次
1.成果物
2.概要
3.実装手順
4.まとめ
5.おわりに
成果物
概要
実装したプロフィール画面で実装したい要件
- AppBarとTabBarの間にプロフィール画面を実装してスクロールした時にTabBarを上に固定
- タブを切り替えてもスクロールした位置を状態を維持
最初はNestedScrollViewで実装を試みたのですが、下記の2点のissueもあり今回はこのissueを改善したExtendedNestedScrollViewを使用して実装しました。
実装手順
ExtendedNestedScrollViewの追加
extended_nested_scroll_viewをpubspec.yaml
に追加します。
dependencies:
extended_nested_scroll_view: ^latest_version
スクロールした時にTabBarを上に固定
headerSliverBuilder
に、画面上部のプロフィール情報を表示し、スクロール時にタブが上部に固定されるように設定されています。
また、TabBarを継続的に表示させるためにSliverPersistentHeader
を使用することで実装しました。
SliverPersistentHeader
の使用でAppBarとTabBarの間にプロフィール画面を実装も可能になります。
ExtendedNestedScrollView(
// スクロール位置のすべてをまとめてスクロールするのを避ける
onlyOneScrollInBody: true,
// タブを上で固定するためにヘッダーの合計の高さを指定
pinnedHeaderSliverHeightBuilder: () {
return kToolbarHeight + MediaQuery.of(context).padding.top;
},
headerSliverBuilder: (context, innerBoxIsScrolled) {
return <Widget>[
SliverAppBar(
pinned: true, // スクロールした時に上にAppBarが表示されたままになる
elevation: 0,
title: Text(
'プロフィール',
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Colors.white,
),
),
),
// プロフィール画面
const SliverToBoxAdapter(
child: _ProfileTile(
userName: '株式会社Never',
userId: '@never_inc',
selfIntroduction:
'株式会社Neverとはモバイルアプリケーションをメインに開発、運用をおこなっております。',
),
),
// タブの情報
SliverPersistentHeader(
pinned: true,
delegate: _StickyTabBarDelegate(
TabBar(
controller: _tabController,
tabs:
TabItem.values.map((e) => Tab(text: e.title)).toList(),
indicatorColor: Colors.blue,
indicatorSize: TabBarIndicatorSize.label,
labelColor: Colors.blue,
labelPadding: EdgeInsets.zero,
),
),
),
],
},
// タブごとにウィジェットを設定
body: TabBarView(
controller: _tabController,
children: <Widget>[
ListView.separated(
key: PageStorageKey<String>(TabItem.post.title),
itemCount: 100,
itemBuilder: (BuildContext context, int index) {
return _PostTile(
index: index,
userName: '株式会社Never',
userId: '@never_inc',
);
},
separatorBuilder: (context, _) {
return const Divider(height: 1);
},
),
ListView.separated(
key: PageStorageKey<String>(TabItem.favorite.title),
itemCount: 100,
itemBuilder: (BuildContext context, int index) {
return _PostTile(
index: index,
userName: 'HOGEHOGE',
userId: '@hoge_hoge',
);
},
separatorBuilder: (context, _) {
return const Divider(height: 1);
},
),
],
),
),
※ _ProfileTileについてはの実装はこちら
※ _PostTileについてはの実装はこちら
_StickyTabBarDelegateについて
SliverPersistentHeaderDelegate
を継承した_StickyTabBarDelegate
クラスを使用して、TabBarの動作を制御します。このクラスでは、TabBarの高さを計算し、buildメソッドでTabBarを含むウィジェットを返します。
また、shouldRebuild
では、古いデリゲートと新しいデリゲートが異なる場合にのみウィジェットを再構築するように設定します。
class _StickyTabBarDelegate extends SliverPersistentHeaderDelegate {
const _StickyTabBarDelegate(this.tabBar);
final TabBar tabBar;
@override
double get minExtent => tabBar.preferredSize.height * 0.7;
@override
double get maxExtent => tabBar.preferredSize.height * 0.7;
@override
Widget build(
BuildContext context,
double shrinkOffset,
bool overlapsContent,
) {
return ColoredBox(
color: Theme.of(context).scaffoldBackgroundColor,
child: tabBar,
);
}
@override
bool shouldRebuild(_StickyTabBarDelegate oldDelegate) {
return tabBar != oldDelegate.tabBar;
}
}
タブを切り替えてもスクロールした位置を状態を維持
ListView
にPageStorageKey
を渡すことで、タブを切り替えるたびにスクロール位置を維持することができます。
ListView.separated(
key: PageStorageKey<String>(TabItem.favorite.title),
itemCount: 100,
itemBuilder: (BuildContext context, int index) {
return _PostTile(
index: index,
userName: '株式会社Never',
userId: '@never_inc',
);
},
separatorBuilder: (context, _) {
return const Divider(height: 1);
},
),
まとめ
ExtendedNestedScrollViewを使用してプロフィール画面を作成する方法を説明しました。
画面をスクロールした時にタブを上に固定して、タブを切り替えた際にスクロール位置が保持されるプロフィール画面を作成することができました。
今回のソースコードはこちらのGitHubにて公開してます。
おわりに
最後までお読みいただきありがとうございます。
間違っている点などあればコメントにて指摘いただけますと幸いです。
アプリ設計や実装でお困りの際はお気楽にお問合せください。