💭
Flutterの縦スクロールのPageView.builderでRefreshしたい
FlutterのPageView.builder
を使って縦型スクロールの画面を実装しているときに問題に直面しました。
解決策を考えてみたので忘れないうちに書いておきます。
やりたいこと
PageView.builder
を使って縦型スクロールの画面において、一番最初のページから更に戻ろうとしたら何か処理を実行したい
赤い画面から更に戻ろうとしたら、何か処理を実行したいです。(pull to refresh
的な感じで)
(例えばTikTokだと一番最初の動画から、さらに戻ろうとすると更新処理が走る)
class FeedPage extends StatelessWidget {
const FeedPage({Key? key}) : super(key: key);
Widget build(BuildContext context) {
List<Widget> containers = [
Container(
color: Colors.red,
),
Container(
color: Colors.blue,
),
Container(
color: Colors.green,
),
];
return Scaffold(
body: PageView.builder(
scrollDirection: Axis.vertical,
itemCount: containers.length,
itemBuilder: (BuildContext context, int index) {
return containers[index];
},
),
);
}
}
直面した問題
indexが0のページから更に戻りたいときって、どうやって検知すれば良いのか?
無理だったこと
-
GestureDetector
のonVerticalDrag***
を使う
あらゆる場所に仕込んでみたが、ダメだった。多分イベントがconflictしてる。
GitHubのissueも検索しましたが、PageView
の中でGestureDetector
を使うのは、なかなか難しそうですね。。
考えた解決策
-
index: 0
のページにダミーのページを入れる - pageControllerのlistenerで
PageController.page
が1未満になったときを検知して、そこで処理を実行する- ダミーのページには遷移させたくないので、すぐに
jumpToPage(1)
をする
- ダミーのページには遷移させたくないので、すぐに
class FeedPage extends HookConsumerWidget {
const FeedPage({Key? key}) : super(key: key);
Widget build(BuildContext context, WidgetRef ref) {
List<Widget> containers = [
Container(
color: Colors.red,
),
Container(
color: Colors.blue,
),
Container(
color: Colors.green,
),
];
Widget dummyContainer = Container(child: Text('dummyContainer'));
final _pageController = PageController(initialPage: 1);
useEffect(
() {
void listener() {
if (_pageController.page! < 1) {
const snackBar = SnackBar(
content: Text('OnRefresh'),
);
ScaffoldMessenger.of(context).showSnackBar(snackBar);
_pageController.jumpToPage(1);
}
}
_pageController.addListener(listener);
return () {
_pageController.removeListener(listener);
};
},
[],
);
return Scaffold(
body: PageView.builder(
scrollDirection: Axis.vertical,
controller: _pageController,
itemCount: containers.length + 1,
onPageChanged: (value) {
if (value == 0) {
return _pageController.jumpToPage(1);
}
},
itemBuilder: (BuildContext context, int index) {
if (index == 0) {
return dummyContainer;
}
return containers[index - 1];
},
),
);
}
}
20240130 追記
- 良い方法見つけたので追記
-
GestureDetector
のonVerticalDrag***
の中で、下向きと上向きのドラッグを検知して、上向きにスライドさせるドラッグであれば、pageController経由でスクロールさせて、下向きであれば、処理を走らせるようにすればConflictしない。
class FeedTabPage extends HookConsumerWidget {
const FeedTabPage({Key? key}) : super(key: key);
Widget build(BuildContext context, WidgetRef ref) {
final pageController = PageController(initialPage: 0);
List<Widget> containers = [
FirstContainer(pageController),
Container(
color: Colors.red,
),
Container(
color: Colors.green,
),
];
return Scaffold(
body: PageView.builder(
scrollDirection: Axis.vertical,
controller: pageController,
itemCount: containers.length,
itemBuilder: (BuildContext context, int index) {
return containers[index];
},
),
);
}
}
class FirstContainer extends HookConsumerWidget {
const FirstContainer(this._pageController, {Key? key}) : super(key: key);
final PageController _pageController;
Widget build(BuildContext context, WidgetRef ref) {
final dySum = useState<double>(0);
final isSnackbarShowing = useState<bool>(false);
return GestureDetector(
onVerticalDragStart: (details) {
dySum.value = 0;
},
onVerticalDragUpdate: (details) {
final dy = details.delta.dy;
if (dy > 0 && !isSnackbarShowing.value) {
const snackBar = SnackBar(
content: Text('OnRefresh'),
);
ScaffoldMessenger.of(context).showSnackBar(
snackBar,
);
isSnackbarShowing.value = true;
return;
}
dySum.value = dySum.value + -1 * dy;
_pageController.jumpTo(dySum.value);
},
onVerticalDragEnd: (details) {
isSnackbarShowing.value = false;
dySum.value = 0;
},
child: Container(
color: Colors.blue,
),
);
}
}
結果
なにか、他に良い方法があったら教えてください!
Discussion