【Flutter】最上部より少し上までスクロールしたときに閉じるWidgetを作るときの注意点
実装した物
方針
画面遷移
閉じたいWidget(ソースコード: SliverItemView
)を表示する方法としては、以下の3パターンを検討。
Navigator.push(context, MaterialPageRoute(fullscreenDialog: true, builder: ...)
でページ下部から新しいページが出てくるようなアニメーションで画面遷移showModalBottomSheet
でモーダル表示(何故か上部のSafeArea
が無視されるので良くないかも)modal_bottom_sheet
パッケージのshowCupertinoModalBottomSheet
、showBarModalBottomSheet
を使う。
~~ Card( child: ListTile( title: Text('Sample2:'), subtitle: Text( 'modal_bottom_sheetパッケージのshowCupertinoModalBottomSheetをそのまま使った場合'), trailing: const Icon(Icons.open_in_new), onTap: () { showCupertinoModalBottomSheet( context: context, builder: (_) => SliverItemView(), ); }, ), ), ~~ Card( child: ListTile( title: Text('Sample6:'), subtitle: Text('Navigator.pushとfullscreenDialog:trueで画面遷移'), trailing: const Icon(Icons.open_in_new), onTap: () { Navigator.push( context, MaterialPageRoute( builder: (_) => SliverItemView(), fullscreenDialog: true, ), ); }, ), ), ~~
スクロール位置に応じてページを閉じる
- 現在のスクロール位置は
ScrollController.offset
プロパティから取得する。 - 閉じたいWidget(
SliverItemView
)で、スクロール位置が閾値以下になったらNavigator.pop
で表示中のWIdgetを閉じるようなlistenerを定義する。
注意点
スクロールイベントは連続して何回も起こるようで、何も工夫しないとlistenerに定義したNavigator.pop(context)
が何度も呼ばれてしまい真っ黒な画面にとばされてしまう。そのため今回のコードでは適当なフラグを建てて、条件を満たした最初の1回のみ処理を行うようにしている。
class _SliverItemViewState extends State<SliverItemView> { final ScrollController _scrollController = ScrollController(); // _onScrollChangedListenerが2回以上呼ばれてしまうため、 // Navigator.popが何回も呼ばれ真っ黒い画面に飛ばされてしまうのを防ぐフラグ bool isFirstEvent = true; @override void initState() { super.initState(); _scrollController.addListener(_onScrollChangedListener); } @override void dispose() { super.dispose(); _scrollController.removeListener(_onScrollChangedListener); } void _onScrollChangedListener() { if (isFirstEvent && _scrollController.offset < _dismissThreshold) { isFirstEvent = false; Navigator.pop(context); } } ~~
その他の注意点
SliverList
(またはSliverGrid
)の要素数が少ないとスクロールできない
デフォルトではSliverList
の要素数が少なくスクロールしなくても全要素見えている場合は、スクロールができない。
これは、CustomScrollView
のphysics
プロパティにAlwaysScrollableScrollPhysics()
を指定することで解決できる。
(要素数にかかわらずスクロール動作が発生するようになる。)
@override Widget build(BuildContext context) { return Scaffold( body: CustomScrollView( controller: _scrollController, physics: AlwaysScrollableScrollPhysics(), // <- 追加 slivers: [ SliverAppBar( title: Text(this.toStringShort()), floating: true, ), SliverList( delegate: _buildSliverChildListDelegate(), ), ], ), ); }